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

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package lxd
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/utils/arch"
     9  	"github.com/juju/utils/os"
    10  	"github.com/lxc/lxd/client"
    11  	"github.com/lxc/lxd/shared"
    12  	"github.com/lxc/lxd/shared/api"
    13  )
    14  
    15  // osSupport is the list of operating system types for which Juju supports
    16  // communicating with LXD via a local socket, by default.
    17  var osSupport = []os.OSType{os.Ubuntu}
    18  
    19  // HasSupport returns true if the current OS supports LXD containers by
    20  // default
    21  func HasSupport() bool {
    22  	t := os.HostOS()
    23  	for _, v := range osSupport {
    24  		if v == t {
    25  			return true
    26  		}
    27  	}
    28  	return false
    29  }
    30  
    31  // Server extends the upstream LXD container server.
    32  type Server struct {
    33  	lxd.ContainerServer
    34  
    35  	name              string
    36  	clustered         bool
    37  	serverCertificate string
    38  	hostArch          string
    39  
    40  	networkAPISupport bool
    41  	clusterAPISupport bool
    42  	storageAPISupport bool
    43  
    44  	localBridgeName string
    45  }
    46  
    47  // MaybeNewLocalServer returns a Server based on a local socket connection,
    48  // if running on an OS supporting LXD containers by default.
    49  // Otherwise a nil server is returned.
    50  func MaybeNewLocalServer() (*Server, error) {
    51  	if !HasSupport() {
    52  		return nil, nil
    53  	}
    54  	svr, err := NewLocalServer()
    55  	return svr, errors.Trace(err)
    56  }
    57  
    58  // NewLocalServer returns a Server based on a local socket connection.
    59  func NewLocalServer() (*Server, error) {
    60  	cSvr, err := ConnectLocal()
    61  	if err != nil {
    62  		return nil, errors.Trace(err)
    63  	}
    64  	svr, err := NewServer(cSvr)
    65  	return svr, errors.Trace(err)
    66  }
    67  
    68  // NewRemoteServer returns a Server based on a remote connection.
    69  func NewRemoteServer(spec ServerSpec) (*Server, error) {
    70  	if err := spec.Validate(); err != nil {
    71  		return nil, errors.Trace(err)
    72  	}
    73  
    74  	// Skip the get, because we know that we're going to request it
    75  	// when calling new server, preventing the double request.
    76  	spec.connectionArgs.SkipGetServer = true
    77  	cSvr, err := ConnectRemote(spec)
    78  	if err != nil {
    79  		return nil, errors.Trace(err)
    80  	}
    81  	svr, err := NewServer(cSvr)
    82  	return svr, err
    83  }
    84  
    85  // NewServer builds and returns a Server for high-level interaction with the
    86  // input LXD container server.
    87  func NewServer(svr lxd.ContainerServer) (*Server, error) {
    88  	info, _, err := svr.GetServer()
    89  	if err != nil {
    90  		return nil, errors.Trace(err)
    91  	}
    92  
    93  	apiExt := info.APIExtensions
    94  
    95  	name := info.Environment.ServerName
    96  	clustered := info.Environment.ServerClustered
    97  	if name == "" && !clustered {
    98  		// If the name is set to empty and clustering is false, then it's highly
    99  		// likely that we're on an older version of LXD. So in that case we
   100  		// need to set the name to something and internally LXD sets this type
   101  		// of node to "none".
   102  		// LP:#1786309
   103  		name = "none"
   104  	}
   105  	serverCertificate := info.Environment.Certificate
   106  	hostArch := arch.NormaliseArch(info.Environment.KernelArchitecture)
   107  
   108  	return &Server{
   109  		ContainerServer:   svr,
   110  		name:              name,
   111  		clustered:         clustered,
   112  		serverCertificate: serverCertificate,
   113  		hostArch:          hostArch,
   114  		networkAPISupport: shared.StringInSlice("network", apiExt),
   115  		clusterAPISupport: shared.StringInSlice("clustering", apiExt),
   116  		storageAPISupport: shared.StringInSlice("storage", apiExt),
   117  	}, nil
   118  }
   119  
   120  // Name returns the name of this LXD server.
   121  func (s *Server) Name() string {
   122  	return s.name
   123  }
   124  
   125  // UpdateServerConfig updates the server configuration with the input values.
   126  func (s *Server) UpdateServerConfig(cfg map[string]string) error {
   127  	svr, eTag, err := s.GetServer()
   128  	if err != nil {
   129  		return errors.Trace(err)
   130  	}
   131  	if svr.Config == nil {
   132  		svr.Config = make(map[string]interface{})
   133  	}
   134  	for k, v := range cfg {
   135  		svr.Config[k] = v
   136  	}
   137  	return errors.Trace(s.UpdateServer(svr.Writable(), eTag))
   138  }
   139  
   140  // UpdateContainerConfig updates the configuration for the container with the
   141  // input name, using the input values.
   142  func (s *Server) UpdateContainerConfig(name string, cfg map[string]string) error {
   143  	container, eTag, err := s.GetContainer(name)
   144  	if err != nil {
   145  		return errors.Trace(err)
   146  	}
   147  	if container.Config == nil {
   148  		container.Config = make(map[string]string)
   149  	}
   150  	for k, v := range cfg {
   151  		container.Config[k] = v
   152  	}
   153  
   154  	resp, err := s.UpdateContainer(name, container.Writable(), eTag)
   155  	if err != nil {
   156  		return errors.Trace(err)
   157  	}
   158  	return errors.Trace(resp.Wait())
   159  }
   160  
   161  // GetContainerProfiles returns the list of profiles that are assocated with a
   162  // container.
   163  func (s *Server) GetContainerProfiles(name string) ([]string, error) {
   164  	container, _, err := s.GetContainer(name)
   165  	if err != nil {
   166  		return []string{}, errors.Trace(err)
   167  	}
   168  	return container.Profiles, nil
   169  }
   170  
   171  // ReplaceOrAddContainerProfile updates the profiles for the container with the
   172  // input name, using the input values.
   173  func (s *Server) ReplaceOrAddContainerProfile(name, oldProfile, newProfile string) error {
   174  	container, eTag, err := s.GetContainer(name)
   175  	if err != nil {
   176  		return errors.Trace(errors.Annotatef(err, "failed to get container %q", name))
   177  	}
   178  	profiles := addRemoveReplaceProfileName(container.Profiles, oldProfile, newProfile)
   179  
   180  	container.Profiles = profiles
   181  	resp, err := s.UpdateContainer(name, container.Writable(), eTag)
   182  	if err != nil {
   183  		return errors.Trace(errors.Annotatef(err, "failed to updated container %q", name))
   184  	}
   185  
   186  	op := resp.Get()
   187  	logger.Debugf("updated container, waiting on %s", op.Description)
   188  	err = resp.Wait()
   189  	if err != nil {
   190  		logger.Tracef("updating container failed on %q", err)
   191  	}
   192  	return errors.Trace(err)
   193  }
   194  
   195  func addRemoveReplaceProfileName(profiles []string, oldProfile, newProfile string) []string {
   196  	if oldProfile == "" {
   197  		// add profile
   198  		profiles = append(profiles, newProfile)
   199  	} else {
   200  		for i, pName := range profiles {
   201  			if pName == oldProfile {
   202  				if newProfile == "" {
   203  					// remove profile
   204  					profiles = append(profiles[:i], profiles[i+1:]...)
   205  				} else {
   206  					// replace profile
   207  					profiles[i] = newProfile
   208  				}
   209  				break
   210  			}
   211  		}
   212  	}
   213  	return profiles
   214  }
   215  
   216  // CreateClientCertificate adds the input certificate to the server,
   217  // indicating that is for use in client communication.
   218  func (s *Server) CreateClientCertificate(cert *Certificate) error {
   219  	req, err := cert.AsCreateRequest()
   220  	if err != nil {
   221  		return errors.Trace(err)
   222  	}
   223  	return errors.Trace(s.CreateCertificate(req))
   224  }
   225  
   226  // HasProfile interrogates the known profile names and returns a boolean
   227  // indicating whether a profile with the input name exists.
   228  func (s *Server) HasProfile(name string) (bool, error) {
   229  	profiles, err := s.GetProfileNames()
   230  	if err != nil {
   231  		return false, errors.Trace(err)
   232  	}
   233  	for _, profile := range profiles {
   234  		if profile == name {
   235  			return true, nil
   236  		}
   237  	}
   238  	return false, nil
   239  }
   240  
   241  // CreateProfileWithConfig creates a new profile with the input name and config.
   242  func (s *Server) CreateProfileWithConfig(name string, cfg map[string]string) error {
   243  	req := api.ProfilesPost{
   244  		Name: name,
   245  		ProfilePut: api.ProfilePut{
   246  			Config: cfg,
   247  		},
   248  	}
   249  	return errors.Trace(s.CreateProfile(req))
   250  }
   251  
   252  // ServerCertificate returns the current server environment certificate
   253  func (s *Server) ServerCertificate() string {
   254  	return s.serverCertificate
   255  }
   256  
   257  // HostArch returns the current host architecture
   258  func (s *Server) HostArch() string {
   259  	return s.hostArch
   260  }
   261  
   262  // IsLXDNotFound checks if an error from the LXD API indicates that a requested
   263  // entity was not found.
   264  func IsLXDNotFound(err error) bool {
   265  	return err != nil && err.Error() == "not found"
   266  }