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 }