func dbUpdatesApplyAll(d *Daemon) error { currentVersion := dbGetSchema(d.db) backup := false for _, update := range dbUpdates { if update.version <= currentVersion { continue } if !d.MockMode && !backup { shared.LogInfof("Updating the LXD database schema. Backup made as \"lxd.db.bak\"") err := shared.FileCopy(shared.VarPath("lxd.db"), shared.VarPath("lxd.db.bak")) if err != nil { return err } backup = true } err := update.apply(currentVersion, d) if err != nil { return err } currentVersion = update.version } return nil }
func (s *storageLvm) ImageCreate(fingerprint string) error { finalName := shared.VarPath("images", fingerprint) lvpath, err := s.createThinLV(fingerprint) if err != nil { s.log.Error("LVMCreateThinLV", log.Ctx{"err": err}) return fmt.Errorf("Error Creating LVM LV for new image: %v", err) } dst := shared.VarPath("images", fmt.Sprintf("%s.lv", fingerprint)) err = os.Symlink(lvpath, dst) if err != nil { return err } tempLVMountPoint, err := ioutil.TempDir(shared.VarPath("images"), "tmp_lv_mnt") if err != nil { return err } defer func() { if err := os.RemoveAll(tempLVMountPoint); err != nil { s.log.Error("Deleting temporary LVM mount point", log.Ctx{"err": err}) } }() fstype := daemonConfig["storage.lvm_fstype"].Get() mountOptions := daemonConfig["storage.lvm_mount_options"].Get() err = tryMount(lvpath, tempLVMountPoint, fstype, 0, mountOptions) if err != nil { shared.LogInfof("Error mounting image LV for unpacking: %v", err) return fmt.Errorf("Error mounting image LV: %v", err) } unpackErr := unpackImage(s.d, finalName, tempLVMountPoint) err = tryUnmount(tempLVMountPoint, 0) if err != nil { s.log.Warn("could not unmount LV. Will not remove", log.Ctx{"lvpath": lvpath, "mountpoint": tempLVMountPoint, "err": err}) if unpackErr == nil { return err } return fmt.Errorf( "Error unmounting '%s' during cleanup of error %v", tempLVMountPoint, unpackErr) } if unpackErr != nil { s.removeLV(fingerprint) return unpackErr } return nil }
func pruneExpiredImages(d *Daemon) { shared.LogInfof("Pruning expired images") // Get the list of expires images expiry := daemonConfig["images.remote_cache_expiry"].GetInt64() images, err := dbImagesGetExpired(d.db, expiry) if err != nil { shared.LogError("Unable to retrieve the list of expired images", log.Ctx{"err": err}) return } // Delete them for _, fp := range images { if err := doDeleteImage(d, fp); err != nil { shared.LogError("Error deleting image", log.Ctx{"err": err, "fp": fp}) } } shared.LogInfof("Done pruning expired images") }
// Stop stops the shared daemon. func (d *Daemon) Stop() error { forceStop := false d.tomb.Kill(errStop) shared.LogInfof("Stopping REST API handler:") for _, socket := range []*Socket{d.TCPSocket, d.UnixSocket} { if socket == nil { continue } if socket.CloseOnExit { shared.LogInfo(" - closing socket", log.Ctx{"socket": socket.Socket.Addr()}) socket.Socket.Close() } else { shared.LogInfo(" - skipping socket-activated socket", log.Ctx{"socket": socket.Socket.Addr()}) forceStop = true } } if n, err := d.numRunningContainers(); err != nil || n == 0 { shared.LogInfof("Unmounting shmounts") syscall.Unmount(shared.VarPath("shmounts"), syscall.MNT_DETACH) shared.LogInfof("Done unmounting shmounts") } else { shared.LogDebugf("Not unmounting shmounts (containers are still running)") } shared.LogInfof("Closing the database") d.db.Close() shared.LogInfof("Stopping /dev/lxd handler") d.devlxd.Close() shared.LogInfof("Stopped /dev/lxd handler") shared.LogInfof("Saving simplestreams cache") imageSaveStreamCache() shared.LogInfof("Saved simplestreams cache") if d.MockMode || forceStop { return nil } err := d.tomb.Wait() if err == errStop { return nil } return err }
func readSavedClientCAList(d *Daemon) { d.clientCerts = []x509.Certificate{} dbCerts, err := dbCertsGet(d.db) if err != nil { shared.LogInfof("Error reading certificates from database: %s", err) return } for _, dbCert := range dbCerts { certBlock, _ := pem.Decode([]byte(dbCert.Certificate)) if certBlock == nil { shared.LogInfof("Error decoding certificate for %s: %s", dbCert.Name, err) continue } cert, err := x509.ParseCertificate(certBlock.Bytes) if err != nil { shared.LogInfof("Error reading certificate for %s: %s", dbCert.Name, err) continue } d.clientCerts = append(d.clientCerts, *cert) } }
func cmdDaemon() error { if *argCPUProfile != "" { f, err := os.Create(*argCPUProfile) if err != nil { fmt.Printf("Error opening cpu profile file: %s\n", err) return nil } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } if *argMemProfile != "" { go memProfiler(*argMemProfile) } neededPrograms := []string{"dnsmasq", "setfacl", "rsync", "tar", "unsquashfs", "xz"} for _, p := range neededPrograms { _, err := exec.LookPath(p) if err != nil { return err } } if *argPrintGoroutinesEvery > 0 { go func() { for { time.Sleep(time.Duration(*argPrintGoroutinesEvery) * time.Second) shared.PrintStack() } }() } d := &Daemon{ group: *argGroup, SetupMode: shared.PathExists(shared.VarPath(".setup_mode"))} err := d.Init() if err != nil { if d != nil && d.db != nil { d.db.Close() } return err } var ret error var wg sync.WaitGroup wg.Add(1) go func() { ch := make(chan os.Signal) signal.Notify(ch, syscall.SIGPWR) sig := <-ch shared.LogInfof("Received '%s signal', shutting down containers.", sig) containersShutdown(d) ret = d.Stop() wg.Done() }() go func() { <-d.shutdownChan shared.LogInfof("Asked to shutdown by API, shutting down containers.") containersShutdown(d) ret = d.Stop() wg.Done() }() go func() { ch := make(chan os.Signal) signal.Notify(ch, syscall.SIGINT) signal.Notify(ch, syscall.SIGQUIT) signal.Notify(ch, syscall.SIGTERM) sig := <-ch shared.LogInfof("Received '%s signal', exiting.", sig) ret = d.Stop() wg.Done() }() wg.Wait() return ret }
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 }
func autoUpdateImages(d *Daemon) { shared.LogInfof("Updating images") images, err := dbImagesGet(d.db, false) if err != nil { shared.LogError("Unable to retrieve the list of images", log.Ctx{"err": err}) return } for _, fp := range images { id, info, err := dbImageGet(d.db, fp, false, true) if err != nil { shared.LogError("Error loading image", log.Ctx{"err": err, "fp": fp}) continue } if !info.AutoUpdate { continue } _, source, err := dbImageSourceGet(d.db, id) if err != nil { continue } shared.LogDebug("Processing image", log.Ctx{"fp": fp, "server": source.Server, "protocol": source.Protocol, "alias": source.Alias}) hash, err := d.ImageDownload(nil, source.Server, source.Protocol, "", "", source.Alias, false, true) if hash == fp { shared.LogDebug("Already up to date", log.Ctx{"fp": fp}) continue } else if err != nil { shared.LogError("Failed to update the image", log.Ctx{"err": err, "fp": fp}) continue } newId, _, err := dbImageGet(d.db, hash, false, true) if err != nil { shared.LogError("Error loading image", log.Ctx{"err": err, "fp": hash}) continue } err = dbImageLastAccessUpdate(d.db, hash, info.LastUsedDate) if err != nil { shared.LogError("Error setting last use date", log.Ctx{"err": err, "fp": hash}) continue } err = dbImageAliasesMove(d.db, id, newId) if err != nil { shared.LogError("Error moving aliases", log.Ctx{"err": err, "fp": hash}) continue } err = doDeleteImage(d, fp) if err != nil { shared.LogError("Error deleting image", log.Ctx{"err": err, "fp": fp}) } } shared.LogInfof("Done updating images") }