func createFromNone(d *Daemon, req *containerPostReq) Response { architecture, err := shared.ArchitectureId(req.Architecture) if err != nil { architecture = 0 } args := containerArgs{ Architecture: architecture, Config: req.Config, Ctype: cTypeRegular, Devices: req.Devices, Ephemeral: req.Ephemeral, Name: req.Name, Profiles: req.Profiles, } run := func(op *operation) error { _, err := containerCreateAsEmpty(d, args) return err } resources := map[string][]string{} resources["containers"] = []string{req.Name} op, err := operationCreate(operationClassTask, resources, nil, run, nil, nil) if err != nil { return InternalError(err) } return OperationResponse(op) }
func dbImageUpdate(db *sql.DB, id int, fname string, sz int64, public bool, autoUpdate bool, architecture string, creationDate time.Time, expiryDate time.Time, properties map[string]string) error { arch, err := shared.ArchitectureId(architecture) if err != nil { arch = 0 } tx, err := dbBegin(db) if err != nil { return err } publicInt := 0 if public { publicInt = 1 } autoUpdateInt := 0 if autoUpdate { autoUpdateInt = 1 } stmt, err := tx.Prepare(`UPDATE images SET filename=?, size=?, public=?, auto_update=?, architecture=?, creation_date=?, expiry_date=? WHERE id=?`) if err != nil { tx.Rollback() return err } defer stmt.Close() _, err = stmt.Exec(fname, sz, publicInt, autoUpdateInt, arch, creationDate, expiryDate, id) if err != nil { tx.Rollback() return err } _, err = tx.Exec(`DELETE FROM images_properties WHERE image_id=?`, id) stmt, err = tx.Prepare(`INSERT INTO images_properties (image_id, type, key, value) VALUES (?, ?, ?, ?)`) if err != nil { tx.Rollback() return err } for key, value := range properties { _, err = stmt.Exec(id, 0, key, value) if err != nil { tx.Rollback() return err } } if err := txCommit(tx); err != nil { return err } return nil }
/* * Update configuration, or, if 'restore:snapshot-name' is present, restore * the named snapshot */ func containerPut(d *Daemon, r *http.Request) Response { name := mux.Vars(r)["name"] c, err := containerLoadByName(d, name) if err != nil { return NotFound } configRaw := containerPutReq{} if err := json.NewDecoder(r.Body).Decode(&configRaw); err != nil { return BadRequest(err) } architecture, err := shared.ArchitectureId(configRaw.Architecture) if err != nil { architecture = 0 } var do = func(*operation) error { return nil } if configRaw.Restore == "" { // Update container configuration do = func(op *operation) error { args := containerArgs{ Architecture: architecture, Config: configRaw.Config, Devices: configRaw.Devices, Ephemeral: configRaw.Ephemeral, Profiles: configRaw.Profiles} // FIXME: should set to true when not migrating err = c.Update(args, false) if err != nil { return err } return nil } } else { // Snapshot Restore do = func(op *operation) error { return containerSnapRestore(d, name, configRaw.Restore) } } resources := map[string][]string{} resources["containers"] = []string{name} op, err := operationCreate(operationClassTask, resources, nil, do, nil, nil) if err != nil { return InternalError(err) } return OperationResponse(op) }
func getImageMetadata(fname string) (*imageMetadata, error) { metadataName := "metadata.yaml" compressionArgs, _, err := detectCompression(fname) if err != nil { return nil, fmt.Errorf( "detectCompression failed, err='%v', tarfile='%s'", err, fname) } args := []string{"-O"} args = append(args, compressionArgs...) args = append(args, fname, metadataName) // read the metadata.yaml output, err := exec.Command("tar", args...).CombinedOutput() if err != nil { outputLines := strings.Split(string(output), "\n") return nil, fmt.Errorf("Could not extract image %s from tar: %v (%s)", metadataName, err, outputLines[0]) } metadata := imageMetadata{} err = yaml.Unmarshal(output, &metadata) if err != nil { return nil, fmt.Errorf("Could not parse %s: %v", metadataName, err) } _, err = shared.ArchitectureId(metadata.Architecture) if err != nil { return nil, err } if metadata.CreationDate == 0 { return nil, fmt.Errorf("Missing creation date.") } return &metadata, nil }
func getImgPostInfo(d *Daemon, r *http.Request, builddir string, post *os.File) (info shared.ImageInfo, err error) { var imageMeta *imageMetadata logger := logging.AddContext(shared.Log, log.Ctx{"function": "getImgPostInfo"}) public, _ := strconv.Atoi(r.Header.Get("X-LXD-public")) info.Public = public == 1 propHeaders := r.Header[http.CanonicalHeaderKey("X-LXD-properties")] ctype, ctypeParams, err := mime.ParseMediaType(r.Header.Get("Content-Type")) if err != nil { ctype = "application/octet-stream" } sha256 := sha256.New() var size int64 // Create a temporary file for the image tarball imageTarf, err := ioutil.TempFile(builddir, "lxd_tar_") if err != nil { return info, err } if ctype == "multipart/form-data" { // Parse the POST data post.Seek(0, 0) mr := multipart.NewReader(post, ctypeParams["boundary"]) // Get the metadata tarball part, err := mr.NextPart() if err != nil { return info, err } if part.FormName() != "metadata" { return info, fmt.Errorf("Invalid multipart image") } size, err = io.Copy(io.MultiWriter(imageTarf, sha256), part) info.Size += size imageTarf.Close() if err != nil { logger.Error( "Failed to copy the image tarfile", log.Ctx{"err": err}) return info, err } // Get the rootfs tarball part, err = mr.NextPart() if err != nil { logger.Error( "Failed to get the next part", log.Ctx{"err": err}) return info, err } if part.FormName() != "rootfs" { logger.Error( "Invalid multipart image") return info, fmt.Errorf("Invalid multipart image") } // Create a temporary file for the rootfs tarball rootfsTarf, err := ioutil.TempFile(builddir, "lxd_tar_") if err != nil { return info, err } size, err = io.Copy(io.MultiWriter(rootfsTarf, sha256), part) info.Size += size rootfsTarf.Close() if err != nil { logger.Error( "Failed to copy the rootfs tarfile", log.Ctx{"err": err}) return info, err } info.Filename = part.FileName() info.Fingerprint = fmt.Sprintf("%x", sha256.Sum(nil)) expectedFingerprint := r.Header.Get("X-LXD-fingerprint") if expectedFingerprint != "" && info.Fingerprint != expectedFingerprint { err = fmt.Errorf("fingerprints don't match, got %s expected %s", info.Fingerprint, expectedFingerprint) return info, err } imgfname := shared.VarPath("images", info.Fingerprint) err = shared.FileMove(imageTarf.Name(), imgfname) if err != nil { logger.Error( "Failed to move the image tarfile", log.Ctx{ "err": err, "source": imageTarf.Name(), "dest": imgfname}) return info, err } rootfsfname := shared.VarPath("images", info.Fingerprint+".rootfs") err = shared.FileMove(rootfsTarf.Name(), rootfsfname) if err != nil { logger.Error( "Failed to move the rootfs tarfile", log.Ctx{ "err": err, "source": rootfsTarf.Name(), "dest": imgfname}) return info, err } imageMeta, err = getImageMetadata(imgfname) if err != nil { logger.Error( "Failed to get image metadata", log.Ctx{"err": err}) return info, err } } else { post.Seek(0, 0) size, err = io.Copy(io.MultiWriter(imageTarf, sha256), post) info.Size = size imageTarf.Close() logger.Debug("Tar size", log.Ctx{"size": size}) if err != nil { logger.Error( "Failed to copy the tarfile", log.Ctx{"err": err}) return info, err } info.Filename = r.Header.Get("X-LXD-filename") info.Fingerprint = fmt.Sprintf("%x", sha256.Sum(nil)) expectedFingerprint := r.Header.Get("X-LXD-fingerprint") if expectedFingerprint != "" && info.Fingerprint != expectedFingerprint { logger.Error( "Fingerprints don't match", log.Ctx{ "got": info.Fingerprint, "expected": expectedFingerprint}) err = fmt.Errorf( "fingerprints don't match, got %s expected %s", info.Fingerprint, expectedFingerprint) return info, err } imgfname := shared.VarPath("images", info.Fingerprint) err = shared.FileMove(imageTarf.Name(), imgfname) if err != nil { logger.Error( "Failed to move the tarfile", log.Ctx{ "err": err, "source": imageTarf.Name(), "dest": imgfname}) return info, err } imageMeta, err = getImageMetadata(imgfname) if err != nil { logger.Error( "Failed to get image metadata", log.Ctx{"err": err}) return info, err } } info.Architecture, _ = shared.ArchitectureId(imageMeta.Architecture) info.CreationDate = imageMeta.CreationDate info.ExpiryDate = imageMeta.ExpiryDate info.Properties = imageMeta.Properties if len(propHeaders) > 0 { for _, ph := range propHeaders { p, _ := url.ParseQuery(ph) for pkey, pval := range p { info.Properties[pkey] = pval[0] } } } return info, nil }
func getImgPostInfo(d *Daemon, r *http.Request, builddir string) (public int, fingerprint string, arch int, filename string, size int64, properties map[string]string, err error) { // Is this a container request? decoder := json.NewDecoder(r.Body) req := imageFromContainerPostReq{} if err = decoder.Decode(&req); err == nil { return imgPostContInfo(d, r, req, builddir) } // ok we've got an image in the body public, _ = strconv.Atoi(r.Header.Get("X-LXD-public")) filename = r.Header.Get("X-LXD-filename") propHeaders := r.Header[http.CanonicalHeaderKey("X-LXD-properties")] properties = map[string]string{} if len(propHeaders) > 0 { for _, ph := range propHeaders { p, _ := url.ParseQuery(ph) for pkey, pval := range p { properties[pkey] = pval[0] } } } // Create a file for the tarball tarf, err := ioutil.TempFile(builddir, "lxd_tar_") if err != nil { return 0, "", 0, "", 0, properties, err } tarfname := tarf.Name() sha256 := sha256.New() var size1, size2 int64 size1, err = io.Copy(io.MultiWriter(tarf, sha256), decoder.Buffered()) if err == nil { size2, err = io.Copy(io.MultiWriter(tarf, sha256), r.Body) } size = size1 + size2 tarf.Close() if err != nil { return 0, "", 0, "", 0, properties, err } fingerprint = fmt.Sprintf("%x", sha256.Sum(nil)) expectedFingerprint := r.Header.Get("X-LXD-fingerprint") if expectedFingerprint != "" && fingerprint != expectedFingerprint { err = fmt.Errorf("fingerprints don't match, got %s expected %s", fingerprint, expectedFingerprint) return 0, "", 0, "", 0, properties, err } imagefname := filepath.Join(builddir, fingerprint) err = os.Rename(tarfname, imagefname) if err != nil { return 0, "", 0, "", 0, properties, err } var imageMeta *imageMetadata imageMeta, err = getImageMetadata(imagefname) if err != nil { return 0, "", 0, "", 0, properties, err } arch, _ = shared.ArchitectureId(imageMeta.Architecture) err = nil return }
func containerPatch(d *Daemon, r *http.Request) Response { // Get the container name := mux.Vars(r)["name"] c, err := containerLoadByName(d, name) if err != nil { return NotFound } // Validate the ETag etag := []interface{}{c.Architecture(), c.LocalConfig(), c.LocalDevices(), c.IsEphemeral(), c.Profiles()} err = etagCheck(r, etag) if err != nil { return PreconditionFailed(err) } body, err := ioutil.ReadAll(r.Body) if err != nil { return InternalError(err) } rdr1 := ioutil.NopCloser(bytes.NewBuffer(body)) rdr2 := ioutil.NopCloser(bytes.NewBuffer(body)) reqRaw := shared.Jmap{} if err := json.NewDecoder(rdr1).Decode(&reqRaw); err != nil { return BadRequest(err) } req := containerPutReq{} if err := json.NewDecoder(rdr2).Decode(&req); err != nil { return BadRequest(err) } if req.Restore != "" { return BadRequest(fmt.Errorf("Can't call PATCH in restore mode.")) } // Check if architecture was passed var architecture int _, err = reqRaw.GetString("architecture") if err != nil { architecture = c.Architecture() } else { architecture, err = shared.ArchitectureId(req.Architecture) if err != nil { architecture = 0 } } // Check if ephemeral was passed _, err = reqRaw.GetBool("ephemeral") if err != nil { req.Ephemeral = c.IsEphemeral() } // Check if profiles was passed if req.Profiles == nil { req.Profiles = c.Profiles() } // Check if config was passed if req.Config == nil { req.Config = c.LocalConfig() } else { for k, v := range c.LocalConfig() { _, ok := req.Config[k] if !ok { req.Config[k] = v } } } // Check if devices was passed if req.Devices == nil { req.Devices = c.LocalDevices() } else { for k, v := range c.LocalDevices() { _, ok := req.Devices[k] if !ok { req.Devices[k] = v } } } // Update container configuration args := containerArgs{ Architecture: architecture, Config: req.Config, Devices: req.Devices, Ephemeral: req.Ephemeral, Profiles: req.Profiles} err = c.Update(args, false) if err != nil { return SmartError(err) } return EmptySyncResponse }
// StartDaemon starts the shared daemon with the provided configuration. func StartDaemon() (*Daemon, error) { d := &Daemon{} /* Setup logging */ if shared.Log == nil { shared.SetLogger("", "", true, true) } shared.Log.Info("LXD is starting.") /* Get the list of supported architectures */ var architectures = []int{} uname := syscall.Utsname{} if err := syscall.Uname(&uname); err != nil { return nil, err } architectureName := "" for _, c := range uname.Machine { if c == 0 { break } architectureName += string(byte(c)) } architecture, err := shared.ArchitectureId(architectureName) if err != nil { return nil, err } architectures = append(architectures, architecture) personalities, err := shared.ArchitecturePersonalities(architecture) if err != nil { return nil, err } for _, personality := range personalities { architectures = append(architectures, personality) } d.architectures = architectures /* Create required paths */ d.lxcpath = shared.VarPath("containers") err = os.MkdirAll(d.lxcpath, 0755) if err != nil { return nil, err } // Create default directories if err := os.MkdirAll(shared.VarPath("images"), 0700); err != nil { return nil, err } if err := os.MkdirAll(shared.VarPath("snapshots"), 0700); err != nil { return nil, err } if err := os.MkdirAll(shared.VarPath("devlxd"), 0755); err != nil { return nil, err } /* Detect the filesystem */ d.BackingFs, err = filesystemDetect(d.lxcpath) if err != nil { shared.Log.Error("Error detecting backing fs", log.Ctx{"err": err}) } /* Read the uid/gid allocation */ d.IdmapSet, err = shared.DefaultIdmapSet() if err != nil { shared.Log.Warn("error reading idmap", log.Ctx{"err": err.Error()}) shared.Log.Warn("operations requiring idmap will not be available") } else { shared.Log.Info("Default uid/gid map:") for _, lxcmap := range d.IdmapSet.ToLxcString() { shared.Log.Info(strings.TrimRight(" - "+lxcmap, "\n")) } } /* Initialize the database */ err = initDb(d) if err != nil { return nil, err } /* Setup the TLS authentication */ certf, keyf, err := readMyCert() if err != nil { return nil, err } d.certf = certf d.keyf = keyf readSavedClientCAList(d) tlsConfig, err := shared.GetTLSConfig(d.certf, d.keyf) if err != nil { return nil, err } /* Setup /dev/lxd */ d.devlxd, err = createAndBindDevLxd() if err != nil { return nil, err } if err := setupSharedMounts(); err != nil { return nil, err } /* Restart containers */ containersRestart(d) containersWatch(d) err = d.SetupStorageDriver() if err != nil { return nil, fmt.Errorf("Failed to setup storage: %s", err) } /* Setup the web server */ d.mux = mux.NewRouter() 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) } d.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { shared.Log.Debug("sending top level 404", log.Ctx{"url": r.URL}) w.Header().Set("Content-Type", "application/json") NotFound.Render(w) }) listeners, err := activation.Listeners(false) if err != nil { return nil, err } var sockets []net.Listener if len(listeners) > 0 { shared.Log.Info("LXD is socket activated.") for _, listener := range listeners { if shared.PathExists(listener.Addr().String()) { sockets = append(sockets, listener) } else { tlsListener := tls.NewListener(listener, tlsConfig) sockets = append(sockets, tlsListener) } } } else { shared.Log.Info("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) { c := &lxd.Config{Remotes: map[string]lxd.RemoteConfig{}} _, err := lxd.NewClient(c, "") if err != nil { shared.Log.Debug("Detected old but dead unix socket, deleting it...") // Connecting failed, so let's delete the socket and // listen on it ourselves. err = os.Remove(localSocketPath) if err != nil { return nil, err } } } unixAddr, err := net.ResolveUnixAddr("unix", localSocketPath) if err != nil { return nil, fmt.Errorf("cannot resolve unix socket address: %v", err) } unixl, err := net.ListenUnix("unix", unixAddr) if err != nil { return nil, fmt.Errorf("cannot listen on unix socket: %v", err) } if err := os.Chmod(localSocketPath, 0660); err != nil { return nil, err } gid, err := shared.GroupId(*group) if err != nil { return nil, err } if err := os.Chown(localSocketPath, os.Getuid(), gid); err != nil { return nil, err } sockets = append(sockets, unixl) } listenAddr, err := d.ConfigValueGet("core.https_address") if err != nil { return nil, err } if listenAddr != "" { _, _, err := net.SplitHostPort(listenAddr) if err != nil { listenAddr = fmt.Sprintf("%s:%s", listenAddr, shared.DefaultPort) } tcpl, err := tls.Listen("tcp", listenAddr, tlsConfig) if err != nil { return nil, fmt.Errorf("cannot listen on https socket: %v", err) } sockets = append(sockets, tcpl) } d.Sockets = sockets d.pruneChan = make(chan bool) go func() { for { expiryStr, err := dbGetImageExpiry(d) var expiry int if err != nil { expiry = 10 } else { expiry, err = strconv.Atoi(expiryStr) if err != nil { expiry = 10 } if expiry <= 0 { expiry = 1 } } timer := time.NewTimer(time.Duration(expiry) * 24 * time.Hour) timeChan := timer.C select { case <-timeChan: d.pruneExpiredImages() case <-d.pruneChan: d.pruneExpiredImages() timer.Stop() } } }() d.tomb.Go(func() error { for _, socket := range d.Sockets { shared.Log.Info(" - binding socket", log.Ctx{"socket": socket.Addr()}) current_socket := socket d.tomb.Go(func() error { return http.Serve(current_socket, d.mux) }) } d.tomb.Go(func() error { server := devLxdServer(d) return server.Serve(d.devlxd) }) return nil }) return d, nil }
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 */ absPath, err := os.Readlink("/proc/self/exe") if err != nil { return err } d.execPath = absPath /* 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.Log.Info("LXD is starting in mock mode", log.Ctx{"path": shared.VarPath("")}) } else if d.SetupMode { shared.Log.Info("LXD is starting in setup mode", log.Ctx{"path": shared.VarPath("")}) } else { shared.Log.Info("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.Log.Warn("AppArmor support has been manually disabled") } if aaAvailable && !shared.IsDir("/sys/kernel/security/apparmor") { aaAvailable = false aaAdmin = false shared.Log.Warn("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.Log.Warn("AppArmor support has been disabled because 'apparmor_parser' couldn't be found") } /* Detect AppArmor admin support */ if aaAdmin && !haveMacAdmin() { aaAdmin = false shared.Log.Warn("Per-container AppArmor profiles are disabled because the mac_admin capability is missing.") } if aaAdmin && runningInUserns { aaAdmin = false shared.Log.Warn("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.Log.Warn("Per-container AppArmor profiles are disabled because LXD is already protected by AppArmor.") } } /* Detect CGroup support */ cgBlkioController = shared.PathExists("/sys/fs/cgroup/blkio/") if !cgBlkioController { shared.Log.Warn("Couldn't find the CGroup blkio controller, I/O limits will be ignored.") } cgCpuController = shared.PathExists("/sys/fs/cgroup/cpu/") if !cgCpuController { shared.Log.Warn("Couldn't find the CGroup CPU controller, CPU time limits will be ignored.") } cgCpusetController = shared.PathExists("/sys/fs/cgroup/cpuset/") if !cgCpusetController { shared.Log.Warn("Couldn't find the CGroup CPUset controller, CPU pinning will be ignored.") } cgDevicesController = shared.PathExists("/sys/fs/cgroup/devices/") if !cgDevicesController { shared.Log.Warn("Couldn't find the CGroup devices controller, device access control won't work.") } cgMemoryController = shared.PathExists("/sys/fs/cgroup/memory/") if !cgMemoryController { shared.Log.Warn("Couldn't find the CGroup memory controller, memory limits will be ignored.") } cgNetPrioController = shared.PathExists("/sys/fs/cgroup/net_prio/") if !cgNetPrioController { shared.Log.Warn("Couldn't find the CGroup network class controller, network limits will be ignored.") } cgPidsController = shared.PathExists("/sys/fs/cgroup/pids/") if !cgPidsController { shared.Log.Warn("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.Log.Warn("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.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.Log.Error("Error detecting backing fs", log.Ctx{"err": err}) } /* Read the uid/gid allocation */ d.IdmapSet, err = shared.DefaultIdmapSet() if err != nil { shared.Log.Warn("Error reading idmap", log.Ctx{"err": err.Error()}) shared.Log.Warn("Only privileged containers will be able to run") } else { shared.Log.Info("Default uid/gid map:") for _, lxcmap := range d.IdmapSet.ToLxcString() { shared.Log.Info(strings.TrimRight(" - "+lxcmap, "\n")) } } /* Initialize the database */ err = initializeDbObject(d, shared.VarPath("lxd.db")) if err != nil { return err } /* Setup the storage driver */ if !d.MockMode { err = d.SetupStorageDriver() if err != nil { return fmt.Errorf("Failed to setup storage: %s", err) } } /* Load all config values from the database */ _, err = d.ConfigValuesGet() if err != nil { return err } /* set the initial proxy function based on config values in the DB */ d.updateProxy() /* Setup /dev/lxd */ d.devlxd, err = createAndBindDevLxd() if err != nil { return err } if err := setupSharedMounts(); 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_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, PreferServerCipherSuites: true, } 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.Log.Debug("Sending top level 404", log.Ctx{"url": r.URL}) w.Header().Set("Content-Type", "application/json") NotFound.Render(w) }) listeners, err := activation.Listeners(false) if err != nil { return err } if len(listeners) > 0 { shared.Log.Info("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.Log.Info("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.Log.Debug("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, err := d.ConfigValueGet("core.https_address") if err != nil { return err } 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.Log.Error("cannot listen on https socket, skipping...", log.Ctx{"err": err}) } else { if d.TCPSocket != nil { shared.Log.Info("Replacing systemd TCP socket by configure one") d.TCPSocket.Socket.Close() } d.TCPSocket = &Socket{Socket: tcpl, CloseOnExit: true} } } d.tomb.Go(func() error { shared.Log.Info("REST API daemon:") if d.UnixSocket != nil { shared.Log.Info(" - 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.Log.Info(" - 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 getImgPostInfo(d *Daemon, r *http.Request, builddir string) (info shared.ImageInfo, err error) { var imageMeta *imageMetadata // Store the post data to disk post, err := ioutil.TempFile(builddir, "lxd_post_") if err != nil { return info, err } defer os.Remove(post.Name()) _, err = io.Copy(post, r.Body) if err != nil { return info, err } // Is this a container request? post.Seek(0, 0) decoder := json.NewDecoder(post) req := imageFromContainerPostReq{} if err = decoder.Decode(&req); err == nil { return imgPostContInfo(d, r, req, builddir) } // ok we've got an image in the body info.Public, _ = strconv.Atoi(r.Header.Get("X-LXD-public")) propHeaders := r.Header[http.CanonicalHeaderKey("X-LXD-properties")] ctype, ctypeParams, err := mime.ParseMediaType(r.Header.Get("Content-Type")) if err != nil { ctype = "application/octet-stream" } sha256 := sha256.New() var size int64 // Create a temporary file for the image tarball imageTarf, err := ioutil.TempFile(builddir, "lxd_tar_") if err != nil { return info, err } if ctype == "multipart/form-data" { // Create a temporary file for the rootfs tarball rootfsTarf, err := ioutil.TempFile(builddir, "lxd_tar_") if err != nil { return info, err } // Parse the POST data post.Seek(0, 0) mr := multipart.NewReader(post, ctypeParams["boundary"]) // Get the metadata tarball part, err := mr.NextPart() if err != nil { return info, err } if part.FormName() != "metadata" { return info, fmt.Errorf("Invalid multipart image") } size, err = io.Copy(io.MultiWriter(imageTarf, sha256), part) info.Size += size imageTarf.Close() if err != nil { return info, err } // Get the rootfs tarball part, err = mr.NextPart() if err != nil { return info, err } if part.FormName() != "rootfs" { return info, fmt.Errorf("Invalid multipart image") } size, err = io.Copy(io.MultiWriter(rootfsTarf, sha256), part) info.Size += size rootfsTarf.Close() if err != nil { return info, err } info.Filename = part.FileName() info.Fingerprint = fmt.Sprintf("%x", sha256.Sum(nil)) expectedFingerprint := r.Header.Get("X-LXD-fingerprint") if expectedFingerprint != "" && info.Fingerprint != expectedFingerprint { err = fmt.Errorf("fingerprints don't match, got %s expected %s", info.Fingerprint, expectedFingerprint) return info, err } imgfname := filepath.Join(builddir, info.Fingerprint) err = os.Rename(imageTarf.Name(), imgfname) if err != nil { return info, err } rootfsfname := filepath.Join(builddir, info.Fingerprint+".rootfs") err = os.Rename(rootfsTarf.Name(), rootfsfname) if err != nil { return info, err } imageMeta, err = getImageMetadata(imgfname) if err != nil { return info, err } } else { post.Seek(0, 0) size, err = io.Copy(io.MultiWriter(imageTarf, sha256), post) info.Size = size imageTarf.Close() if err != nil { return info, err } info.Filename = r.Header.Get("X-LXD-filename") info.Fingerprint = fmt.Sprintf("%x", sha256.Sum(nil)) expectedFingerprint := r.Header.Get("X-LXD-fingerprint") if expectedFingerprint != "" && info.Fingerprint != expectedFingerprint { err = fmt.Errorf("fingerprints don't match, got %s expected %s", info.Fingerprint, expectedFingerprint) return info, err } imgfname := filepath.Join(builddir, info.Fingerprint) err = os.Rename(imageTarf.Name(), imgfname) if err != nil { return info, err } imageMeta, err = getImageMetadata(imgfname) if err != nil { return info, err } } info.Architecture, _ = shared.ArchitectureId(imageMeta.Architecture) info.CreationDate = imageMeta.CreationDate info.ExpiryDate = imageMeta.ExpiryDate info.Properties = imageMeta.Properties if len(propHeaders) > 0 { for _, ph := range propHeaders { p, _ := url.ParseQuery(ph) for pkey, pval := range p { info.Properties[pkey] = pval[0] } } } return info, nil }
// ImageDownload checks if we have that Image Fingerprint else // downloads the image from a remote server. func (d *Daemon) ImageDownload(op *operation, server, fp string, secret string, forContainer bool, directDownload bool) error { if _, err := dbImageGet(d.db, fp, false, false); err == nil { shared.Log.Debug("Image already exists in the db", log.Ctx{"image": fp}) // already have it return nil } shared.Log.Info( "Image not in the db, downloading it", log.Ctx{"image": fp, "server": server}) // 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.Log.Info( "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.Log.Warn("Value transmitted over image lock semaphore?") } if _, err := dbImageGet(d.db, fp, false, true); err != nil { shared.Log.Error( "Previous download didn't succeed", log.Ctx{"image": fp}) return fmt.Errorf("Previous download didn't succeed") } shared.Log.Info( "Previous download succeeded", log.Ctx{"image": fp}) return nil } d.imagesDownloadingLock.RUnlock() shared.Log.Info( "Downloading the image", log.Ctx{"image": fp}) // 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 if !directDownload { /* 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) if err != nil { shared.Log.Error( "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) } } raw, err := d.httpGetFile(exporturl) if err != nil { shared.Log.Error( "Failed to download image", log.Ctx{"image": fp, "err": err}) return err } info.Size = raw.ContentLength destDir := shared.VarPath("images") destName := filepath.Join(destDir, fp) if shared.PathExists(destName) { d.Storage.ImageDelete(fp) } ctype, ctypeParams, err := mime.ParseMediaType(raw.Header.Get("Content-Type")) if err != nil { ctype = "application/octet-stream" } body := &Progress{Reader: raw.Body, length: raw.ContentLength, op: op} 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.Log.Error( "Invalid multipart image", log.Ctx{"image": fp, "err": err}) return err } if part.FormName() != "metadata" { shared.Log.Error( "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.Log.Error( "Failed to save image", log.Ctx{"image": fp, "err": err}) return err } _, err = io.Copy(f, part) f.Close() if err != nil { shared.Log.Error( "Failed to save image", log.Ctx{"image": fp, "err": err}) return err } // Get the rootfs tarball part, err = mr.NextPart() if err != nil { shared.Log.Error( "Invalid multipart image", log.Ctx{"image": fp, "err": err}) return err } if part.FormName() != "rootfs" { shared.Log.Error( "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.Log.Error( "Failed to save image", log.Ctx{"image": fp, "err": err}) return err } _, err = io.Copy(f, part) f.Close() if err != nil { shared.Log.Error( "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.Log.Error( "Failed to save image", log.Ctx{"image": fp, "err": err}) return err } _, err = io.Copy(f, body) f.Close() if err != nil { shared.Log.Error( "Failed to save image", log.Ctx{"image": fp, "err": err}) return err } } if directDownload { imageMeta, err := getImageMetadata(destName) if err != nil { return err } info.Architecture, _ = shared.ArchitectureId(imageMeta.Architecture) info.CreationDate = imageMeta.CreationDate info.ExpiryDate = imageMeta.ExpiryDate info.Properties = imageMeta.Properties } // By default, make all downloaded images private info.Public = false _, err = imageBuildFromInfo(d, info) if err != nil { shared.Log.Error( "Failed to create image", log.Ctx{"image": fp, "err": err}) return err } shared.Log.Info( "Download succeeded", log.Ctx{"image": fp}) if forContainer { return dbImageLastAccessInit(d.db, fp) } return nil }
func createFromImage(d *Daemon, req *containerPostReq) Response { var hash string var err error if req.Source.Fingerprint != "" { hash = req.Source.Fingerprint } else if req.Source.Alias != "" { if req.Source.Server != "" { hash = req.Source.Alias } else { _, alias, err := dbImageAliasGet(d.db, req.Source.Alias, true) if err != nil { return InternalError(err) } hash = alias.Target } } else if req.Source.Fingerprint != "" { hash = req.Source.Fingerprint } else if req.Source.Properties != nil { if req.Source.Server != "" { return BadRequest(fmt.Errorf("Property match is only supported for local images")) } hashes, err := dbImagesGet(d.db, false) if err != nil { return InternalError(err) } var image *shared.ImageInfo for _, hash := range hashes { _, img, err := dbImageGet(d.db, hash, false, true) if err != nil { continue } if image != nil && img.CreationDate.Before(image.CreationDate) { continue } match := true for key, value := range req.Source.Properties { if img.Properties[key] != value { match = false break } } if !match { continue } image = img } if image == nil { return BadRequest(fmt.Errorf("No matching image could be found")) } hash = image.Fingerprint } else { return BadRequest(fmt.Errorf("Must specify one of alias, fingerprint or properties for init from image")) } run := func(op *operation) error { if req.Source.Server != "" { hash, err = d.ImageDownload( op, req.Source.Server, req.Source.Protocol, req.Source.Certificate, req.Source.Secret, hash, true, daemonConfig["images.auto_update_cached"].GetBool()) if err != nil { return err } } _, imgInfo, err := dbImageGet(d.db, hash, false, false) if err != nil { return err } hash = imgInfo.Fingerprint architecture, err := shared.ArchitectureId(imgInfo.Architecture) if err != nil { architecture = 0 } args := containerArgs{ Architecture: architecture, BaseImage: hash, Config: req.Config, Ctype: cTypeRegular, Devices: req.Devices, Ephemeral: req.Ephemeral, Name: req.Name, Profiles: req.Profiles, } _, err = containerCreateFromImage(d, args, hash) return err } resources := map[string][]string{} resources["containers"] = []string{req.Name} op, err := operationCreate(operationClassTask, resources, nil, run, nil, nil) if err != nil { return InternalError(err) } return OperationResponse(op) }
func createFromMigration(d *Daemon, req *containerPostReq) Response { if req.Source.Mode != "pull" && req.Source.Mode != "push" { return NotImplemented } architecture, err := shared.ArchitectureId(req.Architecture) if err != nil { architecture = 0 } args := containerArgs{ Architecture: architecture, BaseImage: req.Source.BaseImage, Config: req.Config, Ctype: cTypeRegular, Devices: req.Devices, Ephemeral: req.Ephemeral, Name: req.Name, Profiles: req.Profiles, } var c container _, _, err = dbImageGet(d.db, req.Source.BaseImage, false, true) /* Only create a container from an image if we're going to * rsync over the top of it. In the case of a better file * transfer mechanism, let's just use that. * * TODO: we could invent some negotiation here, where if the * source and sink both have the same image, we can clone from * it, but we have to know before sending the snapshot that * we're sending the whole thing or just a delta from the * image, so one extra negotiation round trip is needed. An * alternative is to move actual container object to a later * point and just negotiate it over the migration control * socket. Anyway, it'll happen later :) */ if err == nil && d.Storage.MigrationType() == MigrationFSType_RSYNC { c, err = containerCreateFromImage(d, args, req.Source.BaseImage) if err != nil { return InternalError(err) } } else { c, err = containerCreateAsEmpty(d, args) if err != nil { return InternalError(err) } } var cert *x509.Certificate if req.Source.Certificate != "" { certBlock, _ := pem.Decode([]byte(req.Source.Certificate)) if certBlock == nil { return InternalError(fmt.Errorf("Invalid certificate")) } cert, err = x509.ParseCertificate(certBlock.Bytes) if err != nil { return InternalError(err) } } config, err := shared.GetTLSConfig("", "", "", cert) if err != nil { c.Delete() return InternalError(err) } push := false if req.Source.Mode == "push" { push = true } migrationArgs := MigrationSinkArgs{ Url: req.Source.Operation, Dialer: websocket.Dialer{ TLSClientConfig: config, NetDial: shared.RFC3493Dialer}, Container: c, Secrets: req.Source.Websockets, Push: push, Live: req.Source.Live, } sink, err := NewMigrationSink(&migrationArgs) if err != nil { c.Delete() return InternalError(err) } run := func(op *operation) error { // And finaly run the migration. err = sink.Do(op) if err != nil { shared.LogError("Error during migration sink", log.Ctx{"err": err}) c.Delete() return fmt.Errorf("Error transferring container data: %s", err) } err = c.TemplateApply("copy") if err != nil { return err } return nil } resources := map[string][]string{} resources["containers"] = []string{req.Name} var op *operation if push { op, err = operationCreate(operationClassWebsocket, resources, sink.Metadata(), run, nil, sink.Connect) if err != nil { return InternalError(err) } } else { op, err = operationCreate(operationClassTask, resources, nil, run, nil, nil) if err != nil { return InternalError(err) } } return OperationResponse(op) }
func dbImageInsert(db *sql.DB, fp string, fname string, sz int64, public bool, autoUpdate bool, architecture string, creationDate time.Time, expiryDate time.Time, properties map[string]string) error { arch, err := shared.ArchitectureId(architecture) if err != nil { arch = 0 } tx, err := dbBegin(db) if err != nil { return err } publicInt := 0 if public { publicInt = 1 } autoUpdateInt := 0 if autoUpdate { autoUpdateInt = 1 } stmt, err := tx.Prepare(`INSERT INTO images (fingerprint, filename, size, public, auto_update, architecture, creation_date, expiry_date, upload_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, strftime("%s"))`) if err != nil { tx.Rollback() return err } defer stmt.Close() result, err := stmt.Exec(fp, fname, sz, publicInt, autoUpdateInt, arch, creationDate, expiryDate) if err != nil { tx.Rollback() return err } if len(properties) > 0 { id64, err := result.LastInsertId() if err != nil { tx.Rollback() return err } id := int(id64) pstmt, err := tx.Prepare(`INSERT INTO images_properties (image_id, type, key, value) VALUES (?, 0, ?, ?)`) if err != nil { tx.Rollback() return err } defer pstmt.Close() for k, v := range properties { // we can assume, that there is just one // value per key _, err = pstmt.Exec(id, k, v) if err != nil { tx.Rollback() return err } } } if err := txCommit(tx); err != nil { return err } return nil }
func (d *Daemon) Init() error { /* Setup logging */ if shared.Log == nil { shared.SetLogger("", "", true, true) } if !d.IsMock { shared.Log.Info("LXD is starting", log.Ctx{"path": shared.VarPath("")}) } else { shared.Log.Info("Mock LXD is starting", log.Ctx{"path": shared.VarPath("")}) } /* Detect user namespaces */ runningInUserns = shared.RunningInUserNS() /* Detect apparmor support */ if aaEnabled && os.Getenv("LXD_SECURITY_APPARMOR") == "false" { aaEnabled = false shared.Log.Warn("Per-container AppArmor profiles have been manually disabled") } if aaEnabled && !shared.IsDir("/sys/kernel/security/apparmor") { aaEnabled = false shared.Log.Warn("Per-container AppArmor profiles disabled because of lack of kernel support") } if aaEnabled && !haveMacAdmin() { shared.Log.Warn("Per-container AppArmor profiles are disabled because mac_admin capability is missing.") aaEnabled = false } _, err := exec.LookPath("apparmor_parser") if aaEnabled && err != nil { aaEnabled = false shared.Log.Warn("Per-container AppArmor profiles disabled because 'apparmor_parser' couldn't be found") } if aaEnabled && runningInUserns { aaEnabled = false shared.Log.Warn("Per-container AppArmor profiles disabled because LXD is running inside a user namespace") } /* Get the list of supported architectures */ var architectures = []int{} uname := syscall.Utsname{} if err := syscall.Uname(&uname); err != nil { return err } architectureName := "" for _, c := range uname.Machine { if c == 0 { break } architectureName += string(byte(c)) } 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 /* Create required paths */ d.lxcpath = shared.VarPath("containers") err = os.MkdirAll(d.lxcpath, 0755) if err != nil { return err } // Create default directories if err := os.MkdirAll(shared.VarPath("images"), 0700); err != nil { return err } if err := os.MkdirAll(shared.VarPath("snapshots"), 0700); err != nil { return err } if err := os.MkdirAll(shared.VarPath("devlxd"), 0755); err != nil { return err } /* Detect the filesystem */ d.BackingFs, err = filesystemDetect(d.lxcpath) if err != nil { shared.Log.Error("Error detecting backing fs", log.Ctx{"err": err}) } /* Read the uid/gid allocation */ d.IdmapSet, err = shared.DefaultIdmapSet() if err != nil { shared.Log.Warn("Error reading idmap", log.Ctx{"err": err.Error()}) shared.Log.Warn("Only privileged containers will be able to run") } else { shared.Log.Info("Default uid/gid map:") for _, lxcmap := range d.IdmapSet.ToLxcString() { shared.Log.Info(strings.TrimRight(" - "+lxcmap, "\n")) } } /* Initialize the database */ err = initializeDbObject(d, shared.VarPath("lxd.db")) if err != nil { return err } /* Prune images */ d.pruneChan = make(chan bool) go func() { for { expiryStr, err := dbImageExpiryGet(d.db) var expiry int if err != nil { expiry = 10 } else { expiry, err = strconv.Atoi(expiryStr) if err != nil { expiry = 10 } if expiry <= 0 { expiry = 1 } } timer := time.NewTimer(time.Duration(expiry) * 24 * time.Hour) timeChan := timer.C select { case <-timeChan: d.pruneExpiredImages() case <-d.pruneChan: d.pruneExpiredImages() timer.Stop() } } }() /* Setup /dev/lxd */ d.devlxd, err = createAndBindDevLxd() if err != nil { return err } if err := setupSharedMounts(); err != nil { return err } var tlsConfig *tls.Config if !d.IsMock { err = d.SetupStorageDriver() if err != nil { return fmt.Errorf("Failed to setup storage: %s", err) } /* Restart containers */ containersRestart(d) containersWatch(d) /* Setup the TLS authentication */ certf, keyf, err := readMyCert() if err != nil { return err } d.certf = certf d.keyf = keyf readSavedClientCAList(d) tlsConfig, err = shared.GetTLSConfig(d.certf, d.keyf) if err != nil { return err } } /* Setup the web server */ d.mux = mux.NewRouter() 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) } d.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { shared.Log.Debug("Sending top level 404", log.Ctx{"url": r.URL}) w.Header().Set("Content-Type", "application/json") NotFound.Render(w) }) listeners, err := activation.Listeners(false) if err != nil { return err } var sockets []Socket if len(listeners) > 0 { shared.Log.Info("LXD is socket activated") for _, listener := range listeners { if shared.PathExists(listener.Addr().String()) { sockets = append(sockets, Socket{Socket: listener, CloseOnExit: false}) } else { tlsListener := tls.NewListener(listener, tlsConfig) sockets = append(sockets, Socket{Socket: tlsListener, CloseOnExit: false}) } } } else { shared.Log.Info("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) { c := &lxd.Config{Remotes: map[string]lxd.RemoteConfig{}} _, err := lxd.NewClient(c, "") if err != nil { shared.Log.Debug("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 } } } 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 } gid, err := shared.GroupId(*group) if err != nil { return err } if err := os.Chown(localSocketPath, os.Getuid(), gid); err != nil { return err } sockets = append(sockets, Socket{Socket: unixl, CloseOnExit: true}) } listenAddr, err := d.ConfigValueGet("core.https_address") if err != nil { return err } if listenAddr != "" { _, _, err := net.SplitHostPort(listenAddr) if err != nil { listenAddr = fmt.Sprintf("%s:%s", listenAddr, shared.DefaultPort) } tcpl, err := tls.Listen("tcp", listenAddr, tlsConfig) if err != nil { shared.Log.Error("cannot listen on https socket, skipping...", log.Ctx{"err": err}) } else { sockets = append(sockets, Socket{Socket: tcpl, CloseOnExit: true}) } } if !d.IsMock { d.Sockets = sockets } else { d.Sockets = []Socket{} } d.tomb.Go(func() error { shared.Log.Info("REST API daemon:") for _, socket := range d.Sockets { shared.Log.Info(" - binding socket", log.Ctx{"socket": socket.Socket.Addr()}) current_socket := socket d.tomb.Go(func() error { return http.Serve(current_socket.Socket, d.mux) }) } d.tomb.Go(func() error { server := devLxdServer(d) return server.Serve(d.devlxd) }) return nil }) return nil }
func (d *Daemon) Init() error { d.shutdownChan = make(chan bool) /* Set the executable path */ absPath, err := os.Readlink("/proc/self/exe") if err != nil { return err } d.execPath = absPath /* 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 } } if !d.IsMock { shared.Log.Info("LXD is starting", log.Ctx{"path": shared.VarPath("")}) } else { shared.Log.Info("Mock LXD is starting", 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.Log.Warn("AppArmor support has been manually disabled") } if aaAvailable && !shared.IsDir("/sys/kernel/security/apparmor") { aaAvailable = false aaAdmin = false shared.Log.Warn("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.Log.Warn("AppArmor support has been disabled because 'apparmor_parser' couldn't be found") } /* Detect AppArmor admin support */ if aaAdmin && !haveMacAdmin() { aaAdmin = false shared.Log.Warn("Per-container AppArmor profiles are disabled because the mac_admin capability is missing.") } if aaAdmin && runningInUserns { aaAdmin = false shared.Log.Warn("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.Log.Warn("Per-container AppArmor profiles are disabled because LXD is already protected by AppArmor.") } } /* Detect CGroup support */ cgCpuController = shared.PathExists("/sys/fs/cgroup/cpu/") if !cgCpuController { shared.Log.Warn("Couldn't find the CGroup CPU controller, CPU time limits will be ignored.") } cgCpusetController = shared.PathExists("/sys/fs/cgroup/cpuset/") if !cgCpusetController { shared.Log.Warn("Couldn't find the CGroup CPUset controller, CPU pinning will be ignored.") } cgMemoryController = shared.PathExists("/sys/fs/cgroup/memory/") if !cgMemoryController { shared.Log.Warn("Couldn't find the CGroup memory controller, memory limits will be ignored.") } cgSwapAccounting = shared.PathExists("/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes") if !cgSwapAccounting { shared.Log.Warn("CGroup memory swap accounting is disabled, swap limits will be ignored.") } /* Get the list of supported architectures */ var architectures = []int{} uname := syscall.Utsname{} if err := syscall.Uname(&uname); err != nil { return err } architectureName := "" for _, c := range uname.Machine { if c == 0 { break } architectureName += string(byte(c)) } 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.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.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.Log.Error("Error detecting backing fs", log.Ctx{"err": err}) } /* Read the uid/gid allocation */ d.IdmapSet, err = shared.DefaultIdmapSet() if err != nil { shared.Log.Warn("Error reading idmap", log.Ctx{"err": err.Error()}) shared.Log.Warn("Only privileged containers will be able to run") } else { shared.Log.Info("Default uid/gid map:") for _, lxcmap := range d.IdmapSet.ToLxcString() { shared.Log.Info(strings.TrimRight(" - "+lxcmap, "\n")) } } /* Initialize the database */ err = initializeDbObject(d, shared.VarPath("lxd.db")) if err != nil { return err } /* Prune images */ d.pruneChan = make(chan bool) go func() { d.pruneExpiredImages() for { timer := time.NewTimer(24 * time.Hour) timeChan := timer.C select { case <-timeChan: /* run once per day */ d.pruneExpiredImages() case <-d.pruneChan: /* run when image.remote_cache_expiry is changed */ d.pruneExpiredImages() timer.Stop() } } }() /* Setup /dev/lxd */ d.devlxd, err = createAndBindDevLxd() if err != nil { return err } if err := setupSharedMounts(); err != nil { return err } var tlsConfig *tls.Config if !d.IsMock { err = d.SetupStorageDriver() if err != nil { return fmt.Errorf("Failed to setup storage: %s", err) } /* Restart containers */ go func() { containersRestart(d) }() /* Start the scheduler */ go deviceTaskScheduler(d) /* Setup the TLS authentication */ certf, keyf, err := readMyCert() if err != nil { return err } d.certf = certf d.keyf = keyf readSavedClientCAList(d) tlsConfig, err = shared.GetTLSConfig(d.certf, d.keyf) if err != nil { return err } } /* Setup the web server */ d.mux = mux.NewRouter() 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.Log.Debug("Sending top level 404", log.Ctx{"url": r.URL}) w.Header().Set("Content-Type", "application/json") NotFound.Render(w) }) listeners, err := activation.Listeners(false) if err != nil { return err } var sockets []Socket if len(listeners) > 0 { shared.Log.Info("LXD is socket activated") for _, listener := range listeners { if shared.PathExists(listener.Addr().String()) { sockets = append(sockets, Socket{Socket: listener, CloseOnExit: false}) } else { tlsListener := tls.NewListener(listener, tlsConfig) sockets = append(sockets, Socket{Socket: tlsListener, CloseOnExit: false}) } } } else { shared.Log.Info("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) { c, err := lxd.NewClient(&lxd.DefaultConfig, "local") if err != nil { return err } err = c.Finger() if err != nil { shared.Log.Debug("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 } sockets = append(sockets, Socket{Socket: unixl, CloseOnExit: true}) } listenAddr, err := d.ConfigValueGet("core.https_address") if err != nil { return err } if listenAddr != "" { _, _, err := net.SplitHostPort(listenAddr) if err != nil { listenAddr = fmt.Sprintf("%s:%s", listenAddr, shared.DefaultPort) } tcpl, err := tls.Listen("tcp", listenAddr, tlsConfig) if err != nil { shared.Log.Error("cannot listen on https socket, skipping...", log.Ctx{"err": err}) } else { sockets = append(sockets, Socket{Socket: tcpl, CloseOnExit: true}) } } if !d.IsMock { d.Sockets = sockets } else { d.Sockets = []Socket{} } d.tomb.Go(func() error { shared.Log.Info("REST API daemon:") for _, socket := range d.Sockets { shared.Log.Info(" - binding socket", log.Ctx{"socket": socket.Socket.Addr()}) current_socket := socket d.tomb.Go(func() error { return http.Serve(current_socket.Socket, d.mux) }) } d.tomb.Go(func() error { server := devLxdServer(d) return server.Serve(d.devlxd) }) return nil }) return nil }
func createFromImage(d *Daemon, req *containerPostReq) Response { var hash string var err error if req.Source.Fingerprint != "" { hash = req.Source.Fingerprint } else if req.Source.Alias != "" { if req.Source.Server != "" { hash = req.Source.Alias } else { _, alias, err := dbImageAliasGet(d.db, req.Source.Alias, true) if err != nil { return InternalError(err) } hash = alias.Target } } else if req.Source.Fingerprint != "" { hash = req.Source.Fingerprint } else { return BadRequest(fmt.Errorf("must specify one of alias or fingerprint for init from image")) } run := func(op *operation) error { if req.Source.Server != "" { updateCached, _ := d.ConfigValueGet("images.auto_update_cached") hash, err = d.ImageDownload(op, req.Source.Server, req.Source.Protocol, req.Source.Certificate, req.Source.Secret, hash, true, updateCached != "false") if err != nil { return err } } _, imgInfo, err := dbImageGet(d.db, hash, false, false) if err != nil { return err } hash = imgInfo.Fingerprint architecture, err := shared.ArchitectureId(imgInfo.Architecture) if err != nil { architecture = 0 } args := containerArgs{ Architecture: architecture, BaseImage: hash, Config: req.Config, Ctype: cTypeRegular, Devices: req.Devices, Ephemeral: req.Ephemeral, Name: req.Name, Profiles: req.Profiles, } _, err = containerCreateFromImage(d, args, hash) return err } resources := map[string][]string{} resources["containers"] = []string{req.Name} op, err := operationCreate(operationClassTask, resources, nil, run, nil, nil) if err != nil { return InternalError(err) } return OperationResponse(op) }