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 }