// getArgsEnv returns the nspawn or lkvm args and env according to the flavor used func getArgsEnv(p *stage1commontypes.Pod, flavor string, debug bool, n *networking.Networking) ([]string, []string, error) { var args []string env := os.Environ() // We store the pod's flavor so we can later garbage collect it correctly if err := os.Symlink(flavor, filepath.Join(p.Root, stage1initcommon.FlavorFile)); err != nil { return nil, nil, fmt.Errorf("failed to create flavor symlink: %v", err) } switch flavor { case "kvm": if privateUsers != "" { return nil, nil, fmt.Errorf("flag --private-users cannot be used with an lkvm stage1") } // kernel and lkvm are relative path, because init has /var/lib/rkt/..../uuid as its working directory // TODO: move to path.go kernelPath := filepath.Join(common.Stage1RootfsPath(p.Root), "bzImage") lkvmPath := filepath.Join(common.Stage1RootfsPath(p.Root), "lkvm") netDescriptions := kvm.GetNetworkDescriptions(n) lkvmNetArgs, err := kvm.GetKVMNetArgs(netDescriptions) if err != nil { return nil, nil, err } cpu, mem := kvm.GetAppsResources(p.Manifest.Apps) kernelParams := []string{ "console=hvc0", "init=/usr/lib/systemd/systemd", "no_timer_check", "noreplace-smp", "systemd.default_standard_error=journal+console", "systemd.default_standard_output=journal+console", // "systemd.default_standard_output=tty", "tsc=reliable", "MACHINEID=" + p.UUID.String(), } if debug { kernelParams = append(kernelParams, []string{ "debug", "systemd.log_level=debug", "systemd.show_status=true", // "systemd.confirm_spawn=true", }...) } else { kernelParams = append(kernelParams, "quiet") } args = append(args, []string{ "./" + lkvmPath, // relative path "run", "--name", "rkt-" + p.UUID.String(), "--no-dhcp", // speed bootup "--cpu", strconv.FormatInt(cpu, 10), "--mem", strconv.FormatInt(mem, 10), "--console=virtio", "--kernel", kernelPath, "--disk", "stage1/rootfs", // relative to run/pods/uuid dir this is a place where systemd resides // MACHINEID will be available as environment variable "--params", strings.Join(kernelParams, " "), }..., ) args = append(args, lkvmNetArgs...) if debug { args = append(args, "--debug") } // host volume sharing with 9p nsargs := stage1initcommon.VolumesToKvmDiskArgs(p.Manifest.Volumes) args = append(args, nsargs...) // lkvm requires $HOME to be defined, // see https://github.com/coreos/rkt/issues/1393 if os.Getenv("HOME") == "" { env = append(env, "HOME=/root") } return args, env, nil case "coreos": args = append(args, filepath.Join(common.Stage1RootfsPath(p.Root), interpBin)) args = append(args, filepath.Join(common.Stage1RootfsPath(p.Root), nspawnBin)) args = append(args, "--boot") // Launch systemd in the pod if context := os.Getenv(common.EnvSELinuxContext); context != "" { args = append(args, fmt.Sprintf("-Z%s", context)) } if machinedRegister() { args = append(args, fmt.Sprintf("--register=true")) } else { args = append(args, fmt.Sprintf("--register=false")) } // use only dynamic libraries provided in the image env = append(env, "LD_LIBRARY_PATH="+filepath.Join(common.Stage1RootfsPath(p.Root), "usr/lib")) case "src": args = append(args, filepath.Join(common.Stage1RootfsPath(p.Root), nspawnBin)) args = append(args, "--boot") // Launch systemd in the pod if context := os.Getenv(common.EnvSELinuxContext); context != "" { args = append(args, fmt.Sprintf("-Z%s", context)) } if machinedRegister() { args = append(args, fmt.Sprintf("--register=true")) } else { args = append(args, fmt.Sprintf("--register=false")) } case "host": hostNspawnBin, err := lookupPath("systemd-nspawn", os.Getenv("PATH")) if err != nil { return nil, nil, err } // Check dynamically which version is installed on the host // Support version >= 220 versionBytes, err := exec.Command(hostNspawnBin, "--version").CombinedOutput() if err != nil { return nil, nil, fmt.Errorf("unable to probe %s version: %v", hostNspawnBin, err) } versionStr := strings.SplitN(string(versionBytes), "\n", 2)[0] var version int n, err := fmt.Sscanf(versionStr, "systemd %d", &version) if err != nil { return nil, nil, fmt.Errorf("cannot parse version: %q", versionStr) } if n != 1 || version < 220 { return nil, nil, fmt.Errorf("rkt needs systemd-nspawn >= 220. %s version not supported: %v", hostNspawnBin, versionStr) } // Copy systemd, bash, etc. in stage1 at run-time if err := installAssets(); err != nil { return nil, nil, fmt.Errorf("cannot install assets from the host: %v", err) } args = append(args, hostNspawnBin) args = append(args, "--boot") // Launch systemd in the pod args = append(args, fmt.Sprintf("--register=true")) if context := os.Getenv(common.EnvSELinuxContext); context != "" { args = append(args, fmt.Sprintf("-Z%s", context)) } default: return nil, nil, fmt.Errorf("unrecognized stage1 flavor: %q", flavor) } // link journal only if the host is running systemd if util.IsRunningSystemd() { // we write /etc/machine-id here because systemd-nspawn needs it to link // the container's journal to the host mPath := filepath.Join(common.Stage1RootfsPath(p.Root), "etc", "machine-id") mID := strings.Replace(p.UUID.String(), "-", "", -1) if err := ioutil.WriteFile(mPath, []byte(mID), 0644); err != nil { log.Fatalf("error writing /etc/machine-id: %v\n", err) } args = append(args, "--link-journal=try-guest") keepUnit, err := util.RunningFromSystemService() if err != nil { if err == util.ErrSoNotFound { fmt.Fprintln(os.Stderr, "Warning: libsystemd not found even though systemd is running. Cgroup limits set by the environment (e.g. a systemd service) won't be enforced.") } else { return nil, nil, fmt.Errorf("error determining if we're running from a system service: %v", err) } } if keepUnit { args = append(args, "--keep-unit") } } if !debug { args = append(args, "--quiet") // silence most nspawn output (log_warning is currently not covered by this) env = append(env, "SYSTEMD_LOG_LEVEL=err") // silence log_warning too } env = append(env, "SYSTEMD_NSPAWN_CONTAINER_SERVICE=rkt") if len(privateUsers) > 0 { args = append(args, "--private-users="+privateUsers) } nsargs, err := stage1initcommon.PodToNspawnArgs(p) if err != nil { return nil, nil, fmt.Errorf("failed to generate nspawn args: %v", err) } args = append(args, nsargs...) // Arguments to systemd args = append(args, "--") args = append(args, "--default-standard-output=tty") // redirect all service logs straight to tty if !debug { args = append(args, "--log-target=null") // silence systemd output inside pod // TODO remove --log-level=warning when we update stage1 to systemd v222 args = append(args, "--log-level=warning") // limit log output (systemd-shutdown ignores --log-target) args = append(args, "--show-status=0") // silence systemd initialization status output } return args, env, nil }
func TestServiceFile(t *testing.T) { if !sd_util.IsRunningSystemd() { t.Skip("Systemd is not running on the host.") } ctx := newRktRunCtx() defer ctx.cleanup() r := rand.New(rand.NewSource(time.Now().UnixNano())) conn, err := sd_dbus.New() if err != nil { t.Fatal(err) } imageFile := os.Getenv("RKT_INSPECT_IMAGE") if imageFile == "" { t.Fatal("RKT_INSPECT_IMAGE is not set") } image, err := filepath.Abs(imageFile) if err != nil { t.Fatal(err) } opts := "-- --print-msg=HelloWorld --sleep=1000" cmd := fmt.Sprintf("%s --insecure-skip-verify run --mds-register=false --set-env=MESSAGE_LOOP=1000 %s %s", ctx.cmd(), image, opts) props := []sd_dbus.Property{ sd_dbus.PropExecStart(strings.Split(cmd, " "), false), } target := fmt.Sprintf("rkt-testing-transient-%d.service", r.Int()) reschan := make(chan string) _, err = conn.StartTransientUnit(target, "replace", props, reschan) if err != nil { t.Fatal(err) } job := <-reschan if job != "done" { t.Fatal("Job is not done:", job) } units, err := conn.ListUnits() var found bool for _, u := range units { if u.Name == target { found = true if u.ActiveState != "active" { t.Fatalf("Test unit %s not active: %s (target: %s)", u.Name, u.ActiveState, target) } } } if !found { t.Fatalf("Test unit not found in list") } // Run the unit for 10 seconds. You can check the logs manually in journalctl time.Sleep(10 * time.Second) // Stop the unit _, err = conn.StopUnit(target, "replace", reschan) if err != nil { t.Fatal(err) } // wait for StopUnit job to complete <-reschan units, err = conn.ListUnits() found = false for _, u := range units { if u.Name == target { found = true } } if found { t.Fatalf("Test unit found in list, should be stopped") } }
func TestSocketActivation(t *testing.T) { if !sd_util.IsRunningSystemd() { t.Skip("Systemd is not running on the host.") } r := rand.New(rand.NewSource(time.Now().UnixNano())) port, err := randomFreePort(t) if err != nil { t.Fatal(err) } echoImage := patchTestACI("rkt-inspect-echo.aci", "--exec=/echo-socket-activated", fmt.Sprintf("--ports=%d-tcp,protocol=tcp,port=%d,socketActivated=true", port, port)) defer os.Remove(echoImage) ctx := testutils.NewRktRunCtx() defer ctx.Cleanup() conn, err := sd_dbus.New() if err != nil { t.Fatal(err) } rktTestingEchoService := ` [Unit] Description=Socket-activated echo server [Service] ExecStart=%s KillMode=process ` rnd := r.Int() // Write unit files directly to runtime system units directory // (/run/systemd/system) to avoid calling LinkUnitFiles - it is buggy in // systemd v219 as it does not work with absolute paths. unitsDir := "/run/systemd/system" cmd := fmt.Sprintf("%s --insecure-skip-verify run --mds-register=false %s", ctx.Cmd(), echoImage) serviceContent := fmt.Sprintf(rktTestingEchoService, cmd) serviceTargetBase := fmt.Sprintf("rkt-testing-socket-activation-%d.service", rnd) serviceTarget := filepath.Join(unitsDir, serviceTargetBase) if err := ioutil.WriteFile(serviceTarget, []byte(serviceContent), 0666); err != nil { t.Fatal(err) } defer os.Remove(serviceTarget) rktTestingEchoSocket := ` [Unit] Description=Socket-activated netcat server socket [Socket] ListenStream=%d ` socketContent := fmt.Sprintf(rktTestingEchoSocket, port) socketTargetBase := fmt.Sprintf("rkt-testing-socket-activation-%d.socket", rnd) socketTarget := filepath.Join(unitsDir, socketTargetBase) if err := ioutil.WriteFile(socketTarget, []byte(socketContent), 0666); err != nil { t.Fatal(err) } defer os.Remove(socketTarget) reschan := make(chan string) doJob := func() { job := <-reschan if job != "done" { t.Fatal("Job is not done:", job) } } if _, err := conn.StartUnit(socketTargetBase, "replace", reschan); err != nil { t.Fatal(err) } doJob() defer func() { if _, err := conn.StopUnit(socketTargetBase, "replace", reschan); err != nil { t.Fatal(err) } doJob() if _, err := conn.StopUnit(serviceTargetBase, "replace", reschan); err != nil { t.Fatal(err) } doJob() }() expected := "HELO\n" sockConn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port)) if err != nil { t.Fatal(err) } if _, err := fmt.Fprintf(sockConn, expected); err != nil { t.Fatal(err) } answer, err := bufio.NewReader(sockConn).ReadString('\n') if err != nil { t.Fatal(err) } if answer != expected { t.Fatalf("Expected %q, Got %q", expected, answer) } return }
// getArgsEnv returns the nspawn args and env according to the usr used func getArgsEnv(p *Pod, flavor string, debug bool, n *networking.Networking) ([]string, []string, error) { args := []string{} env := os.Environ() switch flavor { case "kvm": // kernel and lkvm are relative path, because init has /var/lib/rkt/..../uuid as its working directory // TODO: move to path.go kernelPath := filepath.Join(common.Stage1RootfsPath(p.Root), "bzImage") lkvmPath := filepath.Join(common.Stage1RootfsPath(p.Root), "lkvm") lkvmNetArgs, kernelNetParams, err := kvm.GetKVMNetArgs(n) if err != nil { return nil, nil, err } // TODO: base on resource isolators cpu := 1 mem := 128 kernelParams := []string{ "console=hvc0", "init=/usr/lib/systemd/systemd", "no_timer_check", "noreplace-smp", "systemd.default_standard_error=journal+console", "systemd.default_standard_output=journal+console", strings.Join(kernelNetParams, " "), // "systemd.default_standard_output=tty", "tsc=reliable", "MACHINEID=" + p.UUID.String(), } if debug { kernelParams = append(kernelParams, []string{ "debug", "systemd.log_level=debug", "systemd.show_status=true", // "systemd.confirm_spawn=true", }...) } else { kernelParams = append(kernelParams, "quiet") } args = append(args, []string{ "./" + lkvmPath, // relative path "run", "--name", "rkt-" + p.UUID.String(), "--no-dhcp", // speed bootup "--cpu", strconv.Itoa(cpu), "--mem", strconv.Itoa(mem), "--console=virtio", "--kernel", kernelPath, "--disk", "stage1/rootfs", // relative to run/pods/uuid dir this is a place where systemd resides // MACHINEID will be available as environment variable "--params", strings.Join(kernelParams, " "), }..., ) args = append(args, lkvmNetArgs...) if debug { args = append(args, "--debug") } // host volume sharing with 9p nsargs := kvm.VolumesToKvmDiskArgs(p.Manifest.Volumes) args = append(args, nsargs...) return args, env, nil case "coreos": args = append(args, filepath.Join(common.Stage1RootfsPath(p.Root), interpBin)) args = append(args, filepath.Join(common.Stage1RootfsPath(p.Root), nspawnBin)) args = append(args, "--boot") // Launch systemd in the pod if machinedRegister() { args = append(args, fmt.Sprintf("--register=true")) } else { args = append(args, fmt.Sprintf("--register=false")) } // use only dynamic libraries provided in the image env = append(env, "LD_LIBRARY_PATH="+filepath.Join(common.Stage1RootfsPath(p.Root), "usr/lib")) case "src": args = append(args, filepath.Join(common.Stage1RootfsPath(p.Root), nspawnBin)) args = append(args, "--boot") // Launch systemd in the pod if machinedRegister() { args = append(args, fmt.Sprintf("--register=true")) } else { args = append(args, fmt.Sprintf("--register=false")) } case "host": hostNspawnBin, err := lookupPath("systemd-nspawn", os.Getenv("PATH")) if err != nil { return nil, nil, err } // Check dynamically which version is installed on the host // Support version >= 220 versionBytes, err := exec.Command(hostNspawnBin, "--version").CombinedOutput() if err != nil { return nil, nil, fmt.Errorf("unable to probe %s version: %v", hostNspawnBin, err) } versionStr := strings.SplitN(string(versionBytes), "\n", 2)[0] var version int n, err := fmt.Sscanf(versionStr, "systemd %d", &version) if err != nil { return nil, nil, fmt.Errorf("cannot parse version: %q", versionStr) } if n != 1 || version < 220 { return nil, nil, fmt.Errorf("rkt needs systemd-nspawn >= 220. %s version not supported: %v", hostNspawnBin, versionStr) } // Copy systemd, bash, etc. in stage1 at run-time if err := installAssets(); err != nil { return nil, nil, fmt.Errorf("cannot install assets from the host: %v", err) } args = append(args, hostNspawnBin) args = append(args, "--boot") // Launch systemd in the pod args = append(args, fmt.Sprintf("--register=true")) default: return nil, nil, fmt.Errorf("unrecognized stage1 flavor: %q", flavor) } // link journal only if the host is running systemd if util.IsRunningSystemd() { // we write /etc/machine-id here because systemd-nspawn needs it to link // the container's journal to the host mPath := filepath.Join(common.Stage1RootfsPath(p.Root), "etc", "machine-id") mId := strings.Replace(p.UUID.String(), "-", "", -1) if err := ioutil.WriteFile(mPath, []byte(mId), 0644); err != nil { log.Fatalf("error writing /etc/machine-id: %v\n", err) } args = append(args, "--link-journal=try-guest") } if !debug { args = append(args, "--quiet") // silence most nspawn output (log_warning is currently not covered by this) env = append(env, "SYSTEMD_LOG_LEVEL=err") // silence log_warning too } keepUnit, err := isRunningFromUnitFile() if err != nil { return nil, nil, fmt.Errorf("error determining if we're running from a unit file: %v", err) } if keepUnit { args = append(args, "--keep-unit") } nsargs, err := p.PodToNspawnArgs() if err != nil { return nil, nil, fmt.Errorf("Failed to generate nspawn args: %v", err) } args = append(args, nsargs...) // Arguments to systemd args = append(args, "--") args = append(args, "--default-standard-output=tty") // redirect all service logs straight to tty if !debug { args = append(args, "--log-target=null") // silence systemd output inside pod // TODO remove --log-level=warning when we update stage1 to systemd v222 args = append(args, "--log-level=warning") // limit log output (systemd-shutdown ignores --log-target) args = append(args, "--show-status=0") // silence systemd initialization status output } return args, env, nil }
// getArgsEnv returns the nspawn args and env according to the usr used func getArgsEnv(p *Pod, flavor string, systemdStage1Version string, debug bool) ([]string, []string, error) { args := []string{} env := os.Environ() switch flavor { case "coreos": args = append(args, filepath.Join(common.Stage1RootfsPath(p.Root), interpBin)) args = append(args, filepath.Join(common.Stage1RootfsPath(p.Root), nspawnBin)) args = append(args, "--boot") // Launch systemd in the pod if machinedRegister() { args = append(args, fmt.Sprintf("--register=true")) } else { args = append(args, fmt.Sprintf("--register=false")) } // use only dynamic libraries provided in the image env = append(env, "LD_LIBRARY_PATH="+filepath.Join(common.Stage1RootfsPath(p.Root), "usr/lib")) case "src": args = append(args, filepath.Join(common.Stage1RootfsPath(p.Root), nspawnBin)) args = append(args, "--boot") // Launch systemd in the pod switch systemdStage1Version { case "v215": lfd, err := common.GetRktLockFD() if err != nil { return nil, nil, err } args = append(args, fmt.Sprintf("--keep-fd=%v", lfd)) case "v219": // --keep-fd is not needed thanks to // stage1/rootfs/usr_from_src/patches/v219/0005-nspawn-close-extra-fds-before-execing-init.patch default: // since systemd-nspawn v220 (commit 6b7d2e, "nspawn: close extra fds // before execing init"), fds remain open, so --keep-fd is not needed. } if machinedRegister() { args = append(args, fmt.Sprintf("--register=true")) } else { args = append(args, fmt.Sprintf("--register=false")) } case "host": hostNspawnBin, err := lookupPath("systemd-nspawn", os.Getenv("PATH")) if err != nil { return nil, nil, err } // Check dynamically which version is installed on the host // Support version >= 220 versionBytes, err := exec.Command(hostNspawnBin, "--version").CombinedOutput() if err != nil { return nil, nil, fmt.Errorf("unable to probe %s version: %v", hostNspawnBin, err) } versionStr := strings.SplitN(string(versionBytes), "\n", 2)[0] var version int n, err := fmt.Sscanf(versionStr, "systemd %d", &version) if err != nil { return nil, nil, fmt.Errorf("cannot parse version: %q", versionStr) } if n != 1 || version < 220 { return nil, nil, fmt.Errorf("rkt needs systemd-nspawn >= 220. %s version not supported: %v", hostNspawnBin, versionStr) } // Copy systemd, bash, etc. in stage1 at run-time if err := installAssets(); err != nil { return nil, nil, fmt.Errorf("cannot install assets from the host: %v", err) } args = append(args, hostNspawnBin) args = append(args, "--boot") // Launch systemd in the pod args = append(args, fmt.Sprintf("--register=true")) default: return nil, nil, fmt.Errorf("unrecognized stage1 flavor: %q", flavor) } // link journal only if the host is running systemd and stage1 supports // linking if util.IsRunningSystemd() && systemdSupportsJournalLinking(systemdStage1Version) { // we write /etc/machine-id here because systemd-nspawn needs it to link // the container's journal to the host mPath := filepath.Join(common.Stage1RootfsPath(p.Root), "etc", "machine-id") mId := strings.Replace(p.UUID.String(), "-", "", -1) if err := ioutil.WriteFile(mPath, []byte(mId), 0644); err != nil { log.Fatalf("error writing /etc/machine-id: %v\n", err) } args = append(args, "--link-journal=try-guest") } if !debug { args = append(args, "--quiet") // silence most nspawn output (log_warning is currently not covered by this) env = append(env, "SYSTEMD_LOG_LEVEL=err") // silence log_warning too } keepUnit, err := isRunningFromUnitFile() if err != nil { return nil, nil, fmt.Errorf("error determining if we're running from a unit file: %v", err) } if keepUnit { args = append(args, "--keep-unit") } nsargs, err := p.PodToNspawnArgs() if err != nil { return nil, nil, fmt.Errorf("Failed to generate nspawn args: %v", err) } args = append(args, nsargs...) // Arguments to systemd args = append(args, "--") args = append(args, "--default-standard-output=tty") // redirect all service logs straight to tty if !debug { args = append(args, "--log-target=null") // silence systemd output inside pod // TODO remove --log-level=warning when we update stage1 to systemd v222 args = append(args, "--log-level=warning") // limit log output (systemd-shutdown ignores --log-target) args = append(args, "--show-status=0") // silence systemd initialization status output } return args, env, nil }