github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/lxd/server_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  	"github.com/golang/mock/gomock"
    13  	"github.com/juju/errors"
    14  	"github.com/juju/juju/cloud"
    15  	"github.com/juju/juju/environs"
    16  	client "github.com/lxc/lxd/client"
    17  	"github.com/lxc/lxd/shared/api"
    18  	gc "gopkg.in/check.v1"
    19  
    20  	containerLXD "github.com/juju/juju/container/lxd"
    21  	"github.com/juju/juju/provider/lxd"
    22  )
    23  
    24  var (
    25  	_ = gc.Suite(&serverSuite{})
    26  )
    27  
    28  type serverSuite struct{}
    29  
    30  func (s *serverSuite) TestLocalServer(c *gc.C) {
    31  	ctrl := gomock.NewController(c)
    32  	defer ctrl.Finish()
    33  
    34  	profile := &api.Profile{}
    35  	etag := "etag"
    36  	bridgeName := "lxdbr0"
    37  	hostAddress := "192.168.0.1"
    38  	connectionInfo := &client.ConnectionInfo{
    39  		Addresses: []string{
    40  			"https://192.168.0.1:8443",
    41  		},
    42  	}
    43  	serverInfo := &api.Server{
    44  		ServerUntrusted: api.ServerUntrusted{
    45  			APIVersion: "1.1",
    46  		},
    47  	}
    48  
    49  	factory, server, interfaceAddr := s.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().GetServer().Return(serverInfo, etag, nil),
    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 *serverSuite) 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  	serverInfo := &api.Server{
    87  		ServerUntrusted: api.ServerUntrusted{
    88  			APIVersion: "1.1",
    89  		},
    90  	}
    91  
    92  	factory, server, interfaceAddr := s.newLocalServerFactory(ctrl)
    93  
    94  	gomock.InOrder(
    95  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
    96  		server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil),
    97  		server.EXPECT().EnableHTTPSListener().Return(nil),
    98  		server.EXPECT().LocalBridgeName().Return(bridgeName),
    99  		interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil),
   100  		server.EXPECT().GetConnectionInfo().Return(emptyConnectionInfo, nil),
   101  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
   102  		server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil),
   103  		server.EXPECT().EnableHTTPSListener().Return(nil),
   104  		server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil),
   105  		server.EXPECT().StorageSupported().Return(true),
   106  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
   107  		server.EXPECT().EnsureDefaultStorage(profile, etag).Return(nil),
   108  		server.EXPECT().GetServer().Return(serverInfo, etag, nil),
   109  	)
   110  
   111  	svr, err := factory.LocalServer()
   112  	c.Assert(svr, gc.Not(gc.IsNil))
   113  	c.Assert(svr, gc.Equals, server)
   114  	c.Assert(err, gc.IsNil)
   115  }
   116  
   117  func (s *serverSuite) TestLocalServerRetrySemanticsFailure(c *gc.C) {
   118  	ctrl := gomock.NewController(c)
   119  	defer ctrl.Finish()
   120  
   121  	profile := &api.Profile{}
   122  	etag := "etag"
   123  	bridgeName := "lxdbr0"
   124  	hostAddress := "192.168.0.1"
   125  	emptyConnectionInfo := &client.ConnectionInfo{
   126  		Addresses: []string{},
   127  	}
   128  
   129  	factory, server, interfaceAddr := s.newLocalServerFactory(ctrl)
   130  
   131  	server.EXPECT().GetProfile("default").Return(profile, etag, nil).Times(31)
   132  	server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil).Times(31)
   133  	server.EXPECT().EnableHTTPSListener().Return(nil).Times(31)
   134  	server.EXPECT().LocalBridgeName().Return(bridgeName)
   135  	interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil)
   136  	server.EXPECT().GetConnectionInfo().Return(emptyConnectionInfo, nil).Times(30)
   137  
   138  	svr, err := factory.LocalServer()
   139  	c.Assert(svr, gc.IsNil)
   140  	c.Assert(err.Error(), gc.Equals, "LXD is not listening on address https://192.168.0.1 (reported addresses: [])")
   141  }
   142  
   143  func (s *serverSuite) TestLocalServerWithInvalidAPIVersion(c *gc.C) {
   144  	ctrl := gomock.NewController(c)
   145  	defer ctrl.Finish()
   146  
   147  	profile := &api.Profile{}
   148  	etag := "etag"
   149  	bridgeName := "lxdbr0"
   150  	hostAddress := "192.168.0.1"
   151  	connectionInfo := &client.ConnectionInfo{
   152  		Addresses: []string{
   153  			"https://192.168.0.1:8443",
   154  		},
   155  	}
   156  	serverInfo := &api.Server{
   157  		ServerUntrusted: api.ServerUntrusted{
   158  			APIVersion: "a.b",
   159  		},
   160  	}
   161  
   162  	factory, server, interfaceAddr := s.newLocalServerFactory(ctrl)
   163  
   164  	gomock.InOrder(
   165  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
   166  		server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil),
   167  		server.EXPECT().EnableHTTPSListener().Return(nil),
   168  		server.EXPECT().LocalBridgeName().Return(bridgeName),
   169  		interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil),
   170  		server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil),
   171  		server.EXPECT().StorageSupported().Return(true),
   172  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
   173  		server.EXPECT().EnsureDefaultStorage(profile, etag).Return(nil),
   174  		server.EXPECT().GetServer().Return(serverInfo, etag, nil),
   175  	)
   176  
   177  	svr, err := factory.LocalServer()
   178  	c.Assert(svr, gc.Not(gc.IsNil))
   179  	c.Assert(svr, gc.Equals, server)
   180  	c.Assert(err, gc.IsNil)
   181  }
   182  
   183  func (s *serverSuite) TestLocalServerErrorMessageShowsInstallMessage(c *gc.C) {
   184  	ctrl := gomock.NewController(c)
   185  	defer ctrl.Finish()
   186  
   187  	factory := lxd.NewServerFactoryWithMocks(
   188  		func() (lxd.Server, error) {
   189  			return nil, errors.New("bad")
   190  		},
   191  		defaultRemoteServerFunc(ctrl),
   192  		nil,
   193  		&lxd.MockClock{},
   194  	)
   195  
   196  	_, err := factory.LocalServer()
   197  	c.Assert(errors.Cause(err).Error(), gc.Equals, `bad
   198  
   199  Please install LXD by running:
   200  	$ sudo snap install lxd
   201  and then configure it with:
   202  	$ newgrp lxd
   203  	$ lxd init
   204  `)
   205  }
   206  
   207  func (s *serverSuite) TestLocalServerErrorMessageShowsConfigureMessage(c *gc.C) {
   208  	ctrl := gomock.NewController(c)
   209  	defer ctrl.Finish()
   210  
   211  	factory := lxd.NewServerFactoryWithMocks(
   212  		func() (lxd.Server, error) {
   213  			return nil, errors.Annotatef(&url.Error{
   214  				Err: &net.OpError{
   215  					Op:  "dial",
   216  					Net: "unix",
   217  					Err: &os.SyscallError{
   218  						Err: syscall.ECONNREFUSED,
   219  					},
   220  				},
   221  			}, "bad")
   222  		},
   223  		defaultRemoteServerFunc(ctrl),
   224  		nil,
   225  		&lxd.MockClock{},
   226  	)
   227  
   228  	_, err := factory.LocalServer()
   229  	c.Assert(errors.Cause(err).Error(), gc.Equals, `LXD refused connections; is LXD running?
   230  
   231  Please configure LXD by running:
   232  	$ newgrp lxd
   233  	$ lxd init
   234  `)
   235  }
   236  
   237  func (s *serverSuite) TestLocalServerErrorMessageShowsConfigureMessageWhenEACCES(c *gc.C) {
   238  	ctrl := gomock.NewController(c)
   239  	defer ctrl.Finish()
   240  
   241  	factory := lxd.NewServerFactoryWithMocks(
   242  		func() (lxd.Server, error) {
   243  			return nil, errors.Annotatef(&url.Error{
   244  				Err: &net.OpError{
   245  					Op:  "dial",
   246  					Net: "unix",
   247  					Err: &os.SyscallError{
   248  						Err: syscall.EACCES,
   249  					},
   250  				},
   251  			}, "bad")
   252  		},
   253  		defaultRemoteServerFunc(ctrl),
   254  		nil,
   255  		&lxd.MockClock{},
   256  	)
   257  
   258  	_, err := factory.LocalServer()
   259  	c.Assert(errors.Cause(err).Error(), gc.Equals, `Permission denied, are you in the lxd group?
   260  
   261  Please configure LXD by running:
   262  	$ newgrp lxd
   263  	$ lxd init
   264  `)
   265  }
   266  
   267  func (s *serverSuite) TestLocalServerErrorMessageShowsInstallMessageWhenENOENT(c *gc.C) {
   268  	ctrl := gomock.NewController(c)
   269  	defer ctrl.Finish()
   270  
   271  	factory := lxd.NewServerFactoryWithMocks(
   272  		func() (lxd.Server, error) {
   273  			return nil, errors.Annotatef(&url.Error{
   274  				Err: &net.OpError{
   275  					Op:  "dial",
   276  					Net: "unix",
   277  					Err: &os.SyscallError{
   278  						Err: syscall.ENOENT,
   279  					},
   280  				},
   281  			}, "bad")
   282  		},
   283  		defaultRemoteServerFunc(ctrl),
   284  		nil,
   285  		&lxd.MockClock{},
   286  	)
   287  
   288  	_, err := factory.LocalServer()
   289  	c.Assert(errors.Cause(err).Error(), gc.Equals, `LXD socket not found; is LXD installed & running?
   290  
   291  Please install LXD by running:
   292  	$ sudo snap install lxd
   293  and then configure it with:
   294  	$ newgrp lxd
   295  	$ lxd init
   296  `)
   297  }
   298  
   299  func (s *serverSuite) TestLocalServerWithStorageNotSupported(c *gc.C) {
   300  	ctrl := gomock.NewController(c)
   301  	defer ctrl.Finish()
   302  
   303  	profile := &api.Profile{}
   304  	etag := "etag"
   305  	bridgeName := "lxdbr0"
   306  	hostAddress := "192.168.0.1"
   307  	connectionInfo := &client.ConnectionInfo{
   308  		Addresses: []string{
   309  			"https://192.168.0.1:8443",
   310  		},
   311  	}
   312  	serverInfo := &api.Server{
   313  		ServerUntrusted: api.ServerUntrusted{
   314  			APIVersion: "2.2",
   315  		},
   316  	}
   317  
   318  	factory, server, interfaceAddr := s.newLocalServerFactory(ctrl)
   319  
   320  	gomock.InOrder(
   321  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
   322  		server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil),
   323  		server.EXPECT().EnableHTTPSListener().Return(nil),
   324  		server.EXPECT().LocalBridgeName().Return(bridgeName),
   325  		interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil),
   326  		server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil),
   327  		server.EXPECT().StorageSupported().Return(false),
   328  		server.EXPECT().GetServer().Return(serverInfo, etag, nil),
   329  	)
   330  
   331  	svr, err := factory.RemoteServer(environs.CloudSpec{
   332  		Endpoint: "",
   333  	})
   334  	c.Assert(svr, gc.Not(gc.IsNil))
   335  	c.Assert(err, gc.IsNil)
   336  }
   337  
   338  func (s *serverSuite) TestRemoteServerWithEmptyEndpointYieldsLocalServer(c *gc.C) {
   339  	ctrl := gomock.NewController(c)
   340  	defer ctrl.Finish()
   341  
   342  	profile := &api.Profile{}
   343  	etag := "etag"
   344  	bridgeName := "lxdbr0"
   345  	hostAddress := "192.168.0.1"
   346  	connectionInfo := &client.ConnectionInfo{
   347  		Addresses: []string{
   348  			"https://192.168.0.1:8443",
   349  		},
   350  	}
   351  	serverInfo := &api.Server{
   352  		ServerUntrusted: api.ServerUntrusted{
   353  			APIVersion: "1.1",
   354  		},
   355  	}
   356  
   357  	factory, server, interfaceAddr := s.newLocalServerFactory(ctrl)
   358  
   359  	gomock.InOrder(
   360  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
   361  		server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil),
   362  		server.EXPECT().EnableHTTPSListener().Return(nil),
   363  		server.EXPECT().LocalBridgeName().Return(bridgeName),
   364  		interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil),
   365  		server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil),
   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().GetServer().Return(serverInfo, etag, nil),
   370  	)
   371  
   372  	svr, err := factory.RemoteServer(environs.CloudSpec{
   373  		Endpoint: "",
   374  	})
   375  	c.Assert(svr, gc.Not(gc.IsNil))
   376  	c.Assert(err, gc.IsNil)
   377  }
   378  
   379  func (s *serverSuite) TestRemoteServer(c *gc.C) {
   380  	ctrl := gomock.NewController(c)
   381  	defer ctrl.Finish()
   382  
   383  	profile := &api.Profile{}
   384  	etag := "etag"
   385  	serverInfo := &api.Server{
   386  		ServerUntrusted: api.ServerUntrusted{
   387  			APIVersion: "1.1",
   388  		},
   389  	}
   390  
   391  	factory, server := s.newRemoteServerFactory(ctrl)
   392  
   393  	gomock.InOrder(
   394  		server.EXPECT().StorageSupported().Return(true),
   395  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
   396  		server.EXPECT().EnsureDefaultStorage(profile, etag).Return(nil),
   397  		server.EXPECT().GetServer().Return(serverInfo, etag, nil),
   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(environs.CloudSpec{
   406  		Endpoint:   "https://10.0.0.9:8443",
   407  		Credential: &creds,
   408  	})
   409  	c.Assert(svr, gc.Not(gc.IsNil))
   410  	c.Assert(svr, gc.Equals, server)
   411  	c.Assert(err, gc.IsNil)
   412  }
   413  
   414  func (s *serverSuite) TestRemoteServerWithNoStorage(c *gc.C) {
   415  	ctrl := gomock.NewController(c)
   416  	defer ctrl.Finish()
   417  
   418  	etag := "etag"
   419  	serverInfo := &api.Server{
   420  		ServerUntrusted: api.ServerUntrusted{
   421  			APIVersion: "1.1",
   422  		},
   423  	}
   424  
   425  	factory, server := s.newRemoteServerFactory(ctrl)
   426  
   427  	gomock.InOrder(
   428  		server.EXPECT().StorageSupported().Return(false),
   429  		server.EXPECT().GetServer().Return(serverInfo, etag, nil),
   430  	)
   431  
   432  	creds := cloud.NewCredential("any", map[string]string{
   433  		"client-cert": "client-cert",
   434  		"client-key":  "client-key",
   435  		"server-cert": "server-cert",
   436  	})
   437  	svr, err := factory.RemoteServer(environs.CloudSpec{
   438  		Endpoint:   "https://10.0.0.9:8443",
   439  		Credential: &creds,
   440  	})
   441  	c.Assert(svr, gc.Not(gc.IsNil))
   442  	c.Assert(svr, gc.Equals, server)
   443  	c.Assert(err, gc.IsNil)
   444  }
   445  
   446  func (s *serverSuite) TestRemoteServerMissingCertificates(c *gc.C) {
   447  	ctrl := gomock.NewController(c)
   448  	defer ctrl.Finish()
   449  
   450  	factory, _ := s.newRemoteServerFactory(ctrl)
   451  
   452  	creds := cloud.NewCredential("any", map[string]string{})
   453  	svr, err := factory.RemoteServer(environs.CloudSpec{
   454  		Endpoint:   "https://10.0.0.9:8443",
   455  		Credential: &creds,
   456  	})
   457  	c.Assert(svr, gc.IsNil)
   458  	c.Assert(errors.Cause(err).Error(), gc.Equals, "credentials not valid")
   459  }
   460  
   461  func (s *serverSuite) TestRemoteServerWithGetServerError(c *gc.C) {
   462  	ctrl := gomock.NewController(c)
   463  	defer ctrl.Finish()
   464  
   465  	factory, server := s.newRemoteServerFactory(ctrl)
   466  
   467  	gomock.InOrder(
   468  		server.EXPECT().StorageSupported().Return(false),
   469  		server.EXPECT().GetServer().Return(nil, "", errors.New("bad")),
   470  	)
   471  
   472  	creds := cloud.NewCredential("any", map[string]string{
   473  		"client-cert": "client-cert",
   474  		"client-key":  "client-key",
   475  		"server-cert": "server-cert",
   476  	})
   477  	_, err := factory.RemoteServer(environs.CloudSpec{
   478  		Endpoint:   "https://10.0.0.9:8443",
   479  		Credential: &creds,
   480  	})
   481  	c.Assert(errors.Cause(err).Error(), gc.Equals, "bad")
   482  }
   483  
   484  func (s *serverSuite) newLocalServerFactory(ctrl *gomock.Controller) (lxd.ServerFactory, *lxd.MockServer, *lxd.MockInterfaceAddress) {
   485  	server := lxd.NewMockServer(ctrl)
   486  	interfaceAddr := lxd.NewMockInterfaceAddress(ctrl)
   487  
   488  	factory := lxd.NewServerFactoryWithMocks(
   489  		func() (lxd.Server, error) {
   490  			return server, nil
   491  		},
   492  		defaultRemoteServerFunc(ctrl),
   493  		interfaceAddr,
   494  		&lxd.MockClock{},
   495  	)
   496  
   497  	return factory, server, interfaceAddr
   498  }
   499  
   500  func (s *serverSuite) newRemoteServerFactory(ctrl *gomock.Controller) (lxd.ServerFactory, *lxd.MockServer) {
   501  	server := lxd.NewMockServer(ctrl)
   502  	interfaceAddr := lxd.NewMockInterfaceAddress(ctrl)
   503  
   504  	return lxd.NewServerFactoryWithMocks(
   505  		defaultLocalServerFunc(ctrl),
   506  		func(spec containerLXD.ServerSpec) (lxd.Server, error) {
   507  			return server, nil
   508  		},
   509  		interfaceAddr,
   510  		&lxd.MockClock{},
   511  	), server
   512  }
   513  
   514  func defaultLocalServerFunc(ctrl *gomock.Controller) func() (lxd.Server, error) {
   515  	return func() (lxd.Server, error) {
   516  		return lxd.NewMockServer(ctrl), nil
   517  	}
   518  }
   519  
   520  func defaultRemoteServerFunc(ctrl *gomock.Controller) func(containerLXD.ServerSpec) (lxd.Server, error) {
   521  	return func(containerLXD.ServerSpec) (lxd.Server, error) {
   522  		return lxd.NewMockServer(ctrl), nil
   523  	}
   524  }
   525  
   526  func (s *serverSuite) TestIsSupportedAPIVersion(c *gc.C) {
   527  	for _, t := range []struct {
   528  		input    string
   529  		expected bool
   530  		output   string
   531  	}{
   532  		{
   533  			input:    "foo",
   534  			expected: false,
   535  			output:   `LXD API version "foo": expected format <major>\.<minor>`,
   536  		},
   537  		{
   538  			input:    "a.b",
   539  			expected: false,
   540  			output:   `LXD API version "a.b": unexpected major number: strconv.(ParseInt|Atoi): parsing "a": invalid syntax`,
   541  		},
   542  		{
   543  			input:    "0.9",
   544  			expected: false,
   545  			output:   `LXD API version "0.9": expected major version 1 or later`,
   546  		},
   547  		{
   548  			input:    "1.0",
   549  			expected: true,
   550  			output:   "",
   551  		},
   552  		{
   553  			input:    "2.0",
   554  			expected: true,
   555  			output:   "",
   556  		},
   557  		{
   558  			input:    "2.1",
   559  			expected: true,
   560  			output:   "",
   561  		},
   562  	} {
   563  		msg, ok := lxd.IsSupportedAPIVersion(t.input)
   564  		c.Assert(ok, gc.Equals, t.expected)
   565  		c.Assert(msg, gc.Matches, t.output)
   566  	}
   567  }