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 }