/* * This is called by lxd when called as "lxd forkstart <container>" * 'forkstart' is used instead of just 'start' in the hopes that people * do not accidentally type 'lxd start' instead of 'lxc start' * * We expect to read the lxcconfig over fd 3. */ func startContainer(args []string) error { if len(args) != 4 { return fmt.Errorf("Bad arguments: %q\n", args) } name := args[1] lxcpath := args[2] configPath := args[3] c, err := lxc.NewContainer(name, lxcpath) if err != nil { return fmt.Errorf("Error initializing container for start: %q", err) } err = c.LoadConfigFile(configPath) if err != nil { return fmt.Errorf("Error opening startup config file: %q", err) } err = c.Start() if err != nil { os.Remove(configPath) } else { shared.FileMove(configPath, shared.LogPath(name, "lxc.conf")) } return err }
func containerLogsGet(d *Daemon, r *http.Request) Response { /* Let's explicitly *not* try to do a containerLoadByName here. In some * cases (e.g. when container creation failed), the container won't * exist in the DB but it does have some log files on disk. * * However, we should check this name and ensure it's a valid container * name just so that people can't list arbitrary directories. */ name := mux.Vars(r)["name"] if err := containerValidName(name); err != nil { return BadRequest(err) } result := []string{} dents, err := ioutil.ReadDir(shared.LogPath(name)) if err != nil { return SmartError(err) } for _, f := range dents { result = append(result, fmt.Sprintf("/%s/containers/%s/logs/%s", shared.APIVersion, name, f.Name())) } return SyncResponse(true, result) }
func containerLogsGet(d *Daemon, r *http.Request) Response { /* Let's explicitly *not* try to do a containerLXDLoad here. In some * cases (e.g. when container creation failed), the container won't * exist in the DB but it does have some log files on disk. * * However, we should check this name and ensure it's a valid container * name just so that people can't list arbitrary directories. */ name := mux.Vars(r)["name"] if err := validContainerName(name); err != nil { return BadRequest(err) } result := []map[string]interface{}{} dents, err := ioutil.ReadDir(shared.LogPath(name)) if err != nil { return SmartError(err) } for _, f := range dents { result = append(result, map[string]interface{}{ "name": f.Name(), "size": f.Size(), }) } return SyncResponse(true, result) }
/* * This is called by lxd when called as "lxd forkstart <container>" * 'forkstart' is used instead of just 'start' in the hopes that people * do not accidentally type 'lxd start' instead of 'lxc start' * * We expect to read the lxcconfig over fd 3. */ func startContainer(args []string) error { if len(args) != 4 { return fmt.Errorf("Bad arguments: %q", args) } name := args[1] lxcpath := args[2] configPath := args[3] c, err := lxc.NewContainer(name, lxcpath) if err != nil { return fmt.Errorf("Error initializing container for start: %q", err) } err = c.LoadConfigFile(configPath) if err != nil { return fmt.Errorf("Error opening startup config file: %q", err) } /* due to https://github.com/golang/go/issues/13155 and the * CollectOutput call we make for the forkstart process, we need to * close our stdin/stdout/stderr here. Collecting some of the logs is * better than collecting no logs, though. */ os.Stdin.Close() os.Stderr.Close() os.Stdout.Close() err = c.Start() if err != nil { os.Remove(configPath) } else { shared.FileMove(configPath, shared.LogPath(name, "lxc.conf")) } return err }
/* * This is called by lxd when called as "lxd forkstart <container>" * 'forkstart' is used instead of just 'start' in the hopes that people * do not accidentally type 'lxd start' instead of 'lxc start' * * We expect to read the lxcconfig over fd 3. */ func startContainer(args []string) error { if len(args) != 4 { return fmt.Errorf("Bad arguments: %q", args) } name := args[1] lxcpath := args[2] configPath := args[3] c, err := lxc.NewContainer(name, lxcpath) if err != nil { return fmt.Errorf("Error initializing container for start: %q", err) } err = c.LoadConfigFile(configPath) if err != nil { return fmt.Errorf("Error opening startup config file: %q", err) } /* due to https://github.com/golang/go/issues/13155 and the * CollectOutput call we make for the forkstart process, we need to * close our stdin/stdout/stderr here. Collecting some of the logs is * better than collecting no logs, though. */ os.Stdin.Close() os.Stderr.Close() os.Stdout.Close() // Redirect stdout and stderr to a log file logPath := shared.LogPath(name, "forkstart.log") if shared.PathExists(logPath) { os.Remove(logPath) } logFile, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0644) if err == nil { syscall.Dup3(int(logFile.Fd()), 1, 0) syscall.Dup3(int(logFile.Fd()), 2, 0) } // Move the config so we can inspect it on failure shared.FileMove(configPath, shared.LogPath(name, "lxc.conf")) return c.Start() }
func containerLogDelete(d *Daemon, r *http.Request) Response { name := mux.Vars(r)["name"] file := mux.Vars(r)["file"] if err := validContainerName(name); err != nil { return BadRequest(err) } if !validLogFileName(file) { return BadRequest(fmt.Errorf("log file name %s not valid", file)) } return SmartError(os.Remove(shared.LogPath(name, file))) }
func (c *lxdContainer) Start() error { f, err := ioutil.TempFile("", "lxd_lxc_startconfig_") if err != nil { return err } configPath := f.Name() if err = f.Chmod(0600); err != nil { f.Close() os.Remove(configPath) return err } f.Close() err = c.c.SaveConfigFile(configPath) if err != nil { return err } err = templateApply(c, "start") if err != nil { return err } err = exec.Command( os.Args[0], "forkstart", c.name, c.daemon.lxcpath, configPath).Run() if err != nil { err = fmt.Errorf( "Error calling 'lxd forkstart %s %s %s': err='%v'", c.name, c.daemon.lxcpath, shared.LogPath(c.name, "lxc.conf"), err) } if err == nil && c.ephemeral == true { containerWatchEphemeral(c) } return err }
func containerLogGet(d *Daemon, r *http.Request) Response { name := mux.Vars(r)["name"] file := mux.Vars(r)["file"] if err := validContainerName(name); err != nil { return BadRequest(err) } if !validLogFileName(file) { return BadRequest(fmt.Errorf("log file name %s not valid", file)) } ent := fileResponseEntry{ path: shared.LogPath(name, file), filename: file, } return FileResponse(r, []fileResponseEntry{ent}, nil, false) }
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 }
/* * This is called by lxd when called as "lxd forkexec <container>" */ func execContainer(args []string) (int, error) { if len(args) < 6 { return -1, fmt.Errorf("Bad arguments: %q", args) } wait := true if args[1] == "nowait" { wait = false } name := args[2] lxcpath := args[3] configPath := args[4] c, err := lxc.NewContainer(name, lxcpath) if err != nil { return -1, fmt.Errorf("Error initializing container for start: %q", err) } err = c.LoadConfigFile(configPath) if err != nil { return -1, fmt.Errorf("Error opening startup config file: %q", err) } syscall.Dup3(int(os.Stdin.Fd()), 200, 0) syscall.Dup3(int(os.Stdout.Fd()), 201, 0) syscall.Dup3(int(os.Stderr.Fd()), 202, 0) syscall.Close(int(os.Stdin.Fd())) syscall.Close(int(os.Stdout.Fd())) syscall.Close(int(os.Stderr.Fd())) opts := lxc.DefaultAttachOptions opts.ClearEnv = true opts.StdinFd = 200 opts.StdoutFd = 201 opts.StderrFd = 202 logPath := shared.LogPath(name, "forkexec.log") if shared.PathExists(logPath) { os.Remove(logPath) } logFile, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0644) if err == nil { syscall.Dup3(int(logFile.Fd()), 1, 0) syscall.Dup3(int(logFile.Fd()), 2, 0) } env := []string{} cmd := []string{} section := "" for _, arg := range args[5:len(args)] { // The "cmd" section must come last as it may contain a -- if arg == "--" && section != "cmd" { section = "" continue } if section == "" { section = arg continue } if section == "env" { fields := strings.SplitN(arg, "=", 2) if len(fields) == 2 && fields[0] == "HOME" { opts.Cwd = fields[1] } env = append(env, arg) } else if section == "cmd" { cmd = append(cmd, arg) } else { return -1, fmt.Errorf("Invalid exec section: %s", section) } } opts.Env = env var status int if wait { status, err = c.RunCommandStatus(cmd, opts) if err != nil { return -1, fmt.Errorf("Failed running command and waiting for it to exit: %q", err) } } else { status, err = c.RunCommandNoWait(cmd, opts) if err != nil { return -1, fmt.Errorf("Failed running command: %q", err) } // Send the PID of the executing process. w := os.NewFile(uintptr(3), "attachedPid") defer w.Close() err = json.NewEncoder(w).Encode(status) if err != nil { return -1, fmt.Errorf("Failed sending PID of executing command: %q", err) } proc, err := os.FindProcess(status) if err != nil { return -1, fmt.Errorf("Failed finding process: %q", err) } procState, err := proc.Wait() if err != nil { return -1, fmt.Errorf("Failed waiting on process %d: %q", status, err) } if procState.Success() { return 0, nil } status, ok := procState.Sys().(syscall.WaitStatus) if ok { if status.Exited() { return status.ExitStatus(), nil } // Backwards compatible behavior. Report success when we exited // due to a signal. Otherwise this may break Jenkins, e.g. when // lxc exec foo reboot receives SIGTERM and status.Exitstats() // would report -1. if status.Signaled() { return 0, nil } } return -1, fmt.Errorf("Command failed") } return status >> 8, nil }
func containerCreateInternal(d *Daemon, args containerArgs) (container, error) { // Set default values if args.Profiles == nil { args.Profiles = []string{"default"} } if args.Config == nil { args.Config = map[string]string{} } if args.BaseImage != "" { args.Config["volatile.base_image"] = args.BaseImage } if args.Devices == nil { args.Devices = shared.Devices{} } if args.Architecture == 0 { args.Architecture = d.architectures[0] } // Validate container name if args.Ctype == cTypeRegular { err := containerValidName(args.Name) if err != nil { return nil, err } } // Validate container config err := containerValidConfig(d, args.Config, false, false) if err != nil { return nil, err } // Validate container devices err = containerValidDevices(args.Devices, false, false) if err != nil { return nil, err } // Validate architecture _, err = shared.ArchitectureName(args.Architecture) if err != nil { return nil, err } // Validate profiles profiles, err := dbProfiles(d.db) if err != nil { return nil, err } for _, profile := range args.Profiles { if !shared.StringInSlice(profile, profiles) { return nil, fmt.Errorf("Requested profile '%s' doesn't exist", profile) } } path := containerPath(args.Name, args.Ctype == cTypeSnapshot) if shared.PathExists(path) { if shared.IsSnapshot(args.Name) { return nil, fmt.Errorf("Snapshot '%s' already exists", args.Name) } return nil, fmt.Errorf("The container already exists") } // Wipe any existing log for this container name os.RemoveAll(shared.LogPath(args.Name)) // Create the container entry id, err := dbContainerCreate(d.db, args) if err != nil { return nil, err } args.Id = id // Read the timestamp from the database dbArgs, err := dbContainerGet(d.db, args.Name) if err != nil { return nil, err } args.CreationDate = dbArgs.CreationDate args.LastUsedDate = dbArgs.LastUsedDate return containerLXCCreate(d, args) }
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 */ 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.") } 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.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 } /* 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 deviceEventListener(d) /* Re-balance in case things changed while LXD was down */ deviceTaskBalance(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.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 } 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, &lxdHttpServer{d.mux, d}) }) } d.tomb.Go(func() error { server := devLxdServer(d) return server.Serve(d.devlxd) }) return nil }) return nil }
func newLxdContainer(name string, daemon *Daemon) (*lxdContainer, error) { d := &lxdContainer{ daemon: daemon, ephemeral: false, architecture: -1, cType: -1, id: -1} ephemInt := -1 templateConfBase := "ubuntu" templateConfDir := os.Getenv("LXC_TEMPLATE_CONFIG") if templateConfDir == "" { templateConfDir = "/usr/share/lxc/config" } q := "SELECT id, architecture, type, ephemeral FROM containers WHERE name=?" arg1 := []interface{}{name} arg2 := []interface{}{&d.id, &d.architecture, &d.cType, &ephemInt} err := dbQueryRowScan(daemon.db, q, arg1, arg2) if err != nil { return nil, err } if d.id == -1 { return nil, fmt.Errorf("Unknown container") } if ephemInt == 1 { d.ephemeral = true } c, err := lxc.NewContainer(name, daemon.lxcpath) if err != nil { return nil, err } d.c = c dir := shared.LogPath(c.Name()) err = os.MkdirAll(dir, 0700) if err != nil { return nil, err } if err = d.c.SetLogFile(filepath.Join(dir, "lxc.log")); err != nil { return nil, err } personality, err := shared.ArchitecturePersonality(d.architecture) if err == nil { err = c.SetConfigItem("lxc.arch", personality) if err != nil { return nil, err } } err = c.SetConfigItem("lxc.include", fmt.Sprintf("%s/%s.common.conf", templateConfDir, templateConfBase)) if err != nil { return nil, err } if !d.isPrivileged() { err = c.SetConfigItem("lxc.include", fmt.Sprintf("%s/%s.userns.conf", templateConfDir, templateConfBase)) if err != nil { return nil, err } } config, err := dbGetConfig(daemon.db, d.id) if err != nil { return nil, err } d.config = config profiles, err := dbGetProfiles(daemon.db, d.id) if err != nil { return nil, err } d.profiles = profiles d.devices = shared.Devices{} d.name = name rootfsPath := shared.VarPath("lxc", name, "rootfs") err = c.SetConfigItem("lxc.rootfs", rootfsPath) if err != nil { return nil, err } err = c.SetConfigItem("lxc.loglevel", "0") if err != nil { return nil, err } err = c.SetConfigItem("lxc.utsname", name) if err != nil { return nil, err } err = c.SetConfigItem("lxc.tty", "0") if err != nil { return nil, err } if err := setupDevLxdMount(c); err != nil { return nil, err } /* apply profiles */ for _, p := range profiles { err := applyProfile(daemon, d, p) if err != nil { return nil, err } } /* get container_devices */ newdevs, err := dbGetDevices(daemon.db, d.name, false) if err != nil { return nil, err } for k, v := range newdevs { d.devices[k] = v } if err := d.setupMacAddresses(daemon); err != nil { return nil, err } /* now add the lxc.* entries for the configured devices */ err = d.applyDevices() if err != nil { return nil, err } if !d.isPrivileged() { d.idmapset = daemon.IdmapSet // TODO - per-tenant idmaps } err = d.applyIdmapSet() if err != nil { return nil, err } err = d.applyConfig(d.config, false) if err != nil { return nil, err } return d, nil }
func (d *Daemon) ExpireLogs() error { entries, err := ioutil.ReadDir(shared.LogPath()) if err != nil { return err } result, err := dbContainersList(d.db, cTypeRegular) if err != nil { return err } newestFile := func(path string, dir os.FileInfo) time.Time { newest := dir.ModTime() entries, err := ioutil.ReadDir(path) if err != nil { return newest } for _, entry := range entries { if entry.ModTime().After(newest) { newest = entry.ModTime() } } return newest } for _, entry := range entries { // Check if the container still exists if shared.StringInSlice(entry.Name(), result) { // Remove any log file which wasn't modified in the past 48 hours logs, err := ioutil.ReadDir(shared.LogPath(entry.Name())) if err != nil { return err } for _, logfile := range logs { path := shared.LogPath(entry.Name(), logfile.Name()) // Always keep the LXC config if logfile.Name() == "lxc.conf" { continue } // Deal with directories (snapshots) if logfile.IsDir() { newest := newestFile(path, logfile) if time.Since(newest).Hours() >= 48 { os.RemoveAll(path) if err != nil { return err } } continue } // Individual files if time.Since(logfile.ModTime()).Hours() >= 48 { err := os.Remove(path) if err != nil { return err } } } } else { // Empty directory if unchanged in the past 24 hours path := shared.LogPath(entry.Name()) newest := newestFile(path, entry) if time.Since(newest).Hours() >= 24 { err := os.RemoveAll(path) if err != nil { return err } } } } return nil }
func (c *containerLXD) LogPathGet() string { return shared.LogPath(c.NameGet()) }
func CollectCRIULogFile(c container, imagesDir string, function string, method string) error { t := time.Now().Format(time.RFC3339) newPath := shared.LogPath(c.Name(), fmt.Sprintf("%s_%s_%s.log", function, method, t)) return shared.FileCopy(filepath.Join(imagesDir, fmt.Sprintf("%s.log", method)), newPath) }
func collectMigrationLogFile(c *lxc.Container, imagesDir string, method string) error { t := time.Now().Format(time.RFC3339) newPath := shared.LogPath(c.Name(), fmt.Sprintf("migration_%s_%s.log", method, t)) return shared.CopyFile(newPath, filepath.Join(imagesDir, fmt.Sprintf("%s.log", method))) }
/* * This is called by lxd when called as "lxd forkexec <container>" */ func execContainer(args []string) (int, error) { if len(args) < 6 { return -1, fmt.Errorf("Bad arguments: %q", args) } name := args[1] lxcpath := args[2] configPath := args[3] c, err := lxc.NewContainer(name, lxcpath) if err != nil { return -1, fmt.Errorf("Error initializing container for start: %q", err) } err = c.LoadConfigFile(configPath) if err != nil { return -1, fmt.Errorf("Error opening startup config file: %q", err) } syscall.Dup3(int(os.Stdin.Fd()), 200, 0) syscall.Dup3(int(os.Stdout.Fd()), 201, 0) syscall.Dup3(int(os.Stderr.Fd()), 202, 0) syscall.Close(int(os.Stdin.Fd())) syscall.Close(int(os.Stdout.Fd())) syscall.Close(int(os.Stderr.Fd())) opts := lxc.DefaultAttachOptions opts.ClearEnv = true opts.StdinFd = 200 opts.StdoutFd = 201 opts.StderrFd = 202 logPath := shared.LogPath(name, "forkexec.log") if shared.PathExists(logPath) { os.Remove(logPath) } logFile, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0644) if err == nil { syscall.Dup3(int(logFile.Fd()), 1, 0) syscall.Dup3(int(logFile.Fd()), 2, 0) } env := []string{} cmd := []string{} section := "" for _, arg := range args[5:len(args)] { // The "cmd" section must come last as it may contain a -- if arg == "--" && section != "cmd" { section = "" continue } if section == "" { section = arg continue } if section == "env" { fields := strings.SplitN(arg, "=", 2) if len(fields) == 2 && fields[0] == "HOME" { opts.Cwd = fields[1] } env = append(env, arg) } else if section == "cmd" { cmd = append(cmd, arg) } else { return -1, fmt.Errorf("Invalid exec section: %s", section) } } opts.Env = env status, err := c.RunCommandStatus(cmd, opts) if err != nil { return -1, fmt.Errorf("Failed running command: %q", err) } return status >> 8, nil }