func (d *Daemon) Init() error { /* Initialize some variables */ d.imagesDownloading = map[string]chan bool{} d.readyChan = make(chan bool) d.shutdownChan = make(chan bool) /* Set the executable path */ /* Set the LVM environment */ err := os.Setenv("LVM_SUPPRESS_FD_WARNINGS", "1") if err != nil { return err } /* Setup logging if that wasn't done before */ if shared.Log == nil { shared.Log, err = logging.GetLogger("", "", true, true, nil) if err != nil { return err } } /* Print welcome message */ if d.MockMode { shared.LogInfo("LXD is starting in mock mode", log.Ctx{"path": shared.VarPath("")}) } else if d.SetupMode { shared.LogInfo("LXD is starting in setup mode", log.Ctx{"path": shared.VarPath("")}) } else { shared.LogInfo("LXD is starting in normal mode", log.Ctx{"path": shared.VarPath("")}) } /* Detect user namespaces */ runningInUserns = shared.RunningInUserNS() /* Detect AppArmor support */ if aaAvailable && os.Getenv("LXD_SECURITY_APPARMOR") == "false" { aaAvailable = false aaAdmin = false shared.LogWarnf("AppArmor support has been manually disabled") } if aaAvailable && !shared.IsDir("/sys/kernel/security/apparmor") { aaAvailable = false aaAdmin = false shared.LogWarnf("AppArmor support has been disabled because of lack of kernel support") } _, err = exec.LookPath("apparmor_parser") if aaAvailable && err != nil { aaAvailable = false aaAdmin = false shared.LogWarnf("AppArmor support has been disabled because 'apparmor_parser' couldn't be found") } /* Detect AppArmor admin support */ if aaAdmin && !haveMacAdmin() { aaAdmin = false shared.LogWarnf("Per-container AppArmor profiles are disabled because the mac_admin capability is missing.") } if aaAdmin && runningInUserns { aaAdmin = false shared.LogWarnf("Per-container AppArmor profiles are disabled because LXD is running in an unprivileged container.") } /* Detect AppArmor confinment */ if !aaConfined { profile := aaProfile() if profile != "unconfined" && profile != "" { aaConfined = true shared.LogWarnf("Per-container AppArmor profiles are disabled because LXD is already protected by AppArmor.") } } if aaAvailable { canStack := func() bool { contentBytes, err := ioutil.ReadFile("/sys/kernel/security/apparmor/features/domain/stack") if err != nil { return false } if string(contentBytes) != "yes\n" { return false } contentBytes, err = ioutil.ReadFile("/sys/kernel/security/apparmor/features/domain/version") if err != nil { return false } content := string(contentBytes) parts := strings.Split(strings.TrimSpace(content), ".") if len(parts) == 0 { shared.LogWarn("unknown apparmor domain version", log.Ctx{"version": content}) return false } major, err := strconv.Atoi(parts[0]) if err != nil { shared.LogWarn("unknown apparmor domain version", log.Ctx{"version": content}) return false } minor := 0 if len(parts) == 2 { minor, err = strconv.Atoi(parts[1]) if err != nil { shared.LogWarn("unknown apparmor domain version", log.Ctx{"version": content}) return false } } return major >= 1 && minor >= 2 } aaStacking = canStack() } /* Detect CGroup support */ cgBlkioController = shared.PathExists("/sys/fs/cgroup/blkio/") if !cgBlkioController { shared.LogWarnf("Couldn't find the CGroup blkio controller, I/O limits will be ignored.") } cgCpuController = shared.PathExists("/sys/fs/cgroup/cpu/") if !cgCpuController { shared.LogWarnf("Couldn't find the CGroup CPU controller, CPU time limits will be ignored.") } cgCpuacctController = shared.PathExists("/sys/fs/cgroup/cpuacct/") if !cgCpuacctController { shared.LogWarnf("Couldn't find the CGroup CPUacct controller, CPU accounting will not be available.") } cgCpusetController = shared.PathExists("/sys/fs/cgroup/cpuset/") if !cgCpusetController { shared.LogWarnf("Couldn't find the CGroup CPUset controller, CPU pinning will be ignored.") } cgDevicesController = shared.PathExists("/sys/fs/cgroup/devices/") if !cgDevicesController { shared.LogWarnf("Couldn't find the CGroup devices controller, device access control won't work.") } cgMemoryController = shared.PathExists("/sys/fs/cgroup/memory/") if !cgMemoryController { shared.LogWarnf("Couldn't find the CGroup memory controller, memory limits will be ignored.") } cgNetPrioController = shared.PathExists("/sys/fs/cgroup/net_prio/") if !cgNetPrioController { shared.LogWarnf("Couldn't find the CGroup network class controller, network limits will be ignored.") } cgPidsController = shared.PathExists("/sys/fs/cgroup/pids/") if !cgPidsController { shared.LogWarnf("Couldn't find the CGroup pids controller, process limits will be ignored.") } cgSwapAccounting = shared.PathExists("/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes") if !cgSwapAccounting { shared.LogWarnf("CGroup memory swap accounting is disabled, swap limits will be ignored.") } /* Get the list of supported architectures */ var architectures = []int{} architectureName, err := shared.ArchitectureGetLocal() if err != nil { return err } architecture, err := shared.ArchitectureId(architectureName) if err != nil { return err } architectures = append(architectures, architecture) personalities, err := shared.ArchitecturePersonalities(architecture) if err != nil { return err } for _, personality := range personalities { architectures = append(architectures, personality) } d.architectures = architectures /* Set container path */ d.lxcpath = shared.VarPath("containers") /* Make sure all our directories are available */ if err := os.MkdirAll(shared.CachePath(), 0700); err != nil { return err } if err := os.MkdirAll(shared.VarPath("containers"), 0711); err != nil { return err } if err := os.MkdirAll(shared.VarPath("devices"), 0711); err != nil { return err } if err := os.MkdirAll(shared.VarPath("devlxd"), 0755); err != nil { return err } if err := os.MkdirAll(shared.VarPath("images"), 0700); err != nil { return err } if err := os.MkdirAll(shared.LogPath(), 0700); err != nil { return err } if err := os.MkdirAll(shared.VarPath("security"), 0700); err != nil { return err } if err := os.MkdirAll(shared.VarPath("shmounts"), 0711); err != nil { return err } if err := os.MkdirAll(shared.VarPath("snapshots"), 0700); err != nil { return err } /* Detect the filesystem */ d.BackingFs, err = filesystemDetect(d.lxcpath) if err != nil { shared.LogError("Error detecting backing fs", log.Ctx{"err": err}) } /* Read the uid/gid allocation */ d.IdmapSet, err = shared.DefaultIdmapSet() if err != nil { shared.LogWarn("Error reading idmap", log.Ctx{"err": err.Error()}) shared.LogWarnf("Only privileged containers will be able to run") } else { shared.LogInfof("Default uid/gid map:") for _, lxcmap := range d.IdmapSet.ToLxcString() { shared.LogInfof(strings.TrimRight(" - "+lxcmap, "\n")) } } /* Initialize the database */ err = initializeDbObject(d, shared.VarPath("lxd.db")) if err != nil { return err } /* Load all config values from the database */ err = daemonConfigInit(d.db) if err != nil { return err } if !d.MockMode { /* Setup the storage driver */ err = d.SetupStorageDriver() if err != nil { return fmt.Errorf("Failed to setup storage: %s", err) } /* Apply all patches */ err = patchesApplyAll(d) if err != nil { return err } /* Setup the networks */ err = networkStartup(d) if err != nil { return err } /* Restore simplestreams cache */ err = imageLoadStreamCache(d) if err != nil { return err } } /* Log expiry */ go func() { t := time.NewTicker(24 * time.Hour) for { shared.LogInfof("Expiring log files") err := d.ExpireLogs() if err != nil { shared.LogError("Failed to expire logs", log.Ctx{"err": err}) } shared.LogInfof("Done expiring log files") <-t.C } }() /* set the initial proxy function based on config values in the DB */ d.proxy = shared.ProxyFromConfig( daemonConfig["core.proxy_https"].Get(), daemonConfig["core.proxy_http"].Get(), daemonConfig["core.proxy_ignore_hosts"].Get(), ) /* Setup /dev/lxd */ shared.LogInfof("Starting /dev/lxd handler") d.devlxd, err = createAndBindDevLxd() if err != nil { return err } if !d.MockMode { /* Start the scheduler */ go deviceEventListener(d) /* Setup the TLS authentication */ certf, keyf, err := readMyCert() if err != nil { return err } cert, err := tls.LoadX509KeyPair(certf, keyf) if err != nil { return err } tlsConfig := &tls.Config{ InsecureSkipVerify: true, ClientAuth: tls.RequestClientCert, Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12, CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, PreferServerCipherSuites: true, } if shared.PathExists(shared.VarPath("server.ca")) { ca, err := shared.ReadCert(shared.VarPath("server.ca")) if err != nil { return err } caPool := x509.NewCertPool() caPool.AddCert(ca) tlsConfig.RootCAs = caPool tlsConfig.ClientCAs = caPool shared.LogInfof("LXD is in CA mode, only CA-signed certificates will be allowed") } tlsConfig.BuildNameToCertificate() d.tlsConfig = tlsConfig readSavedClientCAList(d) } /* Setup the web server */ d.mux = mux.NewRouter() d.mux.StrictSlash(false) d.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") SyncResponse(true, []string{"/1.0"}).Render(w) }) for _, c := range api10 { d.createCmd("1.0", c) } for _, c := range apiInternal { d.createCmd("internal", c) } d.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { shared.LogInfo("Sending top level 404", log.Ctx{"url": r.URL}) w.Header().Set("Content-Type", "application/json") NotFound.Render(w) }) listeners := d.GetListeners() if len(listeners) > 0 { shared.LogInfof("LXD is socket activated") for _, listener := range listeners { if shared.PathExists(listener.Addr().String()) { d.UnixSocket = &Socket{Socket: listener, CloseOnExit: false} } else { tlsListener := tls.NewListener(listener, d.tlsConfig) d.TCPSocket = &Socket{Socket: tlsListener, CloseOnExit: false} } } } else { shared.LogInfof("LXD isn't socket activated") localSocketPath := shared.VarPath("unix.socket") // If the socket exists, let's try to connect to it and see if there's // a lxd running. if shared.PathExists(localSocketPath) { _, err := lxd.NewClient(&lxd.DefaultConfig, "local") if err != nil { shared.LogDebugf("Detected stale unix socket, deleting") // Connecting failed, so let's delete the socket and // listen on it ourselves. err = os.Remove(localSocketPath) if err != nil { return err } } else { return fmt.Errorf("LXD is already running.") } } unixAddr, err := net.ResolveUnixAddr("unix", localSocketPath) if err != nil { return fmt.Errorf("cannot resolve unix socket address: %v", err) } unixl, err := net.ListenUnix("unix", unixAddr) if err != nil { return fmt.Errorf("cannot listen on unix socket: %v", err) } if err := os.Chmod(localSocketPath, 0660); err != nil { return err } var gid int if d.group != "" { gid, err = shared.GroupId(d.group) if err != nil { return err } } else { gid = os.Getgid() } if err := os.Chown(localSocketPath, os.Getuid(), gid); err != nil { return err } d.UnixSocket = &Socket{Socket: unixl, CloseOnExit: true} } listenAddr := daemonConfig["core.https_address"].Get() if listenAddr != "" { _, _, err := net.SplitHostPort(listenAddr) if err != nil { listenAddr = fmt.Sprintf("%s:%s", listenAddr, shared.DefaultPort) } tcpl, err := tls.Listen("tcp", listenAddr, d.tlsConfig) if err != nil { shared.LogError("cannot listen on https socket, skipping...", log.Ctx{"err": err}) } else { if d.TCPSocket != nil { shared.LogInfof("Replacing inherited TCP socket with configured one") d.TCPSocket.Socket.Close() } d.TCPSocket = &Socket{Socket: tcpl, CloseOnExit: true} } } d.tomb.Go(func() error { shared.LogInfof("REST API daemon:") if d.UnixSocket != nil { shared.LogInfo(" - binding Unix socket", log.Ctx{"socket": d.UnixSocket.Socket.Addr()}) d.tomb.Go(func() error { return http.Serve(d.UnixSocket.Socket, &lxdHttpServer{d.mux, d}) }) } if d.TCPSocket != nil { shared.LogInfo(" - binding TCP socket", log.Ctx{"socket": d.TCPSocket.Socket.Addr()}) d.tomb.Go(func() error { return http.Serve(d.TCPSocket.Socket, &lxdHttpServer{d.mux, d}) }) } d.tomb.Go(func() error { server := devLxdServer(d) return server.Serve(d.devlxd) }) return nil }) if !d.MockMode && !d.SetupMode { err := d.Ready() if err != nil { return err } } return nil }
// ImageDownload checks if we have that Image Fingerprint else // downloads the image from a remote server. func (d *Daemon) ImageDownload(op *operation, server string, protocol string, certificate string, secret string, alias string, forContainer bool, autoUpdate bool) (string, error) { var err error var ss *shared.SimpleStreams var ctxMap log.Ctx if protocol == "" { protocol = "lxd" } fp := alias // Expand aliases if protocol == "simplestreams" { imageStreamCacheLock.Lock() entry, _ := imageStreamCache[server] if entry == nil || entry.expiry.Before(time.Now()) { refresh := func() (*imageStreamCacheEntry, error) { // Setup simplestreams client ss, err = shared.SimpleStreamsClient(server, d.proxy) if err != nil { return nil, err } // Get all aliases aliases, err := ss.ListAliases() if err != nil { return nil, err } // Get all fingerprints images, err := ss.ListImages() if err != nil { return nil, err } fingerprints := []string{} for _, image := range images { fingerprints = append(fingerprints, image.Fingerprint) } // Generate cache entry entry = &imageStreamCacheEntry{ss: ss, Aliases: aliases, Fingerprints: fingerprints, expiry: time.Now().Add(time.Hour)} imageStreamCache[server] = entry imageSaveStreamCache() return entry, nil } newEntry, err := refresh() if err == nil { // Cache refreshed entry = newEntry } else if entry != nil { // Failed to fetch entry but existing cache shared.LogWarn("Unable to refresh cache, using stale entry", log.Ctx{"server": server}) entry.expiry = time.Now().Add(time.Hour) } else { // Failed to fetch entry and nothing in cache imageStreamCacheLock.Unlock() return "", err } } else { shared.LogDebug("Using SimpleStreams cache entry", log.Ctx{"server": server, "expiry": entry.expiry}) ss = entry.ss } imageStreamCacheLock.Unlock() // Expand aliases for _, alias := range entry.Aliases { if alias.Name != fp { continue } fp = alias.Target break } // Expand fingerprint for _, fingerprint := range entry.Fingerprints { if !strings.HasPrefix(fingerprint, fp) { continue } if fp == alias { alias = fingerprint } fp = fingerprint break } } else if protocol == "lxd" { target, err := remoteGetImageFingerprint(d, server, certificate, fp) if err == nil && target != "" { fp = target } } if _, _, err := dbImageGet(d.db, fp, false, false); err == nil { shared.LogDebug("Image already exists in the db", log.Ctx{"image": fp}) // already have it return fp, nil } // Now check if we already downloading the image d.imagesDownloadingLock.RLock() if waitChannel, ok := d.imagesDownloading[fp]; ok { // We already download the image d.imagesDownloadingLock.RUnlock() shared.LogDebug( "Already downloading the image, waiting for it to succeed", log.Ctx{"image": fp}) // Wait until the download finishes (channel closes) if _, ok := <-waitChannel; ok { shared.LogWarnf("Value transmitted over image lock semaphore?") } if _, _, err := dbImageGet(d.db, fp, false, true); err != nil { shared.LogError( "Previous download didn't succeed", log.Ctx{"image": fp}) return "", fmt.Errorf("Previous download didn't succeed") } shared.LogDebug( "Previous download succeeded", log.Ctx{"image": fp}) return fp, nil } d.imagesDownloadingLock.RUnlock() if op == nil { ctxMap = log.Ctx{"alias": alias, "server": server} } else { ctxMap = log.Ctx{"trigger": op.url, "image": fp, "operation": op.id, "alias": alias, "server": server} } shared.LogInfo("Downloading image", ctxMap) // Add the download to the queue d.imagesDownloadingLock.Lock() d.imagesDownloading[fp] = make(chan bool) d.imagesDownloadingLock.Unlock() // Unlock once this func ends. defer func() { d.imagesDownloadingLock.Lock() if waitChannel, ok := d.imagesDownloading[fp]; ok { close(waitChannel) delete(d.imagesDownloading, fp) } d.imagesDownloadingLock.Unlock() }() exporturl := server var info shared.ImageInfo info.Fingerprint = fp destDir := shared.VarPath("images") destName := filepath.Join(destDir, fp) if shared.PathExists(destName) { d.Storage.ImageDelete(fp) } progress := func(progressInt int64, speedInt int64) { if op == nil { return } meta := op.metadata if meta == nil { meta = make(map[string]interface{}) } progress := fmt.Sprintf("%d%% (%s/s)", progressInt, shared.GetByteSizeString(speedInt)) if meta["download_progress"] != progress { meta["download_progress"] = progress op.UpdateMetadata(meta) } } if protocol == "lxd" { /* grab the metadata from /1.0/images/%s */ var url string if secret != "" { url = fmt.Sprintf( "%s/%s/images/%s?secret=%s", server, shared.APIVersion, fp, secret) } else { url = fmt.Sprintf("%s/%s/images/%s", server, shared.APIVersion, fp) } resp, err := d.httpGetSync(url, certificate) if err != nil { shared.LogError( "Failed to download image metadata", log.Ctx{"image": fp, "err": err}) return "", err } if err := json.Unmarshal(resp.Metadata, &info); err != nil { return "", err } /* now grab the actual file from /1.0/images/%s/export */ if secret != "" { exporturl = fmt.Sprintf( "%s/%s/images/%s/export?secret=%s", server, shared.APIVersion, fp, secret) } else { exporturl = fmt.Sprintf( "%s/%s/images/%s/export", server, shared.APIVersion, fp) } } else if protocol == "simplestreams" { err := ss.Download(fp, "meta", destName, nil) if err != nil { return "", err } err = ss.Download(fp, "root", destName+".rootfs", progress) if err != nil { return "", err } info, err := ss.GetImageInfo(fp) if err != nil { return "", err } info.Public = false info.AutoUpdate = autoUpdate _, err = imageBuildFromInfo(d, *info) if err != nil { return "", err } if alias != fp { id, _, err := dbImageGet(d.db, fp, false, true) if err != nil { return "", err } err = dbImageSourceInsert(d.db, id, server, protocol, "", alias) if err != nil { return "", err } } shared.LogInfo("Image downloaded", ctxMap) if forContainer { return fp, dbImageLastAccessInit(d.db, fp) } return fp, nil } raw, err := d.httpGetFile(exporturl, certificate) if err != nil { shared.LogError( "Failed to download image", log.Ctx{"image": fp, "err": err}) return "", err } info.Size = raw.ContentLength ctype, ctypeParams, err := mime.ParseMediaType(raw.Header.Get("Content-Type")) if err != nil { ctype = "application/octet-stream" } body := &shared.ProgressReader{ ReadCloser: raw.Body, Tracker: &shared.ProgressTracker{ Length: raw.ContentLength, Handler: progress, }, } if ctype == "multipart/form-data" { // Parse the POST data mr := multipart.NewReader(body, ctypeParams["boundary"]) // Get the metadata tarball part, err := mr.NextPart() if err != nil { shared.LogError( "Invalid multipart image", log.Ctx{"image": fp, "err": err}) return "", err } if part.FormName() != "metadata" { shared.LogError( "Invalid multipart image", log.Ctx{"image": fp, "err": err}) return "", fmt.Errorf("Invalid multipart image") } destName = filepath.Join(destDir, info.Fingerprint) f, err := os.Create(destName) if err != nil { shared.LogError( "Failed to save image", log.Ctx{"image": fp, "err": err}) return "", err } _, err = io.Copy(f, part) f.Close() if err != nil { shared.LogError( "Failed to save image", log.Ctx{"image": fp, "err": err}) return "", err } // Get the rootfs tarball part, err = mr.NextPart() if err != nil { shared.LogError( "Invalid multipart image", log.Ctx{"image": fp, "err": err}) return "", err } if part.FormName() != "rootfs" { shared.LogError( "Invalid multipart image", log.Ctx{"image": fp}) return "", fmt.Errorf("Invalid multipart image") } destName = filepath.Join(destDir, info.Fingerprint+".rootfs") f, err = os.Create(destName) if err != nil { shared.LogError( "Failed to save image", log.Ctx{"image": fp, "err": err}) return "", err } _, err = io.Copy(f, part) f.Close() if err != nil { shared.LogError( "Failed to save image", log.Ctx{"image": fp, "err": err}) return "", err } } else { destName = filepath.Join(destDir, info.Fingerprint) f, err := os.Create(destName) if err != nil { shared.LogError( "Failed to save image", log.Ctx{"image": fp, "err": err}) return "", err } _, err = io.Copy(f, body) f.Close() if err != nil { shared.LogError( "Failed to save image", log.Ctx{"image": fp, "err": err}) return "", err } } if protocol == "direct" { imageMeta, err := getImageMetadata(destName) if err != nil { return "", err } info.Architecture = imageMeta.Architecture info.CreationDate = time.Unix(imageMeta.CreationDate, 0) info.ExpiryDate = time.Unix(imageMeta.ExpiryDate, 0) info.Properties = imageMeta.Properties } // By default, make all downloaded images private info.Public = false if alias != fp && secret == "" { info.AutoUpdate = autoUpdate } _, err = imageBuildFromInfo(d, info) if err != nil { shared.LogError( "Failed to create image", log.Ctx{"image": fp, "err": err}) return "", err } if alias != fp { id, _, err := dbImageGet(d.db, fp, false, true) if err != nil { return "", err } err = dbImageSourceInsert(d.db, id, server, protocol, "", alias) if err != nil { return "", err } } shared.LogInfo("Image downloaded", ctxMap) if forContainer { return fp, dbImageLastAccessInit(d.db, fp) } return fp, nil }
func (d *Daemon) createCmd(version string, c Command) { var uri string if c.name == "" { uri = fmt.Sprintf("/%s", version) } else { uri = fmt.Sprintf("/%s/%s", version, c.name) } d.mux.HandleFunc(uri, func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if d.isTrustedClient(r) { shared.LogDebug( "handling", log.Ctx{"method": r.Method, "url": r.URL.RequestURI(), "ip": r.RemoteAddr}) } else if r.Method == "GET" && c.untrustedGet { shared.LogDebug( "allowing untrusted GET", log.Ctx{"url": r.URL.RequestURI(), "ip": r.RemoteAddr}) } else if r.Method == "POST" && c.untrustedPost { shared.LogDebug( "allowing untrusted POST", log.Ctx{"url": r.URL.RequestURI(), "ip": r.RemoteAddr}) } else { shared.LogWarn( "rejecting request from untrusted client", log.Ctx{"ip": r.RemoteAddr}) Forbidden.Render(w) return } if debug && r.Method != "GET" && isJSONRequest(r) { newBody := &bytes.Buffer{} captured := &bytes.Buffer{} multiW := io.MultiWriter(newBody, captured) if _, err := io.Copy(multiW, r.Body); err != nil { InternalError(err).Render(w) return } r.Body = shared.BytesReadCloser{Buf: newBody} shared.DebugJson(captured) } var resp Response resp = NotImplemented switch r.Method { case "GET": if c.get != nil { resp = c.get(d, r) } case "PUT": if c.put != nil { resp = c.put(d, r) } case "POST": if c.post != nil { resp = c.post(d, r) } case "DELETE": if c.delete != nil { resp = c.delete(d, r) } case "PATCH": if c.patch != nil { resp = c.patch(d, r) } default: resp = NotFound } if err := resp.Render(w); err != nil { err := InternalError(err).Render(w) if err != nil { shared.LogErrorf("Failed writing error for error, giving up") } } /* * When we create a new lxc.Container, it adds a finalizer (via * SetFinalizer) that frees the struct. However, it sometimes * takes the go GC a while to actually free the struct, * presumably since it is a small amount of memory. * Unfortunately, the struct also keeps the log fd open, so if * we leave too many of these around, we end up running out of * fds. So, let's explicitly do a GC to collect these at the * end of each request. */ runtime.GC() }) }
func deviceTaskBalance(d *Daemon) { min := func(x, y int) int { if x < y { return x } return y } // Don't bother running when CGroup support isn't there if !cgCpusetController { return } // Get effective cpus list - those are all guaranteed to be online effectiveCpus, err := cGroupGet("cpuset", "/", "cpuset.effective_cpus") if err != nil { // Older kernel - use cpuset.cpus effectiveCpus, err = cGroupGet("cpuset", "/", "cpuset.cpus") if err != nil { shared.LogErrorf("Error reading host's cpuset.cpus") return } } err = cGroupSet("cpuset", "/lxc", "cpuset.cpus", effectiveCpus) if err != nil && shared.PathExists("/sys/fs/cgroup/cpuset/lxc") { shared.LogWarn("Error setting lxd's cpuset.cpus", log.Ctx{"err": err}) } cpus, err := parseCpuset(effectiveCpus) if err != nil { shared.LogError("Error parsing host's cpu set", log.Ctx{"cpuset": effectiveCpus, "err": err}) return } // Iterate through the containers containers, err := dbContainersList(d.db, cTypeRegular) if err != nil { shared.LogError("problem loading containers list", log.Ctx{"err": err}) return } fixedContainers := map[int][]container{} balancedContainers := map[container]int{} for _, name := range containers { c, err := containerLoadByName(d, name) if err != nil { continue } conf := c.ExpandedConfig() cpulimit, ok := conf["limits.cpu"] if !ok || cpulimit == "" { cpulimit = effectiveCpus } if !c.IsRunning() { continue } count, err := strconv.Atoi(cpulimit) if err == nil { // Load-balance count = min(count, len(cpus)) balancedContainers[c] = count } else { // Pinned containerCpus, err := parseCpuset(cpulimit) if err != nil { return } for _, nr := range containerCpus { if !shared.IntInSlice(nr, cpus) { continue } _, ok := fixedContainers[nr] if ok { fixedContainers[nr] = append(fixedContainers[nr], c) } else { fixedContainers[nr] = []container{c} } } } } // Balance things pinning := map[container][]string{} usage := map[int]deviceTaskCPU{} for _, id := range cpus { cpu := deviceTaskCPU{} cpu.id = id cpu.strId = fmt.Sprintf("%d", id) count := 0 cpu.count = &count usage[id] = cpu } for cpu, ctns := range fixedContainers { c, ok := usage[cpu] if !ok { shared.LogErrorf("Internal error: container using unavailable cpu") continue } id := c.strId for _, ctn := range ctns { _, ok := pinning[ctn] if ok { pinning[ctn] = append(pinning[ctn], id) } else { pinning[ctn] = []string{id} } *c.count += 1 } } sortedUsage := make(deviceTaskCPUs, 0) for _, value := range usage { sortedUsage = append(sortedUsage, value) } for ctn, count := range balancedContainers { sort.Sort(sortedUsage) for _, cpu := range sortedUsage { if count == 0 { break } count -= 1 id := cpu.strId _, ok := pinning[ctn] if ok { pinning[ctn] = append(pinning[ctn], id) } else { pinning[ctn] = []string{id} } *cpu.count += 1 } } // Set the new pinning for ctn, set := range pinning { // Confirm the container didn't just stop if !ctn.IsRunning() { continue } sort.Strings(set) err := ctn.CGroupSet("cpuset.cpus", strings.Join(set, ",")) if err != nil { shared.LogError("balance: Unable to set cpuset", log.Ctx{"name": ctn.Name(), "err": err, "value": strings.Join(set, ",")}) } } }