Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial sketch of --exec #104

Merged
merged 5 commits into from
Jan 20, 2019
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
24 changes: 22 additions & 2 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

108 changes: 108 additions & 0 deletions chaoskube/action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package chaoskube

import (
"fmt"
"os"
"time"

"k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
)

type ChaosAction interface {
// Imbue chaos in the given victim
ApplyChaos(victim v1.Pod) error
// Name of this action, ideally a verb - like "terminate pod"
Name() string
}

func NewDryRunAction() ChaosAction {
return &dryRun{}
}

func NewDeletePodAction(client kubernetes.Interface, gracePeriod time.Duration) ChaosAction {
return &deletePod{client, gracePeriod}
}

func NewExecAction(client restclient.Interface, config *restclient.Config, containerName string, command []string) ChaosAction {
return &execOnPod{client, config, containerName, command}
}

// no-op
type dryRun struct {
}

func (s *dryRun) ApplyChaos(victim v1.Pod) error {
return nil
}
func (s *dryRun) Name() string { return "terminating pod" }

var _ ChaosAction = &dryRun{}

// Simply ask k8s to delete the victim pod
type deletePod struct {
client kubernetes.Interface
gracePeriod time.Duration
}

func (s *deletePod) ApplyChaos(victim v1.Pod) error {
return s.client.CoreV1().Pods(victim.Namespace).Delete(victim.Name, deleteOptions(s.gracePeriod))
}
func (s *deletePod) Name() string { return "terminating pod" }

var _ ChaosAction = &deletePod{}

// Execute the given command on victim pods
type execOnPod struct {
client restclient.Interface
config *restclient.Config

containerName string
command []string
}

// Based on https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/exec.go
func (s *execOnPod) ApplyChaos(pod v1.Pod) error {
var container string
if s.containerName != "" {
for _, c := range pod.Spec.Containers {
container = c.Name
}
}

req := s.client.Post().
Resource("pods").
Name(pod.Name).
Namespace(pod.Namespace).
SubResource("exec").
Param("container", container)
req.VersionedParams(&v1.PodExecOptions{
Container: container,
Command: s.command,
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)

exec, err := remotecommand.NewSPDYExecutor(s.config, "POST", req.URL())
if err != nil {
return err
}
// TODO: Collect stderr/stdout in RAM and log
err = exec.Stream(remotecommand.StreamOptions{
Stdin: nil,
Stdout: os.Stdout,
Stderr: os.Stderr,
Tty: false,
TerminalSizeQueue: nil,
})

return err
}
func (s *execOnPod) Name() string { return fmt.Sprintf("exec '%v'", s.command) }

var _ ChaosAction = &execOnPod{}
25 changes: 10 additions & 15 deletions chaoskube/chaoskube.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ type Chaoskube struct {
MinimumAge time.Duration
// an instance of logrus.StdLogger to write log messages to
Logger log.FieldLogger
// dry run will not allow any pod terminations
DryRun bool
// action taken against victim pods
Action ChaosAction
// create event with deletion message in victims namespace
CreateEvent bool
// grace period to terminate the pods
Expand Down Expand Up @@ -71,9 +71,9 @@ var (
// * a list of weekdays, times of day and/or days of a year when chaos mode is disabled
// * a time zone to apply to the aforementioned time-based filters
// * a logger implementing logrus.FieldLogger to send log output to
// * whether to enable/disable dry-run mode
// * what specific action to use to imbue chaos on victim pods
// * whether to enable/disable event creation
func New(client kubernetes.Interface, labels, annotations, namespaces labels.Selector, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, minimumAge time.Duration, logger log.FieldLogger, dryRun bool, createEvent bool, gracePeriod time.Duration) *Chaoskube {
func New(client kubernetes.Interface, labels, annotations, namespaces labels.Selector, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, minimumAge time.Duration, logger log.FieldLogger, action ChaosAction, createEvent bool) *Chaoskube {
return &Chaoskube{
Client: client,
Labels: labels,
Expand All @@ -85,9 +85,8 @@ func New(client kubernetes.Interface, labels, annotations, namespaces labels.Sel
Timezone: timezone,
MinimumAge: minimumAge,
Logger: logger,
DryRun: dryRun,
Action: action,
CreateEvent: createEvent,
GracePeriod: gracePeriod,
Now: time.Now,
}
}
Expand Down Expand Up @@ -144,7 +143,7 @@ func (c *Chaoskube) TerminateVictim() error {
return err
}

return c.DeletePod(victim)
return c.ApplyChaos(victim)
}

// Victim returns a random pod from the list of Candidates.
Expand Down Expand Up @@ -188,19 +187,15 @@ func (c *Chaoskube) Candidates() ([]v1.Pod, error) {
return pods, nil
}

// DeletePod deletes the given pod.
// ApplyChaos deletes the given pod.
// It will not delete the pod if dry-run mode is enabled.
func (c *Chaoskube) DeletePod(victim v1.Pod) error {
func (c *Chaoskube) ApplyChaos(victim v1.Pod) error {
c.Logger.WithFields(log.Fields{
"namespace": victim.Namespace,
"name": victim.Name,
}).Info("terminating pod")
}).Info(c.Action.Name())

if c.DryRun {
return nil
}

err := c.Client.CoreV1().Pods(victim.Namespace).Delete(victim.Name, deleteOptions(c.GracePeriod))
err := c.Action.ApplyChaos(victim)
if err != nil {
return err
}
Expand Down
24 changes: 15 additions & 9 deletions chaoskube/chaoskube_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (suite *Suite) TestNew() {
excludedTimesOfDay = []util.TimePeriod{util.TimePeriod{}}
excludedDaysOfYear = []time.Time{time.Now()}
minimumAge = time.Duration(42)
gracePeriod = 10 * time.Second
action = NewDeletePodAction(client, 10*time.Second)
)

chaoskube := New(
Expand All @@ -57,9 +57,8 @@ func (suite *Suite) TestNew() {
time.UTC,
minimumAge,
logger,
false,
action,
true,
gracePeriod,
)
suite.Require().NotNil(chaoskube)

Expand All @@ -73,8 +72,7 @@ func (suite *Suite) TestNew() {
suite.Equal(time.UTC, chaoskube.Timezone)
suite.Equal(minimumAge, chaoskube.MinimumAge)
suite.Equal(logger, chaoskube.Logger)
suite.Equal(false, chaoskube.DryRun)
suite.Equal(gracePeriod, chaoskube.GracePeriod)
suite.Equal(action, chaoskube.Action)
}

// TestRunContextCanceled tests that a canceled context will exit the Run function.
Expand Down Expand Up @@ -232,7 +230,7 @@ func (suite *Suite) TestDeletePod() {

victim := util.NewPod("default", "foo", v1.PodRunning)

err := chaoskube.DeletePod(victim)
err := chaoskube.ApplyChaos(victim)
suite.Require().NoError(err)

suite.assertLog(log.InfoLevel, "terminating pod", log.Fields{"namespace": "default", "name": "foo"})
Expand Down Expand Up @@ -596,8 +594,17 @@ func (suite *Suite) setupWithPods(labelSelector labels.Selector, annotations lab
func (suite *Suite) setup(labelSelector labels.Selector, annotations labels.Selector, namespaces labels.Selector, excludedWeekdays []time.Weekday, excludedTimesOfDay []util.TimePeriod, excludedDaysOfYear []time.Time, timezone *time.Location, minimumAge time.Duration, dryRun bool, createEvent bool, gracePeriod time.Duration) *Chaoskube {
logOutput.Reset()

client := fake.NewSimpleClientset()

var action ChaosAction
if dryRun {
action = NewDryRunAction()
} else {
action = NewDeletePodAction(client, gracePeriod)
}

return New(
fake.NewSimpleClientset(),
client,
labelSelector,
annotations,
namespaces,
Expand All @@ -607,9 +614,8 @@ func (suite *Suite) setup(labelSelector labels.Selector, annotations labels.Sele
timezone,
minimumAge,
logger,
dryRun,
action,
createEvent,
gracePeriod,
)
}

Expand Down
Loading