Skip to content
Closed
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
85 changes: 85 additions & 0 deletions cmd/oci-lifecycle-demo/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package main

import (
"fmt"

l "github.com/opencontainers/runtime-tools/lifecycle"
)

func TestDemo() {
runtime := "runc"

type ActionCase struct {
action l.LifecycleAction
args []string
expected bool
}
cases := []struct {
bundle string
id string
actions []ActionCase
}{
{"noexist_bundle", "1", []ActionCase{
{action: l.LifecycleCreate, expected: false}},
},
{"exist_bundle", "1", []ActionCase{
{action: l.LifecycleCreate, expected: true}},
},
{"exist_bundle", "1", []ActionCase{
{action: l.LifecycleCreate, expected: true},
{action: l.LifecycleCreate, expected: false}},
},
{"exist_bundle_run_err", "1", []ActionCase{
{action: l.LifecycleCreate, expected: true},
{action: l.LifecycleStart, expected: false}},
Copy link
Contributor

Choose a reason for hiding this comment

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

The sharness analog to this is:

test_expect_success 'create fails when bundle does not exist' "
  test_expect_code 1 ${RUNTIME} create --bundle noexist_bundle 1
"

test_expect_success 'create/kill/destroy succeeds when bundle exists' "
  subreaper-wrapper ${RUNTIME} create --bundle exist_bundle 1 &
  while test ! -e 1-created; do sleep 1; done &&  # this would be easier with runtime-spec#508, but works as a stop-gap with subreaper-wrapper touching this file when 'create' exits
  ${RUNTIME} kill 1 &&
  wait &&
  ${RUNTIME} delete 1 &&
"

test_expect_success 'second create call with same container ID fails' "
  subreaper-wrapper ${RUNTIME} create --bundle exist_bundle 1 &
  while test ! -e 1-created; do sleep 1; done &&  # this would be easier with runtime-spec#508, but works as a stop-gap with subreaper-wrapper touching this file when 'create' exits
  test_expect_code 1 ${RUNTIME} create --bundle exist_bundle 1
  ${RUNTIME} kill 1 &&
  wait &&
  ${RUNTIME} delete 1 &&
"

test_expect_success 'exist_bundle_run_err fails' "
  subreaper-wrapper ${RUNTIME} create --bundle exist_bundle_run_err 1 &
  while test ! -e 1-created; do sleep 1; done &&  # this would be easier with runtime-spec#508, but works as a stop-gap with subreaper-wrapper touching this file when 'create' exits
  test_expect_code 1 wait &&
  ${RUNTIME} delete 1 &&
"

That's a bit more complicated than your cases literal here, but:

  • There's no need to write code to iterate over the cases,
  • There's no need for an analog to your Lifecycle type,
  • You aren't killing successful creates before deleting them, and
  • You don't do any of the subreaper magic that you need if you want to see when/how the container process exits.

And I'm cutting corners by assuming that failing commands will exit 1 (opencontainers/runtime-spec#513 only requires non-zero). To do this right in sharness, I'd want to define a test_expect_code_not helper.

},
}
for _, c := range cases {
//TODO: Prepare c.bundle
lc, _ := l.NewLifecycle(runtime, c.bundle, c.id)
for _, a := range c.actions {
_, err := lc.Operate(a.action)
if err != nil && a.expected == true {
fmt.Printf("Failed in test")
break
} else if err == nil && a.expected == false {
fmt.Printf("Failed in test")
break
}
}

//TODO: Remove it anyway
lc.Operate(l.LifecycleDelete)
}
}

func main() {
runtime := "runc"
bundle := "bundle"
id := "25"
lc, _ := l.NewLifecycle(runtime, bundle, id)

fmt.Println("create")
out, err := lc.Operate(l.LifecycleCreate)
fmt.Println(string(out), err)

fmt.Println("state")
out, err = lc.Operate(l.LifecycleState)
fmt.Println(string(out), err)

fmt.Println("start")
out, err = lc.Operate(l.LifecycleStart)
fmt.Println(string(out), err)

fmt.Println("state")
out, _ = lc.Operate(l.LifecycleState)
fmt.Println(string(out))

fmt.Println("delete")
out, _ = lc.Operate(l.LifecycleDelete)
fmt.Println(string(out))

fmt.Println("state")
out, _ = lc.Operate(l.LifecycleState)
fmt.Println(string(out))
Copy link
Contributor

Choose a reason for hiding this comment

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

The sharness analog to this file is:

test_expect_success 'Demo lifecycle' "
  subreaper-wrapper ${RUNTIME} create --bundle bundle 25 &
  while test ! -e 25-created; do sleep 1; done &&  # this would be easier with runtime-spec#508, but works as a stop-gap with subreaper-wrapper touching this file when 'create' exits
  ${RUNTIME} state 25 &&
  ${RUNTIME} start 25 &&
  ${RUNTIME} state 25 &&
  ${RUNTIME} delete 25 &&
  ${RUNTIME} state 25 &&  # this line will probably fail.  I'm not sure if/how you handle that in your Go demo
  wait
"

I think that's more compact/obvious/flexible, although I haven't implemented subreaper-wrapper (although this PR does not setup subreaper handling either).

Copy link
Member Author

Choose a reason for hiding this comment

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

In the main function, the lifecycle lib is not that meanful.

I added a new demo pr. I hope it helps to make a clear and 'easy to extend' test frame.
Each bundle + ActionCase is a test case.
We can provide more bundles/actionCases to test a runtime.

}
63 changes: 63 additions & 0 deletions lifecycle/lifecycle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package lifecycle

import (
"errors"
"fmt"
"os/exec"
)

// LifecycleAction defines lifecycle action according to oci spec
type LifecycleAction string

// Define actions for Lifecycle
const (
LifecycleState LifecycleAction = "state"
LifecycleCreate LifecycleAction = "create"
LifecycleStart LifecycleAction = "start"
LifecycleKill LifecycleAction = "kill"
LifecycleDelete LifecycleAction = "delete"
)

// Lifecycle storages the runtime location, bundle path and the container id
type Lifecycle struct {
ID string
Runtime string
BundlePath string
}

// NewLifecycle creates a lifecycle with a runtime, bundle path and the container id
// no need to check the validate of bundlepath or id
func NewLifecycle(runtime, bundlePath, id string) (Lifecycle, error) {
var lc Lifecycle
lc.Runtime = runtime
lc.BundlePath = bundlePath
lc.ID = id
return lc, nil
}

// Operate provides lifecycle operations
func (lc *Lifecycle) Operate(action LifecycleAction, arg ...string) ([]byte, error) {
var cmd *exec.Cmd

switch action {
case LifecycleState:
cmd = exec.Command(lc.Runtime, string(action), lc.ID)
case LifecycleCreate:
// FIXME: '-b' is used in 'runc'
cmd = exec.Command(lc.Runtime, string(action), "-b", lc.BundlePath, lc.ID)
case LifecycleStart:
cmd = exec.Command(lc.Runtime, string(action), lc.ID)
case LifecycleKill:
if len(arg) != 1 {
return nil, errors.New("Should choose a 'signal' to kill a container")
}
cmd = exec.Command(lc.Runtime, string(action), lc.ID, arg[0])
case LifecycleDelete:
cmd = exec.Command(lc.Runtime, string(action), lc.ID)
default:
return nil, fmt.Errorf("'%s' is not supported", string(action))

}

return cmd.CombinedOutput()
}