github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/lxd/server.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package lxd
     5  
     6  import (
     7  	"fmt"
     8  	"net"
     9  	"net/url"
    10  	"os"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  	"syscall"
    15  	"time"
    16  
    17  	"github.com/juju/clock"
    18  	"github.com/juju/errors"
    19  	"github.com/juju/retry"
    20  	"github.com/juju/utils"
    21  
    22  	"github.com/juju/juju/container/lxd"
    23  	"github.com/juju/juju/environs"
    24  	"github.com/juju/juju/network"
    25  	"github.com/juju/juju/utils/proxy"
    26  
    27  	lxdclient "github.com/lxc/lxd/client"
    28  	lxdapi "github.com/lxc/lxd/shared/api"
    29  )
    30  
    31  // Server defines an interface of all localized methods that the environment
    32  // and provider utilizes.
    33  //go:generate mockgen -package lxd -destination server_mock_test.go github.com/juju/juju/provider/lxd Server,ServerFactory,InterfaceAddress
    34  type Server interface {
    35  	FindImage(string, string, []lxd.ServerSpec, bool, environs.StatusCallbackFunc) (lxd.SourcedImage, error)
    36  	GetServer() (server *lxdapi.Server, ETag string, err error)
    37  	GetConnectionInfo() (info *lxdclient.ConnectionInfo, err error)
    38  	UpdateServerConfig(map[string]string) error
    39  	UpdateContainerConfig(string, map[string]string) error
    40  	CreateCertificate(lxdapi.CertificatesPost) error
    41  	GetCertificate(fingerprint string) (certificate *lxdapi.Certificate, ETag string, err error)
    42  	DeleteCertificate(fingerprint string) (err error)
    43  	CreateClientCertificate(certificate *lxd.Certificate) error
    44  	LocalBridgeName() string
    45  	AliveContainers(prefix string) ([]lxd.Container, error)
    46  	ContainerAddresses(name string) ([]network.Address, error)
    47  	RemoveContainer(name string) error
    48  	RemoveContainers(names []string) error
    49  	FilterContainers(prefix string, statuses ...string) ([]lxd.Container, error)
    50  	CreateContainerFromSpec(spec lxd.ContainerSpec) (*lxd.Container, error)
    51  	WriteContainer(*lxd.Container) error
    52  	CreateProfileWithConfig(string, map[string]string) error
    53  	GetProfile(string) (*lxdapi.Profile, string, error)
    54  	GetContainerProfiles(string) ([]string, error)
    55  	HasProfile(string) (bool, error)
    56  	CreateProfile(post lxdapi.ProfilesPost) (err error)
    57  	DeleteProfile(string) (err error)
    58  	ReplaceOrAddContainerProfile(string, string, string) error
    59  	VerifyNetworkDevice(*lxdapi.Profile, string) error
    60  	EnsureDefaultStorage(*lxdapi.Profile, string) error
    61  	StorageSupported() bool
    62  	GetStoragePool(name string) (pool *lxdapi.StoragePool, ETag string, err error)
    63  	GetStoragePools() (pools []lxdapi.StoragePool, err error)
    64  	CreatePool(name, driver string, attrs map[string]string) error
    65  	GetStoragePoolVolume(pool string, volType string, name string) (*lxdapi.StorageVolume, string, error)
    66  	GetStoragePoolVolumes(pool string) (volumes []lxdapi.StorageVolume, err error)
    67  	CreateVolume(pool, name string, config map[string]string) error
    68  	UpdateStoragePoolVolume(pool string, volType string, name string, volume lxdapi.StorageVolumePut, ETag string) error
    69  	DeleteStoragePoolVolume(pool string, volType string, name string) (err error)
    70  	ServerCertificate() string
    71  	HostArch() string
    72  	EnableHTTPSListener() error
    73  	GetNICsFromProfile(profName string) (map[string]map[string]string, error)
    74  	IsClustered() bool
    75  	UseTargetServer(name string) (*lxd.Server, error)
    76  	GetClusterMembers() (members []lxdapi.ClusterMember, err error)
    77  	Name() string
    78  }
    79  
    80  // ServerFactory creates a new factory for creating servers that are required
    81  // by the server.
    82  type ServerFactory interface {
    83  	// LocalServer creates a new lxd server and augments and wraps the lxd
    84  	// server, by ensuring sane defaults exist with network, storage.
    85  	LocalServer() (Server, error)
    86  
    87  	// LocalServerAddress returns the local servers address from the factory.
    88  	LocalServerAddress() (string, error)
    89  
    90  	// RemoteServer creates a new server that connects to a remote lxd server.
    91  	// If the cloudSpec endpoint is nil or empty, it will assume that you want
    92  	// to connection to a local server and will instead use that one.
    93  	RemoteServer(environs.CloudSpec) (Server, error)
    94  
    95  	// InsecureRemoteServer creates a new server that connect to a remote lxd
    96  	// server in a insecure manner.
    97  	// If the cloudSpec endpoint is nil or empty, it will assume that you want
    98  	// to connection to a local server and will instead use that one.
    99  	InsecureRemoteServer(environs.CloudSpec) (Server, error)
   100  }
   101  
   102  // InterfaceAddress groups methods that is required to find addresses
   103  // for a given interface
   104  type InterfaceAddress interface {
   105  
   106  	// InterfaceAddress looks for the network interface
   107  	// and returns the IPv4 address from the possible addresses.
   108  	// Returns an error if there is an issue locating the interface name or
   109  	// the address associated with it.
   110  	InterfaceAddress(string) (string, error)
   111  }
   112  
   113  type interfaceAddress struct{}
   114  
   115  func (interfaceAddress) InterfaceAddress(interfaceName string) (string, error) {
   116  	return utils.GetAddressForInterface(interfaceName)
   117  }
   118  
   119  type serverFactory struct {
   120  	newLocalServerFunc  func() (Server, error)
   121  	newRemoteServerFunc func(lxd.ServerSpec) (Server, error)
   122  	localServer         Server
   123  	localServerAddress  string
   124  	interfaceAddress    InterfaceAddress
   125  	clock               clock.Clock
   126  	mutex               sync.Mutex
   127  }
   128  
   129  // NewServerFactory creates a new ServerFactory with sane defaults.
   130  func NewServerFactory() ServerFactory {
   131  	return &serverFactory{
   132  		newLocalServerFunc: func() (Server, error) {
   133  			return lxd.NewLocalServer()
   134  		},
   135  		newRemoteServerFunc: func(spec lxd.ServerSpec) (Server, error) {
   136  			return lxd.NewRemoteServer(spec)
   137  		},
   138  		interfaceAddress: interfaceAddress{},
   139  	}
   140  }
   141  
   142  func (s *serverFactory) LocalServer() (Server, error) {
   143  	s.mutex.Lock()
   144  	defer s.mutex.Unlock()
   145  
   146  	// We have an instantiated localServer, that we can reuse over and over.
   147  	if s.localServer != nil {
   148  		return s.localServer, nil
   149  	}
   150  
   151  	// initialize a new local server
   152  	svr, err := s.initLocalServer()
   153  	if err != nil {
   154  		return nil, errors.Trace(err)
   155  	}
   156  
   157  	// bootstrap a new local server, this ensures that all connections to and
   158  	// from the local server are connected and setup correctly.
   159  	var hostName string
   160  	svr, hostName, err = s.bootstrapLocalServer(svr)
   161  	if err == nil {
   162  		s.localServer = svr
   163  		s.localServerAddress = hostName
   164  	}
   165  	return svr, errors.Trace(err)
   166  }
   167  
   168  func (s *serverFactory) LocalServerAddress() (string, error) {
   169  	s.mutex.Lock()
   170  	defer s.mutex.Unlock()
   171  
   172  	if s.localServer == nil {
   173  		return "", errors.NotAssignedf("local server")
   174  	}
   175  
   176  	return s.localServerAddress, nil
   177  }
   178  
   179  func (s *serverFactory) RemoteServer(spec environs.CloudSpec) (Server, error) {
   180  	if spec.Endpoint == "" {
   181  		return s.LocalServer()
   182  	}
   183  
   184  	cred := spec.Credential
   185  	if cred == nil {
   186  		return nil, errors.NotFoundf("credentials")
   187  	}
   188  
   189  	clientCert, serverCert, ok := getCertificates(*cred)
   190  	if !ok {
   191  		return nil, errors.NotValidf("credentials")
   192  	}
   193  	serverSpec := lxd.NewServerSpec(spec.Endpoint,
   194  		serverCert,
   195  		clientCert,
   196  	)
   197  	serverSpec.WithProxy(proxy.DefaultConfig.GetProxy)
   198  	svr, err := s.newRemoteServerFunc(serverSpec)
   199  	if err == nil {
   200  		err = s.bootstrapRemoteServer(svr)
   201  	}
   202  	return svr, errors.Trace(err)
   203  }
   204  
   205  func (s *serverFactory) InsecureRemoteServer(spec environs.CloudSpec) (Server, error) {
   206  	if spec.Endpoint == "" {
   207  		return s.LocalServer()
   208  	}
   209  
   210  	cred := spec.Credential
   211  	if cred == nil {
   212  		return nil, errors.NotFoundf("credentials")
   213  	}
   214  
   215  	clientCert, ok := getClientCertificates(*cred)
   216  	if !ok {
   217  		return nil, errors.NotValidf("credentials")
   218  	}
   219  
   220  	serverSpec := lxd.NewInsecureServerSpec(spec.Endpoint)
   221  	serverSpec.
   222  		WithClientCertificate(clientCert).
   223  		WithSkipGetServer(true)
   224  	svr, err := s.newRemoteServerFunc(serverSpec)
   225  	return svr, errors.Trace(err)
   226  }
   227  
   228  func (s *serverFactory) initLocalServer() (Server, error) {
   229  	svr, err := s.newLocalServerFunc()
   230  	if err != nil {
   231  		return nil, errors.Trace(hoistLocalConnectErr(err))
   232  	}
   233  
   234  	defaultProfile, profileETag, err := svr.GetProfile("default")
   235  	if err != nil {
   236  		return nil, errors.Trace(err)
   237  	}
   238  	if err := svr.VerifyNetworkDevice(defaultProfile, profileETag); err != nil {
   239  		return nil, errors.Trace(err)
   240  	}
   241  
   242  	// LXD itself reports the host:ports that it listens on.
   243  	// Cross-check the address we have with the values reported by LXD.
   244  	if err := svr.EnableHTTPSListener(); err != nil {
   245  		return nil, errors.Annotate(err, "enabling HTTPS listener")
   246  	}
   247  	return svr, nil
   248  }
   249  
   250  func (s *serverFactory) bootstrapLocalServer(svr Server) (Server, string, error) {
   251  	// select the server bridge name, so that we can then try and select
   252  	// the hostAddress from the current interfaceAddress
   253  	bridgeName := svr.LocalBridgeName()
   254  	hostAddress, err := s.interfaceAddress.InterfaceAddress(bridgeName)
   255  	if err != nil {
   256  		return nil, "", errors.Trace(err)
   257  	}
   258  	hostAddress = lxd.EnsureHTTPS(hostAddress)
   259  
   260  	// The following retry mechanism is required for newer LXD versions, where
   261  	// the new lxd client doesn't propagate the EnableHTTPSListener quick enough
   262  	// to get the addresses or on the same existing local provider.
   263  
   264  	// connInfoAddresses is really useful for debugging, so let's keep that
   265  	// information around for the debugging errors.
   266  	var connInfoAddresses []string
   267  	errNotExists := errors.New("not-exists")
   268  	retryArgs := retry.CallArgs{
   269  		Clock: s.Clock(),
   270  		IsFatalError: func(err error) bool {
   271  			return errors.Cause(err) != errNotExists
   272  		},
   273  		Func: func() error {
   274  			cInfo, err := svr.GetConnectionInfo()
   275  			if err != nil {
   276  				return errors.Trace(err)
   277  			}
   278  
   279  			connInfoAddresses = cInfo.Addresses
   280  			for _, addr := range cInfo.Addresses {
   281  				if strings.HasPrefix(addr, hostAddress+":") {
   282  					hostAddress = addr
   283  					return nil
   284  				}
   285  			}
   286  
   287  			// Requesting a NewLocalServer forces a new connection, so that when
   288  			// we GetConnectionInfo it gets the required addresses.
   289  			// Note: this modifies the outer svr server.
   290  			if svr, err = s.initLocalServer(); err != nil {
   291  				return errors.Trace(err)
   292  			}
   293  
   294  			return errNotExists
   295  		},
   296  		Delay:    2 * time.Second,
   297  		Attempts: 30,
   298  	}
   299  	if err := retry.Call(retryArgs); err != nil {
   300  		return nil, "", errors.Errorf(
   301  			"LXD is not listening on address %s (reported addresses: %s)",
   302  			hostAddress, connInfoAddresses,
   303  		)
   304  	}
   305  
   306  	// If the server is not a simple simple stream server, don't check the
   307  	// API version, but do report for other scenarios
   308  	if err := s.validateServer(svr); err != nil {
   309  		return nil, "", errors.Trace(err)
   310  	}
   311  
   312  	return svr, hostAddress, nil
   313  }
   314  
   315  func (s *serverFactory) bootstrapRemoteServer(svr Server) error {
   316  	err := s.validateServer(svr)
   317  	return errors.Trace(err)
   318  }
   319  
   320  func (s *serverFactory) validateServer(svr Server) error {
   321  	// If the storage API is supported, let's make sure the LXD has a
   322  	// default pool; we'll just use dir backend for now.
   323  	if svr.StorageSupported() {
   324  		// Ensure that the default profile has a network configuration that will
   325  		// allow access to containers that we create.
   326  		profile, eTag, err := svr.GetProfile("default")
   327  		if err != nil {
   328  			return errors.Trace(err)
   329  		}
   330  
   331  		if err := svr.EnsureDefaultStorage(profile, eTag); err != nil {
   332  			return errors.Trace(err)
   333  		}
   334  	}
   335  
   336  	// One final request, to make sure we grab the server information for
   337  	// validating the api version
   338  	serverInfo, _, err := svr.GetServer()
   339  	if err != nil {
   340  		return errors.Trace(err)
   341  	}
   342  
   343  	apiVersion := serverInfo.APIVersion
   344  	if msg, ok := isSupportedAPIVersion(apiVersion); !ok {
   345  		logger.Warningf(msg)
   346  		logger.Warningf("trying to use unsupported LXD API version %q", apiVersion)
   347  	} else {
   348  		logger.Tracef("using LXD API version %q", apiVersion)
   349  	}
   350  
   351  	return nil
   352  }
   353  
   354  func (s *serverFactory) Clock() clock.Clock {
   355  	if s.clock == nil {
   356  		return clock.WallClock
   357  	}
   358  	return s.clock
   359  }
   360  
   361  // isSupportedAPIVersion defines what API versions we support.
   362  func isSupportedAPIVersion(version string) (msg string, ok bool) {
   363  	versionParts := strings.Split(version, ".")
   364  	if len(versionParts) < 2 {
   365  		return fmt.Sprintf("LXD API version %q: expected format <major>.<minor>", version), false
   366  	}
   367  
   368  	major, err := strconv.Atoi(versionParts[0])
   369  	if err != nil {
   370  		return fmt.Sprintf("LXD API version %q: unexpected major number: %v", version, err), false
   371  	}
   372  
   373  	if major < 1 {
   374  		return fmt.Sprintf("LXD API version %q: expected major version 1 or later", version), false
   375  	}
   376  
   377  	return "", true
   378  }
   379  
   380  func getMessageFromErr(err error) (bool, string) {
   381  	msg := err.Error()
   382  	t, ok := errors.Cause(err).(*url.Error)
   383  	if !ok {
   384  		return false, msg
   385  	}
   386  
   387  	u, ok := t.Err.(*net.OpError)
   388  	if !ok {
   389  		return false, msg
   390  	}
   391  
   392  	if u.Op == "dial" && u.Net == "unix" {
   393  		var lxdErr error
   394  
   395  		sysErr, ok := u.Err.(*os.SyscallError)
   396  		if ok {
   397  			lxdErr = sysErr.Err
   398  		} else {
   399  			// Try a syscall.Errno as that is what's returned for CentOS
   400  			errno, ok := u.Err.(syscall.Errno)
   401  			if !ok {
   402  				return false, msg
   403  			}
   404  			lxdErr = errno
   405  		}
   406  
   407  		switch lxdErr {
   408  		case syscall.ENOENT:
   409  			return false, "LXD socket not found; is LXD installed & running?"
   410  		case syscall.ECONNREFUSED:
   411  			return true, "LXD refused connections; is LXD running?"
   412  		case syscall.EACCES:
   413  			return true, "Permission denied, are you in the lxd group?"
   414  		}
   415  	}
   416  
   417  	return false, msg
   418  }
   419  
   420  func hoistLocalConnectErr(err error) error {
   421  	installed, msg := getMessageFromErr(err)
   422  
   423  	configureText := `
   424  Please configure LXD by running:
   425  	$ newgrp lxd
   426  	$ lxd init
   427  `
   428  
   429  	installText := `
   430  Please install LXD by running:
   431  	$ sudo snap install lxd
   432  and then configure it with:
   433  	$ newgrp lxd
   434  	$ lxd init
   435  `
   436  
   437  	hint := installText
   438  	if installed {
   439  		hint = configureText
   440  	}
   441  
   442  	return errors.Trace(fmt.Errorf("%s\n%s", msg, hint))
   443  }