Thanks to visit codestin.com
Credit goes to github.com

Skip to content
This repository was archived by the owner on Feb 24, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#### New features and UX changes

- Add support for non-numerical UID/GID as specified in the appc spec ([#2159](https://github.com/coreos/rkt/pull/2159)).
- When an application terminates with a non-zero exit status, `rkt run` now returns that exit status ([#2198](https://github.com/coreos/rkt/pull/2198)). This requires [systemd >= v227](https://lists.freedesktop.org/archives/systemd-devel/2015-October/034509.html) in stage1. systemd-v229 can now be used in the [src and host flavors](https://github.com/coreos/rkt/blob/master/Documentation/build-configure.md#--with-stage1-flavors) but this is not yet available in the default coreos flavor.

#### Bug fixes
- Socket activation was not working if the port on the host is different from the app port as set in the image manifest ([#2137](https://github.com/coreos/rkt/pull/2137)).
Expand Down
1 change: 1 addition & 0 deletions Documentation/devel/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ This means that when an app service is stopped, its associated reaper will run a
When all apps' services stop, their associated reaper services will also stop and will cease referencing the shutdown service causing the pod to exit.
Every app service has an [*OnFailure*](http://www.freedesktop.org/software/systemd/man/systemd.unit.html#OnFailure=) flag that starts the `halt.target`.
This means that if any app in the pod exits with a failed status, the systemd shutdown process will start, the other apps' services will automatically stop and the pod will exit.
In this case, the failed app's exit status will get propagated to rkt.

A [*Conflicts*](http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Conflicts=) dependency was also added between each reaper service and the halt and poweroff targets (they are triggered when the pod is stopped from the outside when rkt receives `SIGINT`).
This will activate all the reaper services when one of the targets is activated, causing the exit statuses to be saved and the pod to finish like it was described in the previous paragraph.
Expand Down
39 changes: 39 additions & 0 deletions common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (

"github.com/appc/spec/aci"
"github.com/appc/spec/schema/types"
"github.com/hashicorp/errwrap"
)

const (
Expand Down Expand Up @@ -274,3 +275,41 @@ func (l *NetList) SpecificArgs(net string) string {
func (l *NetList) All() bool {
return l.Specific("all")
}

// LookupPath search for bin in paths. If found, it returns its absolute path,
// if not, an error
func LookupPath(bin string, paths string) (string, error) {
pathsArr := filepath.SplitList(paths)
for _, path := range pathsArr {
binPath := filepath.Join(path, bin)
binAbsPath, err := filepath.Abs(binPath)
if err != nil {
return "", fmt.Errorf("unable to find absolute path for %s", binPath)
}
d, err := os.Stat(binAbsPath)
if err != nil {
continue
}
// Check the executable bit, inspired by os.exec.LookPath()
if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
return binAbsPath, nil
}
}
return "", fmt.Errorf("unable to find %q in %q", bin, paths)
}

// SystemdVersion parses and returns the version of a given systemd binary
func SystemdVersion(systemdBinaryPath string) (int, error) {
versionBytes, err := exec.Command(systemdBinaryPath, "--version").CombinedOutput()
if err != nil {
return -1, errwrap.Wrap(fmt.Errorf("unable to probe %s version", systemdBinaryPath), err)
}
versionStr := strings.SplitN(string(versionBytes), "\n", 2)[0]
var version int
n, err := fmt.Sscanf(versionStr, "systemd %d", &version)
if err != nil || n != 1 {
return -1, fmt.Errorf("cannot parse version: %q", versionStr)
}

return version, nil
}
91 changes: 80 additions & 11 deletions stage1/init/common/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,43 @@ func appToSystemd(p *stage1commontypes.Pod, ra *schema.RuntimeApp, interactive b
return nil
}

func writeShutdownService(p *stage1commontypes.Pod) error {
_, systemdVersion, err := GetFlavor(p)
if err != nil {
return err
}

opts := []*unit.UnitOption{
unit.NewUnitOption("Unit", "Description", "Pod shutdown"),
unit.NewUnitOption("Unit", "AllowIsolate", "true"),
unit.NewUnitOption("Unit", "StopWhenUnneeded", "yes"),
unit.NewUnitOption("Unit", "DefaultDependencies", "false"),
unit.NewUnitOption("Service", "RemainAfterExit", "yes"),
}

shutdownVerb := "exit"
// systemd <v227 doesn't allow the "exit" verb when running as PID 1, so
// use "halt".
if systemdVersion < 227 {
shutdownVerb = "halt"
}

opts = append(opts, unit.NewUnitOption("Service", "ExecStop", fmt.Sprintf("/usr/bin/systemctl --force %s", shutdownVerb)))

unitsPath := filepath.Join(common.Stage1RootfsPath(p.Root), UnitsDir)
file, err := os.OpenFile(filepath.Join(unitsPath, "shutdown.service"), os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return errwrap.Wrap(errors.New("failed to create unit file"), err)
}
defer file.Close()

if _, err = io.Copy(file, unit.Serialize(opts)); err != nil {
return errwrap.Wrap(errors.New("failed to write unit file"), err)
}

return nil
}

// writeEnvFile creates an environment file for given app name, the minimum
// required environment variables by the appc spec will be set to sensible
// defaults here if they're not provided by env.
Expand Down Expand Up @@ -508,13 +545,17 @@ func writeEnvFile(p *stage1commontypes.Pod, env types.Environment, appName types
// PodToSystemd creates the appropriate systemd service unit files for
// all the constituent apps of the Pod
func PodToSystemd(p *stage1commontypes.Pod, interactive bool, flavor string, privateUsers string) error {

for i := range p.Manifest.Apps {
ra := &p.Manifest.Apps[i]
if err := appToSystemd(p, ra, interactive, flavor, privateUsers); err != nil {
return errwrap.Wrap(fmt.Errorf("failed to transform app %q into systemd service", ra.Name), err)
}
}

if err := writeShutdownService(p); err != nil {
return errwrap.Wrap(errors.New("failed to write shutdown service"), err)
}

return nil
}

Expand Down Expand Up @@ -542,10 +583,21 @@ func appToNspawnArgs(p *stage1commontypes.Pod, ra *schema.RuntimeApp) ([]string,
for _, m := range mounts {
vol := vols[m.Volume]

//set volume permissions
volumePath := filepath.Join(sharedVolPath, vol.Name.String())
if err := PrepareMountpoints(volumePath, &vol); err != nil {
return nil, err
if vol.Kind == "empty" {
p := filepath.Join(sharedVolPath, vol.Name.String())
if err := os.MkdirAll(p, sharedVolPerm); err != nil {
return nil, errwrap.Wrap(fmt.Errorf("could not create shared volume %q", vol.Name), err)
}
if err := os.Chown(p, *vol.UID, *vol.GID); err != nil {
return nil, errwrap.Wrap(fmt.Errorf("could not change owner of %q", p), err)
}
mod, err := strconv.ParseUint(*vol.Mode, 8, 32)
if err != nil {
return nil, errwrap.Wrap(fmt.Errorf("invalid mode %q for volume %q", *vol.Mode, vol.Name), err)
}
if err := os.Chmod(p, os.FileMode(mod)); err != nil {
return nil, errwrap.Wrap(fmt.Errorf("could not change permissions of %q", p), err)
}
}

opt := make([]string, 4)
Expand Down Expand Up @@ -613,22 +665,39 @@ func PodToNspawnArgs(p *stage1commontypes.Pod) ([]string, error) {
}

// GetFlavor populates a flavor string based on the flavor itself and respectively the systemd version
func GetFlavor(p *stage1commontypes.Pod) (flavor string, systemdVersion string, err error) {
func GetFlavor(p *stage1commontypes.Pod) (flavor string, systemdVersion int, err error) {
flavor, err = os.Readlink(filepath.Join(common.Stage1RootfsPath(p.Root), "flavor"))
if err != nil {
return "", "", errwrap.Wrap(errors.New("unable to determine stage1 flavor"), err)
return "", -1, errwrap.Wrap(errors.New("unable to determine stage1 flavor"), err)
}

if flavor == "host" {
// This flavor does not contain systemd, so don't return systemdVersion
return flavor, "", nil
// This flavor does not contain systemd, parse "systemctl --version"
systemctlBin, err := common.LookupPath("systemctl", os.Getenv("PATH"))
if err != nil {
return "", -1, err
}

systemdVersion, err := common.SystemdVersion(systemctlBin)
if err != nil {
return "", -1, errwrap.Wrap(errors.New("error finding systemctl version"), err)
}

return flavor, systemdVersion, nil
}

systemdVersionBytes, err := ioutil.ReadFile(filepath.Join(common.Stage1RootfsPath(p.Root), "systemd-version"))
if err != nil {
return "", "", errwrap.Wrap(errors.New("unable to determine stage1's systemd version"), err)
return "", -1, errwrap.Wrap(errors.New("unable to determine stage1's systemd version"), err)
}
systemdVersionString := strings.Trim(string(systemdVersionBytes), " \n")

// systemdVersionString is of the form v229, remove the first character to
// get the number
systemdVersion, err = strconv.Atoi(systemdVersionString[1:])
if err != nil {
return "", -1, errwrap.Wrap(errors.New("error parsing stage1's systemd version"), err)
}
systemdVersion = strings.Trim(string(systemdVersionBytes), " \n")
return flavor, systemdVersion, nil
}

Expand Down
32 changes: 6 additions & 26 deletions stage1/init/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,47 +158,27 @@ func machinedRegister() bool {
return found == 2
}

func lookupPath(bin string, paths string) (string, error) {
pathsArr := filepath.SplitList(paths)
for _, path := range pathsArr {
binPath := filepath.Join(path, bin)
binAbsPath, err := filepath.Abs(binPath)
if err != nil {
return "", fmt.Errorf("unable to find absolute path for %s", binPath)
}
d, err := os.Stat(binAbsPath)
if err != nil {
continue
}
// Check the executable bit, inspired by os.exec.LookPath()
if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
return binAbsPath, nil
}
}
return "", fmt.Errorf("unable to find %q in %q", bin, paths)
}

func installAssets() error {
systemctlBin, err := lookupPath("systemctl", os.Getenv("PATH"))
systemctlBin, err := common.LookupPath("systemctl", os.Getenv("PATH"))
if err != nil {
return err
}
bashBin, err := lookupPath("bash", os.Getenv("PATH"))
bashBin, err := common.LookupPath("bash", os.Getenv("PATH"))
if err != nil {
return err
}
// More paths could be added in that list if some Linux distributions install it in a different path
// Note that we look in /usr/lib/... first because of the merge:
// http://www.freedesktop.org/wiki/Software/systemd/TheCaseForTheUsrMerge/
systemdShutdownBin, err := lookupPath("systemd-shutdown", "/usr/lib/systemd:/lib/systemd")
systemdShutdownBin, err := common.LookupPath("systemd-shutdown", "/usr/lib/systemd:/lib/systemd")
if err != nil {
return err
}
systemdBin, err := lookupPath("systemd", "/usr/lib/systemd:/lib/systemd")
systemdBin, err := common.LookupPath("systemd", "/usr/lib/systemd:/lib/systemd")
if err != nil {
return err
}
systemdJournaldBin, err := lookupPath("systemd-journald", "/usr/lib/systemd:/lib/systemd")
systemdJournaldBin, err := common.LookupPath("systemd-journald", "/usr/lib/systemd:/lib/systemd")
if err != nil {
return err
}
Expand Down Expand Up @@ -347,7 +327,7 @@ func getArgsEnv(p *stage1commontypes.Pod, flavor string, debug bool, n *networki
}

case "host":
hostNspawnBin, err := lookupPath("systemd-nspawn", os.Getenv("PATH"))
hostNspawnBin, err := common.LookupPath("systemd-nspawn", os.Getenv("PATH"))
if err != nil {
return nil, nil, err
}
Expand Down
7 changes: 7 additions & 0 deletions stage1/reaper/reaper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,12 @@ if [ $# -eq 1 ]; then
app=$1
status=$(${SYSCTL} show --property ExecMainStatus "${app}.service")
echo "${status#*=}" > "/rkt/status/$app"
if [ "${status#*=}" != 0 ] ; then
# The command "systemctl exit $status" sets the return value that will
# be used when the pod exits (via shutdown.service).
# This command is available since systemd v227. On older versions, the
# command will fail and rkt will just exit with return code 0.
${SYSCTL} exit ${status#*=} 2>/dev/null
fi
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment?

# The command "systemctl exit $status" is available since systemd-v227. On older
# systemd, the command will fail and rkt will just exit with return code 0.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested on the current coreos flavor:

root@rkt-25122456-cb0d-4dae-bd21-905c754c22ed:/# exit 5
exit
Invalid number of arguments.

What about using:

${SYSCTL} exit ${status#*=} 2>/dev/null

I don't think we can nor should parse the output of systemctl --version since we don't have grep and other tools in stage1...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also say in the command that systemctl exit does not actually exit the pod right now, but merely update the return value in systemd that will be used when the pod exits later. The pod actually exits via shutdown.service.

Is it correct?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep

exit 0
fi
4 changes: 4 additions & 0 deletions stage1/units/units/exit.target
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[Unit]
Description=Exit the container
DefaultDependencies=no
AllowIsolate=yes
9 changes: 0 additions & 9 deletions stage1/units/units/shutdown.service

This file was deleted.

2 changes: 1 addition & 1 deletion tests/rkt_ace_validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func TestAceValidator(t *testing.T) {
rktCmd := fmt.Sprintf("%s %s", ctx.Cmd(), rktArgs)

child := spawnOrFail(t, rktCmd)
defer waitOrFail(t, child, true)
defer waitOrFail(t, child, 0)

for _, set := range expected {
for len(set) > 0 {
Expand Down
2 changes: 1 addition & 1 deletion tests/rkt_api_service_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func launchPods(ctx *testutils.RktRunCtx, numOfPods int, imagePath string) {
wg.Add(numOfPods)
for i := 0; i < numOfPods; i++ {
go func() {
spawnAndWaitOrFail(t, cmd, true)
spawnAndWaitOrFail(t, cmd, 0)
wg.Done()
}()
}
Expand Down
4 changes: 2 additions & 2 deletions tests/rkt_api_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func stopAPIService(t *testing.T, svc *gexpect.ExpectSubprocess) {
if err := svc.Cmd.Process.Signal(syscall.SIGINT); err != nil {
t.Fatalf("Failed to stop the api service: %v", err)
}
waitOrFail(t, svc, true)
waitOrFail(t, svc, 0)
}

func checkPodState(t *testing.T, rawState string, apiState v1alpha.PodState) {
Expand Down Expand Up @@ -296,7 +296,7 @@ func TestAPIServiceListInspectPods(t *testing.T) {

runCmd := fmt.Sprintf("%s run --pod-manifest=%s", ctx.Cmd(), manifestFile)
esp := spawnOrFail(t, runCmd)
waitOrFail(t, esp, true)
waitOrFail(t, esp, 0)

// ListPods(detail=false).
resp, err = c.ListPods(context.Background(), &v1alpha.ListPodsRequest{})
Expand Down
4 changes: 2 additions & 2 deletions tests/rkt_caps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func TestCaps(t *testing.T) {
if err := expectWithOutput(child, "User: uid=0 euid=0 gid=0 egid=0"); err != nil {
t.Fatalf("Expected user 0 but not found: %v", err)
}
waitOrFail(t, child, true)
waitOrFail(t, child, 0)
}
ctx.Reset()
}
Expand Down Expand Up @@ -146,7 +146,7 @@ func TestCapsNonRoot(t *testing.T) {
t.Fatalf("Expected user 9000 but not found: %v", err)
}

waitOrFail(t, child, true)
waitOrFail(t, child, 0)
ctx.Reset()
}
}
4 changes: 2 additions & 2 deletions tests/rkt_env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func TestEnv(t *testing.T) {
t.Fatalf("Expected %q but not found: %v", tt.runExpect, err)
}

waitOrFail(t, enterChild, true)
waitOrFail(t, enterChild, 0)

if err := child.SendLine("Bye"); err != nil {
t.Fatalf("rkt couldn't write to the container: %v", err)
Expand All @@ -116,7 +116,7 @@ func TestEnv(t *testing.T) {
t.Fatalf("Expected Bye but not found #%v: %v", i, err)
}

waitOrFail(t, child, true)
waitOrFail(t, child, 0)
ctx.Reset()
}
}
Loading