github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/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  	"net/http"
     8  	"strings"
     9  
    10  	lxd "github.com/canonical/lxd/client"
    11  	"github.com/canonical/lxd/shared/api"
    12  	"github.com/juju/clock"
    13  	"github.com/juju/errors"
    14  
    15  	"github.com/juju/juju/core/arch"
    16  )
    17  
    18  // Server extends the upstream LXD container server.
    19  type Server struct {
    20  	lxd.InstanceServer
    21  
    22  	name              string
    23  	clustered         bool
    24  	serverCertificate string
    25  	hostArch          string
    26  	supportedArches   []string
    27  	serverVersion     string
    28  
    29  	networkAPISupport bool
    30  	clusterAPISupport bool
    31  	storageAPISupport bool
    32  
    33  	localBridgeName string
    34  
    35  	clock clock.Clock
    36  }
    37  
    38  // NewLocalServer returns a Server based on a local socket connection.
    39  func NewLocalServer() (*Server, error) {
    40  	cSvr, err := connectLocal()
    41  	if err != nil {
    42  		return nil, errors.Trace(err)
    43  	}
    44  	svr, err := NewServer(cSvr)
    45  	return svr, errors.Trace(err)
    46  }
    47  
    48  // NewRemoteServer returns a Server based on a remote connection.
    49  func NewRemoteServer(spec ServerSpec) (*Server, error) {
    50  	if err := spec.Validate(); err != nil {
    51  		return nil, errors.Trace(err)
    52  	}
    53  
    54  	// Skip the get, because we know that we're going to request it
    55  	// when calling new server, preventing the double request.
    56  	spec.connectionArgs.SkipGetServer = true
    57  	cSvr, err := ConnectRemote(spec)
    58  	if err != nil {
    59  		return nil, errors.Trace(err)
    60  	}
    61  	svr, err := NewServer(cSvr)
    62  	return svr, err
    63  }
    64  
    65  // NewServer builds and returns a Server for high-level interaction with the
    66  // input LXD container server.
    67  func NewServer(svr lxd.InstanceServer) (*Server, error) {
    68  	info, _, err := svr.GetServer()
    69  	if err != nil {
    70  		return nil, errors.Trace(err)
    71  	}
    72  
    73  	apiExt := info.APIExtensions
    74  
    75  	name := info.Environment.ServerName
    76  	clustered := info.Environment.ServerClustered
    77  	if name == "" && !clustered {
    78  		// If the name is set to empty and clustering is false, then it's highly
    79  		// likely that we're on an older version of LXD. So in that case we
    80  		// need to set the name to something and internally LXD sets this type
    81  		// of node to "none".
    82  		// LP:#1786309
    83  		name = "none"
    84  	}
    85  	serverCertificate := info.Environment.Certificate
    86  	hostArch := arch.NormaliseArch(info.Environment.KernelArchitecture)
    87  	supportedArches := []string{}
    88  	for _, entry := range info.Environment.Architectures {
    89  		supportedArches = append(supportedArches, arch.NormaliseArch(entry))
    90  	}
    91  	if len(supportedArches) == 0 {
    92  		supportedArches = []string{hostArch}
    93  	}
    94  
    95  	return &Server{
    96  		InstanceServer:    svr,
    97  		name:              name,
    98  		clustered:         clustered,
    99  		serverCertificate: serverCertificate,
   100  		hostArch:          hostArch,
   101  		supportedArches:   supportedArches,
   102  		networkAPISupport: inSlice("network", apiExt),
   103  		clusterAPISupport: inSlice("clustering", apiExt),
   104  		storageAPISupport: inSlice("storage", apiExt),
   105  		serverVersion:     info.Environment.ServerVersion,
   106  		clock:             clock.WallClock,
   107  	}, nil
   108  }
   109  
   110  // Name returns the name of this LXD server.
   111  func (s *Server) Name() string {
   112  	return s.name
   113  }
   114  
   115  func (s *Server) ServerVersion() string {
   116  	return s.serverVersion
   117  }
   118  
   119  // UpdateServerConfig updates the server configuration with the input values.
   120  func (s *Server) UpdateServerConfig(cfg map[string]string) error {
   121  	svr, eTag, err := s.GetServer()
   122  	if err != nil {
   123  		return errors.Trace(err)
   124  	}
   125  	if svr.Config == nil {
   126  		svr.Config = make(map[string]interface{})
   127  	}
   128  	for k, v := range cfg {
   129  		svr.Config[k] = v
   130  	}
   131  	return errors.Trace(s.UpdateServer(svr.Writable(), eTag))
   132  }
   133  
   134  // UpdateContainerConfig updates the configuration for the container with the
   135  // input name, using the input values.
   136  func (s *Server) UpdateContainerConfig(name string, cfg map[string]string) error {
   137  	container, eTag, err := s.GetInstance(name)
   138  	if err != nil {
   139  		return errors.Trace(err)
   140  	}
   141  	if container.Config == nil {
   142  		container.Config = make(map[string]string)
   143  	}
   144  	for k, v := range cfg {
   145  		container.Config[k] = v
   146  	}
   147  
   148  	resp, err := s.UpdateInstance(name, container.Writable(), eTag)
   149  	if err != nil {
   150  		return errors.Trace(err)
   151  	}
   152  	return errors.Trace(resp.Wait())
   153  }
   154  
   155  // GetContainerProfiles returns the list of profiles that are associated with a
   156  // container.
   157  func (s *Server) GetContainerProfiles(name string) ([]string, error) {
   158  	container, _, err := s.GetInstance(name)
   159  	if err != nil {
   160  		return []string{}, errors.Trace(err)
   161  	}
   162  	return container.Profiles, nil
   163  }
   164  
   165  // UseProject ensures that this server will use the input project.
   166  // See: https://documentation.ubuntu.com/lxd/en/latest/projects.
   167  func (s *Server) UseProject(project string) {
   168  	s.InstanceServer = s.InstanceServer.UseProject(project)
   169  }
   170  
   171  // ReplaceOrAddContainerProfile updates the profiles for the container with the
   172  // input name, using the input values.
   173  // TODO: HML 2-apr-2019
   174  // remove when provisioner_task processProfileChanges() is
   175  // removed.
   176  func (s *Server) ReplaceOrAddContainerProfile(name, oldProfile, newProfile string) error {
   177  	container, eTag, err := s.GetInstance(name)
   178  	if err != nil {
   179  		return errors.Trace(errors.Annotatef(err, "failed to get container %q", name))
   180  	}
   181  	profiles := addRemoveReplaceProfileName(container.Profiles, oldProfile, newProfile)
   182  
   183  	container.Profiles = profiles
   184  	resp, err := s.UpdateInstance(name, container.Writable(), eTag)
   185  	if err != nil {
   186  		return errors.Trace(errors.Annotatef(err, "failed to updated container %q", name))
   187  	}
   188  
   189  	op := resp.Get()
   190  	logger.Debugf("updated container, waiting on %s", op.Description)
   191  	err = resp.Wait()
   192  	if err != nil {
   193  		logger.Tracef("updating container failed on %q", err)
   194  	}
   195  	return errors.Trace(err)
   196  }
   197  
   198  func addRemoveReplaceProfileName(profiles []string, oldProfile, newProfile string) []string {
   199  	if oldProfile == "" {
   200  		// add profile
   201  		profiles = append(profiles, newProfile)
   202  	} else {
   203  		for i, pName := range profiles {
   204  			if pName == oldProfile {
   205  				if newProfile == "" {
   206  					// remove profile
   207  					profiles = append(profiles[:i], profiles[i+1:]...)
   208  				} else {
   209  					// replace profile
   210  					profiles[i] = newProfile
   211  				}
   212  				break
   213  			}
   214  		}
   215  	}
   216  	return profiles
   217  }
   218  
   219  // UpdateContainerProfiles applies the given profiles (by name) to the
   220  // named container.  It is assumed the profiles have all been added to
   221  // the server before hand.
   222  func (s *Server) UpdateContainerProfiles(name string, profiles []string) error {
   223  	container, eTag, err := s.GetInstance(name)
   224  	if err != nil {
   225  		return errors.Trace(errors.Annotatef(err, "failed to get %q", name))
   226  	}
   227  
   228  	container.Profiles = profiles
   229  	resp, err := s.UpdateInstance(name, container.Writable(), eTag)
   230  	if err != nil {
   231  		return errors.Trace(errors.Annotatef(err, "failed to update %q with profiles", name))
   232  	}
   233  
   234  	op := resp.Get()
   235  	logger.Debugf("updated %q profiles, waiting on %s", name, op.Description)
   236  	err = resp.Wait()
   237  	return errors.Trace(errors.Annotatef(err, "update failed"))
   238  }
   239  
   240  // CreateClientCertificate adds the input certificate to the server,
   241  // indicating that is for use in client communication.
   242  func (s *Server) CreateClientCertificate(cert *Certificate) error {
   243  	req, err := cert.AsCreateRequest()
   244  	if err != nil {
   245  		return errors.Trace(err)
   246  	}
   247  	return errors.Trace(s.CreateCertificate(req))
   248  }
   249  
   250  // HasProfile interrogates the known profile names and returns a boolean
   251  // indicating whether a profile with the input name exists.
   252  func (s *Server) HasProfile(name string) (bool, error) {
   253  	profiles, err := s.GetProfileNames()
   254  	if err != nil {
   255  		return false, errors.Trace(err)
   256  	}
   257  	for _, profile := range profiles {
   258  		if profile == name {
   259  			return true, nil
   260  		}
   261  	}
   262  	return false, nil
   263  }
   264  
   265  // CreateProfileWithConfig creates a new profile with the input name and config.
   266  func (s *Server) CreateProfileWithConfig(name string, cfg map[string]string) error {
   267  	req := api.ProfilesPost{
   268  		Name: name,
   269  		ProfilePut: api.ProfilePut{
   270  			Config: cfg,
   271  		},
   272  	}
   273  	return errors.Trace(s.CreateProfile(req))
   274  }
   275  
   276  // ServerCertificate returns the current server environment certificate
   277  func (s *Server) ServerCertificate() string {
   278  	return s.serverCertificate
   279  }
   280  
   281  // HostArch returns the current host architecture
   282  func (s *Server) HostArch() string {
   283  	return s.hostArch
   284  }
   285  
   286  // SupportedArches returns all supported arches
   287  func (s *Server) SupportedArches() []string {
   288  	return s.supportedArches
   289  }
   290  
   291  // IsLXDNotFound checks if an error from the LXD API indicates that a requested
   292  // entity was not found.
   293  func IsLXDNotFound(err error) bool {
   294  	if err == nil {
   295  		return false
   296  	}
   297  
   298  	if _, match := api.StatusErrorMatch(err, http.StatusNotFound); match {
   299  		return true
   300  	}
   301  
   302  	return strings.Contains(strings.ToLower(err.Error()), "not found")
   303  }
   304  
   305  // IsLXDAlreadyExists checks if an error from the LXD API indicates that a
   306  // requested entity already exists.
   307  func IsLXDAlreadyExists(err error) bool {
   308  	if err == nil {
   309  		return false
   310  	}
   311  
   312  	if _, match := api.StatusErrorMatch(err, http.StatusConflict); match {
   313  		return true
   314  	}
   315  
   316  	return strings.Contains(strings.ToLower(err.Error()), "already exists")
   317  }
   318  
   319  func inSlice[T comparable](key T, list []T) bool {
   320  	for _, entry := range list {
   321  		if entry == key {
   322  			return true
   323  		}
   324  	}
   325  	return false
   326  }