Skip to content

Commit

Permalink
Add a new StressChaos to generate plenty of stresses (#332) (#454)
Browse files Browse the repository at this point in the history
* Add stress chaos type

Co-authored-by: AngleNet <AngleNet@users.noreply.github.com>
  • Loading branch information
cwen0 and AngleNet committed Apr 26, 2020
1 parent bb50c66 commit 0d10282
Show file tree
Hide file tree
Showing 28 changed files with 2,724 additions and 258 deletions.
276 changes: 276 additions & 0 deletions api/v1alpha1/stresschaos_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
// Copyright 2020 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package v1alpha1

import (
"fmt"
"time"

"github.com/docker/go-units"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// Stress chaos is a chaos to generate plenty of stresses over a collection of pods.

// +kubebuilder:object:root=true

// StressChaos is the Schema for the stresschaos API
type StressChaos struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// Spec defines the behavior of a time chaos experiment
Spec StressChaosSpec `json:"spec"`

// +optional
// Most recently observed status of the time chaos experiment
Status StressChaosStatus `json:"status"`
}

// StressChaosSpec defines the desired state of StressChaos
type StressChaosSpec struct {
// Mode defines the mode to run chaos action.
// Supported mode: one / all / fixed / fixed-percent / random-max-percent
Mode PodMode `json:"mode"`

// Value is required when the mode is set to `FixedPodMode` / `FixedPercentPodMod` / `RandomMaxPercentPodMod`.
// If `FixedPodMode`, provide an integer of pods to do chaos action.
// If `FixedPercentPodMod`, provide a number from 0-100 to specify the max % of pods the server can do chaos action.
// If `RandomMaxPercentPodMod`, provide a number from 0-100 to specify the % of pods to do chaos action
// +optional
Value string `json:"value"`

// Selector is used to select pods that are used to inject chaos action.
Selector SelectorSpec `json:"selector"`

// Stressors defines plenty of stressors supported to stress system components out.
// You can use one or more of them to make up various kinds of stresses. At least
// one of the stressors should be specified.
// +optional
Stressors *Stressors `json:"stressors,omitempty"`

// StressngStressors defines plenty of stressors just like `Stressors` except that it's an experimental
// feature and more powerful. You can define stressors in `stress-ng` (see also `man stress-ng`) dialect,
// however not all of the supported stressors are well tested. It maybe retired in later releases. You
// should always use `Stressors` to define the stressors and use this only when you want more stressors
// unsupported by `Stressors`. When both `StressngStressors` and `Stressors` are defined, `StressngStressors`
// wins.
// +optional
StressngStressors string `json:"stressngStressors,omitempty"`

// Duration represents the duration of the chaos action
// +optional
Duration *string `json:"duration,omitempty"`

// Scheduler defines some schedule rules to control the running time of the chaos experiment about time.
// +optional
Scheduler *SchedulerSpec `json:"scheduler,omitempty"`

// Next time when this action will be applied again
// +optional
NextStart *metav1.Time `json:"nextStart,omitempty"`

// Next time when this action will be recovered
// +optional
NextRecover *metav1.Time `json:"nextRecover,omitempty"`

// If this chaos has been paused
// +optional
Paused bool `json:"paused"`
}

// GetSelector is a getter for Selector (for implementing SelectSpec)
func (in *StressChaosSpec) GetSelector() SelectorSpec {
return in.Selector
}

// GetMode is a getter for Mode (for implementing SelectSpec)
func (in *StressChaosSpec) GetMode() PodMode {
return in.Mode
}

// GetValue is a getter for Value (for implementing SelectSpec)
func (in *StressChaosSpec) GetValue() string {
return in.Value
}

// StressChaosStatus defines the observed state of StressChaos
type StressChaosStatus struct {
ChaosStatus `json:",inline"`
// Instances always specifies stressing instances
// +optional
Instances map[string]StressInstance `json:"instances,omitempty"`
}

// StressInstance is an instance generates stresses
type StressInstance struct {
// UID is the instance identifier
// +optional
UID string `json:"uid"`
// StartTime specifies when the instance starts
// +optional
StartTime *metav1.Time `json:"startTime"`
}

// GetDuration gets the duration of StressChaos
func (in *StressChaos) GetDuration() (*time.Duration, error) {
if in.Spec.Duration == nil {
return nil, nil
}
duration, err := time.ParseDuration(*in.Spec.Duration)
if err != nil {
return nil, err
}
return &duration, nil
}

// GetNextStart gets NextStart field of StressChaos
func (in *StressChaos) GetNextStart() time.Time {
if in.Spec.NextStart == nil {
return time.Time{}
}
return in.Spec.NextStart.Time
}

// SetNextStart sets NextStart field of StressChaos
func (in *StressChaos) SetNextStart(t time.Time) {
if t.IsZero() {
in.Spec.NextStart = nil
return
}

if in.Spec.NextStart == nil {
in.Spec.NextStart = &metav1.Time{}
}
in.Spec.NextStart.Time = t
}

// GetNextRecover get NextRecover field of StressChaos
func (in *StressChaos) GetNextRecover() time.Time {
if in.Spec.NextRecover == nil {
return time.Time{}
}
return in.Spec.NextRecover.Time
}

// SetNextRecover sets NextRecover field of StressChaos
func (in *StressChaos) SetNextRecover(t time.Time) {
if t.IsZero() {
in.Spec.NextRecover = nil
return
}

if in.Spec.NextRecover == nil {
in.Spec.NextRecover = &metav1.Time{}
}
in.Spec.NextRecover.Time = t
}

// GetScheduler returns the scheduler of StressChaos
func (in *StressChaos) GetScheduler() *SchedulerSpec {
return in.Spec.Scheduler
}

// GetStatus returns the status of StressChaos
func (in *StressChaos) GetStatus() *ChaosStatus {
return &in.Status.ChaosStatus
}

// IsDeleted returns whether this resource has been deleted
func (in *StressChaos) IsDeleted() bool {
return !in.DeletionTimestamp.IsZero()
}

// IsPaused returns whether this resource has been paused
func (in *StressChaos) IsPaused() bool {
return in.Spec.Paused
}

// Stressors defines plenty of stressors supported to stress system components out.
// You can use one or more of them to make up various kinds of stresses
type Stressors struct {
// MemoryStressor stresses virtual memory out
// +optional
MemoryStressor *MemoryStressor `json:"memory,omitempty"`
// CPUStressor stresses CPU out
// +optional
CPUStressor *CPUStressor `json:"cpu,omitempty"`
}

// Normalize the stressors to comply with stress-ng
func (in *Stressors) Normalize() (string, error) {
stressors := ""
if in.MemoryStressor != nil {
stressors += fmt.Sprintf(" --vm %d --vm-keep", in.MemoryStressor.Workers)
if len(in.MemoryStressor.Size) != 0 {
if in.MemoryStressor.Size[len(in.MemoryStressor.Size)-1] != '%' {
size, err := units.FromHumanSize(in.MemoryStressor.Size)
if err != nil {
return "", err
}
stressors += fmt.Sprintf(" --vm-bytes %d", size)
} else {
stressors += fmt.Sprintf("--vm-bytes %s",
in.MemoryStressor.Size)
}
}
}
if in.CPUStressor != nil {
stressors += fmt.Sprintf(" --cpu %d", in.CPUStressor.Workers)
if in.CPUStressor.Load != nil {
stressors += fmt.Sprintf(" --cpu-load %d",
*in.CPUStressor.Load)
}
}
return stressors, nil
}

// Stressor defines common configurations of a stressor
type Stressor struct {
// Workers specifies N workers to apply the stressor.
Workers int `json:"workers"`
}

// MemoryStressor defines how to stress memory out
type MemoryStressor struct {
Stressor `json:",inline"`

// Size specifies N bytes consumed per vm worker, default is the total available memory.
// One can specify the size as % of total available memory or in units of B, KB/KiB,
// MB/MiB, GB/GiB, TB/TiB.
// +optional
Size string `json:"size,omitempty"`
}

// CPUStressor defines how to stress CPU out
type CPUStressor struct {
Stressor `json:",inline"`
// Load specifies P percent loading per CPU worker. 0 is effectively a sleep (no load) and 100
// is full loading.
// +optional
Load *int `json:"load,omitempty"`
}

// +kubebuilder:object:root=true

// StressChaosList contains a list of StressChaos
type StressChaosList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []StressChaos `json:"items"`
}

func init() {
SchemeBuilder.Register(&StressChaos{}, &StressChaosList{})
}
62 changes: 62 additions & 0 deletions api/v1alpha1/stresschaos_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2020 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package v1alpha1

import (
"context"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)

// These tests are written in BDD-style using Ginkgo framework. Refer to
// http://onsi.github.io/ginkgo to learn more.
var _ = Describe("StressChaos", func() {

Context("CRUD API", func() {
var (
key types.NamespacedName
created, fetched *StressChaos
)

It("Should create an object successfully", func() {
key = types.NamespacedName{
Name: "foo",
Namespace: "default",
}
created = &StressChaos{
ObjectMeta: v1.ObjectMeta{
Name: "foo",
Namespace: "default",
},
Spec: StressChaosSpec{
Stressors: &Stressors{MemoryStressor: &MemoryStressor{Stressor: Stressor{Workers: 1}}},
},
}
By("creating an API object")
Expect(k8sClient.Create(context.TODO(), created)).To(Succeed())

fetched = &StressChaos{}
Expect(k8sClient.Get(context.TODO(), key, fetched)).To(Succeed())
Expect(fetched).To(Equal(created))

By("deleting the created object")
Expect(k8sClient.Delete(context.TODO(), created)).To(Succeed())
Expect(k8sClient.Get(context.TODO(), key, created)).NotTo(Succeed())
})
})

})
Loading

0 comments on commit 0d10282

Please sign in to comment.