ci: scope scenario parameters instead of whole job (#2578)
* scenario vars * refined scenario scope * linux agnhost * add delete resource * lint
This commit is contained in:
Родитель
6e66efd7cf
Коммит
9cace8977c
|
@ -120,6 +120,9 @@ func (c *CreateAgnhostStatefulSet) getAgnhostDeployment() *appsv1.StatefulSet {
|
|||
},
|
||||
},
|
||||
},
|
||||
NodeSelector: map[string]string{
|
||||
"kubernetes.io/os": "linux",
|
||||
},
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: c.AgnhostName,
|
||||
|
|
|
@ -202,6 +202,22 @@ func CreateResource(ctx context.Context, obj runtime.Object, clientset *kubernet
|
|||
return fmt.Errorf("failed to create/update NetworkPolicy \"%s\" in namespace \"%s\": %w", o.Name, o.Namespace, err)
|
||||
}
|
||||
|
||||
case *v1.Secret:
|
||||
log.Printf("Creating/Updating Secret \"%s\" in namespace \"%s\"...\n", o.Name, o.Namespace)
|
||||
client := clientset.CoreV1().Secrets(o.Namespace)
|
||||
_, err := client.Get(ctx, o.Name, metaV1.GetOptions{})
|
||||
if errors.IsNotFound(err) {
|
||||
_, err = client.Create(ctx, o, metaV1.CreateOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Secret \"%s\" in namespace \"%s\": %w", o.Name, o.Namespace, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
_, err = client.Update(ctx, o, metaV1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create/update Secret \"%s\" in namespace \"%s\": %w", o.Name, o.Namespace, err)
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown object type: %T, err: %w", obj, ErrUnknownResourceType)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
@ -13,10 +14,188 @@ import (
|
|||
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
var ErrDeleteNilResource = fmt.Errorf("cannot create nil resource")
|
||||
|
||||
type ResourceType string
|
||||
|
||||
const (
|
||||
DaemonSet ResourceType = "DaemonSet"
|
||||
Deployment ResourceType = "Deployment"
|
||||
StatefulSet ResourceType = "StatefulSet"
|
||||
Service ResourceType = "Service"
|
||||
ServiceAccount ResourceType = "ServiceAccount"
|
||||
Role ResourceType = "Role"
|
||||
RoleBinding ResourceType = "RoleBinding"
|
||||
ClusterRole ResourceType = "ClusterRole"
|
||||
ClusterRoleBinding ResourceType = "ClusterRoleBinding"
|
||||
ConfigMap ResourceType = "ConfigMap"
|
||||
NetworkPolicy ResourceType = "NetworkPolicy"
|
||||
Secret ResourceType = "Secret"
|
||||
Unknown ResourceType = "Unknown"
|
||||
)
|
||||
|
||||
// Parameters can only be strings, heres to help add guardrails
|
||||
func TypeString(resourceType ResourceType) string {
|
||||
ResourceTypes := map[ResourceType]string{
|
||||
DaemonSet: "DaemonSet",
|
||||
Deployment: "Deployment",
|
||||
StatefulSet: "StatefulSet",
|
||||
Service: "Service",
|
||||
ServiceAccount: "ServiceAccount",
|
||||
Role: "Role",
|
||||
RoleBinding: "RoleBinding",
|
||||
ClusterRole: "ClusterRole",
|
||||
ClusterRoleBinding: "ClusterRoleBinding",
|
||||
ConfigMap: "ConfigMap",
|
||||
NetworkPolicy: "NetworkPolicy",
|
||||
Secret: "Secret",
|
||||
Unknown: "Unknown",
|
||||
}
|
||||
str, ok := ResourceTypes[resourceType]
|
||||
if !ok {
|
||||
return ResourceTypes[Unknown]
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
type DeleteKubernetesResource struct {
|
||||
ResourceType string // can't use enum, breaks parameter parsing, all must be strings
|
||||
ResourceName string
|
||||
ResourceNamespace string
|
||||
KubeConfigFilePath string
|
||||
}
|
||||
|
||||
func (d *DeleteKubernetesResource) Run() error {
|
||||
config, err := clientcmd.BuildConfigFromFlags("", d.KubeConfigFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error building kubeconfig: %w", err)
|
||||
}
|
||||
|
||||
clientset, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating Kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeoutSeconds*time.Second)
|
||||
defer cancel()
|
||||
|
||||
res := ResourceType(d.ResourceType)
|
||||
|
||||
var resource runtime.Object
|
||||
|
||||
switch res {
|
||||
case DaemonSet:
|
||||
resource = &appsv1.DaemonSet{
|
||||
ObjectMeta: metaV1.ObjectMeta{
|
||||
Name: d.ResourceName,
|
||||
Namespace: d.ResourceNamespace,
|
||||
},
|
||||
}
|
||||
case Deployment:
|
||||
resource = &appsv1.Deployment{
|
||||
ObjectMeta: metaV1.ObjectMeta{
|
||||
Name: d.ResourceName,
|
||||
Namespace: d.ResourceNamespace,
|
||||
},
|
||||
}
|
||||
case StatefulSet:
|
||||
resource = &appsv1.StatefulSet{
|
||||
ObjectMeta: metaV1.ObjectMeta{
|
||||
Name: d.ResourceName,
|
||||
Namespace: d.ResourceNamespace,
|
||||
},
|
||||
}
|
||||
case Service:
|
||||
resource = &v1.Service{
|
||||
ObjectMeta: metaV1.ObjectMeta{
|
||||
Name: d.ResourceName,
|
||||
Namespace: d.ResourceNamespace,
|
||||
},
|
||||
}
|
||||
case ServiceAccount:
|
||||
resource = &v1.ServiceAccount{
|
||||
ObjectMeta: metaV1.ObjectMeta{
|
||||
Name: d.ResourceName,
|
||||
Namespace: d.ResourceNamespace,
|
||||
},
|
||||
}
|
||||
case Role:
|
||||
resource = &rbacv1.Role{
|
||||
ObjectMeta: metaV1.ObjectMeta{
|
||||
Name: d.ResourceName,
|
||||
Namespace: d.ResourceNamespace,
|
||||
},
|
||||
}
|
||||
case RoleBinding:
|
||||
resource = &rbacv1.RoleBinding{
|
||||
ObjectMeta: metaV1.ObjectMeta{
|
||||
Name: d.ResourceName,
|
||||
Namespace: d.ResourceNamespace,
|
||||
},
|
||||
}
|
||||
case ClusterRole:
|
||||
resource = &rbacv1.ClusterRole{
|
||||
ObjectMeta: metaV1.ObjectMeta{
|
||||
Name: d.ResourceName,
|
||||
},
|
||||
}
|
||||
case ClusterRoleBinding:
|
||||
resource = &rbacv1.ClusterRoleBinding{
|
||||
ObjectMeta: metaV1.ObjectMeta{
|
||||
Name: d.ResourceName,
|
||||
},
|
||||
}
|
||||
case ConfigMap:
|
||||
resource = &v1.ConfigMap{
|
||||
ObjectMeta: metaV1.ObjectMeta{
|
||||
Name: d.ResourceName,
|
||||
Namespace: d.ResourceNamespace,
|
||||
},
|
||||
}
|
||||
case NetworkPolicy:
|
||||
resource = &networkingv1.NetworkPolicy{
|
||||
ObjectMeta: metaV1.ObjectMeta{
|
||||
Name: d.ResourceName,
|
||||
Namespace: d.ResourceNamespace,
|
||||
},
|
||||
}
|
||||
case Secret:
|
||||
resource = &v1.Secret{
|
||||
ObjectMeta: metaV1.ObjectMeta{
|
||||
Name: d.ResourceName,
|
||||
Namespace: d.ResourceNamespace,
|
||||
},
|
||||
}
|
||||
case Unknown:
|
||||
return fmt.Errorf("unknown resource type: %s: %w", d.ResourceType, ErrUnknownResourceType)
|
||||
default:
|
||||
return ErrUnknownResourceType
|
||||
}
|
||||
|
||||
err = DeleteResource(ctx, resource, clientset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting resource: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DeleteKubernetesResource) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DeleteKubernetesResource) Prevalidate() error {
|
||||
restype := ResourceType(d.ResourceType)
|
||||
if restype == Unknown {
|
||||
return ErrUnknownResourceType
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteResource(ctx context.Context, obj runtime.Object, clientset *kubernetes.Clientset) error { //nolint:gocyclo //this is just boilerplate code
|
||||
if obj == nil {
|
||||
return ErrCreateNilResource
|
||||
|
@ -36,7 +215,7 @@ func DeleteResource(ctx context.Context, obj runtime.Object, clientset *kubernet
|
|||
}
|
||||
|
||||
case *appsv1.Deployment:
|
||||
log.Printf("Creating/Updating Deployment \"%s\" in namespace \"%s\"...\n", o.Name, o.Namespace)
|
||||
log.Printf("Deleting Deployment \"%s\" in namespace \"%s\"...\n", o.Name, o.Namespace)
|
||||
client := clientset.AppsV1().Deployments(o.Namespace)
|
||||
err := client.Delete(ctx, o.Name, metaV1.DeleteOptions{})
|
||||
if err != nil {
|
||||
|
@ -48,7 +227,7 @@ func DeleteResource(ctx context.Context, obj runtime.Object, clientset *kubernet
|
|||
}
|
||||
|
||||
case *appsv1.StatefulSet:
|
||||
log.Printf("Creating/Updating StatefulSet \"%s\" in namespace \"%s\"...\n", o.Name, o.Namespace)
|
||||
log.Printf("Deleting StatefulSet \"%s\" in namespace \"%s\"...\n", o.Name, o.Namespace)
|
||||
client := clientset.AppsV1().StatefulSets(o.Namespace)
|
||||
err := client.Delete(ctx, o.Name, metaV1.DeleteOptions{})
|
||||
if err != nil {
|
||||
|
@ -60,7 +239,7 @@ func DeleteResource(ctx context.Context, obj runtime.Object, clientset *kubernet
|
|||
}
|
||||
|
||||
case *v1.Service:
|
||||
log.Printf("Creating/Updating Service \"%s\" in namespace \"%s\"...\n", o.Name, o.Namespace)
|
||||
log.Printf("Deleting Service \"%s\" in namespace \"%s\"...\n", o.Name, o.Namespace)
|
||||
client := clientset.CoreV1().Services(o.Namespace)
|
||||
err := client.Delete(ctx, o.Name, metaV1.DeleteOptions{})
|
||||
if err != nil {
|
||||
|
@ -72,7 +251,7 @@ func DeleteResource(ctx context.Context, obj runtime.Object, clientset *kubernet
|
|||
}
|
||||
|
||||
case *v1.ServiceAccount:
|
||||
log.Printf("Creating/Updating ServiceAccount \"%s\" in namespace \"%s\"...\n", o.Name, o.Namespace)
|
||||
log.Printf("Deleting ServiceAccount \"%s\" in namespace \"%s\"...\n", o.Name, o.Namespace)
|
||||
client := clientset.CoreV1().ServiceAccounts(o.Namespace)
|
||||
err := client.Delete(ctx, o.Name, metaV1.DeleteOptions{})
|
||||
if err != nil {
|
||||
|
@ -84,7 +263,7 @@ func DeleteResource(ctx context.Context, obj runtime.Object, clientset *kubernet
|
|||
}
|
||||
|
||||
case *rbacv1.Role:
|
||||
log.Printf("Creating/Updating Role \"%s\" in namespace \"%s\"...\n", o.Name, o.Namespace)
|
||||
log.Printf("Deleting Role \"%s\" in namespace \"%s\"...\n", o.Name, o.Namespace)
|
||||
client := clientset.RbacV1().Roles(o.Namespace)
|
||||
err := client.Delete(ctx, o.Name, metaV1.DeleteOptions{})
|
||||
if err != nil {
|
||||
|
@ -96,7 +275,7 @@ func DeleteResource(ctx context.Context, obj runtime.Object, clientset *kubernet
|
|||
}
|
||||
|
||||
case *rbacv1.RoleBinding:
|
||||
log.Printf("Creating/Updating RoleBinding \"%s\" in namespace \"%s\"...\n", o.Name, o.Namespace)
|
||||
log.Printf("Deleting RoleBinding \"%s\" in namespace \"%s\"...\n", o.Name, o.Namespace)
|
||||
client := clientset.RbacV1().RoleBindings(o.Namespace)
|
||||
err := client.Delete(ctx, o.Name, metaV1.DeleteOptions{})
|
||||
if err != nil {
|
||||
|
@ -108,7 +287,7 @@ func DeleteResource(ctx context.Context, obj runtime.Object, clientset *kubernet
|
|||
}
|
||||
|
||||
case *rbacv1.ClusterRole:
|
||||
log.Printf("Creating/Updating ClusterRole \"%s\"...\n", o.Name)
|
||||
log.Printf("Deleting ClusterRole \"%s\"...\n", o.Name)
|
||||
client := clientset.RbacV1().ClusterRoles()
|
||||
err := client.Delete(ctx, o.Name, metaV1.DeleteOptions{})
|
||||
if err != nil {
|
||||
|
@ -120,7 +299,7 @@ func DeleteResource(ctx context.Context, obj runtime.Object, clientset *kubernet
|
|||
}
|
||||
|
||||
case *rbacv1.ClusterRoleBinding:
|
||||
log.Printf("Creating/Updating ClusterRoleBinding \"%s\"...\n", o.Name)
|
||||
log.Printf("Deleting ClusterRoleBinding \"%s\"...\n", o.Name)
|
||||
client := clientset.RbacV1().ClusterRoleBindings()
|
||||
err := client.Delete(ctx, o.Name, metaV1.DeleteOptions{})
|
||||
if err != nil {
|
||||
|
@ -132,7 +311,7 @@ func DeleteResource(ctx context.Context, obj runtime.Object, clientset *kubernet
|
|||
}
|
||||
|
||||
case *v1.ConfigMap:
|
||||
log.Printf("Creating/Updating ConfigMap \"%s\" in namespace \"%s\"...\n", o.Name, o.Namespace)
|
||||
log.Printf("Deleting ConfigMap \"%s\" in namespace \"%s\"...\n", o.Name, o.Namespace)
|
||||
client := clientset.CoreV1().ConfigMaps(o.Namespace)
|
||||
err := client.Delete(ctx, o.Name, metaV1.DeleteOptions{})
|
||||
if err != nil {
|
||||
|
@ -144,7 +323,7 @@ func DeleteResource(ctx context.Context, obj runtime.Object, clientset *kubernet
|
|||
}
|
||||
|
||||
case *networkingv1.NetworkPolicy:
|
||||
log.Printf("Creating/Updating NetworkPolicy \"%s\" in namespace \"%s\"...\n", o.Name, o.Namespace)
|
||||
log.Printf("Deleting NetworkPolicy \"%s\" in namespace \"%s\"...\n", o.Name, o.Namespace)
|
||||
client := clientset.NetworkingV1().NetworkPolicies(o.Namespace)
|
||||
err := client.Delete(ctx, o.Name, metaV1.DeleteOptions{})
|
||||
if err != nil {
|
||||
|
@ -155,6 +334,18 @@ func DeleteResource(ctx context.Context, obj runtime.Object, clientset *kubernet
|
|||
return fmt.Errorf("failed to delete NetworkPolicy \"%s\" in namespace \"%s\": %w", o.Name, o.Namespace, err)
|
||||
}
|
||||
|
||||
case *v1.Secret:
|
||||
log.Printf("Deleting Secret \"%s\" in namespace \"%s\"...\n", o.Name, o.Namespace)
|
||||
client := clientset.CoreV1().Secrets(o.Namespace)
|
||||
err := client.Delete(ctx, o.Name, metaV1.DeleteOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
log.Printf("Secret \"%s\" in namespace \"%s\" does not exist\n", o.Name, o.Namespace)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to delete Secret \"%s\" in namespace \"%s\": %w", o.Name, o.Namespace, err)
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown object type: %T, err: %w", obj, ErrUnknownResourceType)
|
||||
}
|
||||
|
|
|
@ -72,7 +72,6 @@ func (p *PortForward) Run() error {
|
|||
}
|
||||
|
||||
portForwardFn := func() error {
|
||||
|
||||
// if we have a pod name (likely from affinity above), use it, otherwise use label selector
|
||||
opts := k8s.PortForwardingOpts{
|
||||
Namespace: p.Namespace,
|
||||
|
|
|
@ -6,27 +6,25 @@ import (
|
|||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-container-networking/test/e2e/framework/types"
|
||||
)
|
||||
|
||||
func TestFramework(t *testing.T) {
|
||||
job := types.NewJob("Validate that drop metrics are present in the prometheus endpoint")
|
||||
runner := types.NewRunner(t, job)
|
||||
job := NewJob("Validate that drop metrics are present in the prometheus endpoint")
|
||||
runner := NewRunner(t, job)
|
||||
defer runner.Run()
|
||||
|
||||
job.AddStep(&TestBackground{
|
||||
CounterName: "Example Counter",
|
||||
}, &types.StepOptions{
|
||||
}, &StepOptions{
|
||||
ExpectError: false,
|
||||
RunInBackgroundWithID: "TestStep",
|
||||
})
|
||||
|
||||
job.AddStep(&types.Sleep{
|
||||
job.AddStep(&Sleep{
|
||||
Duration: 1 * time.Second,
|
||||
}, nil)
|
||||
|
||||
job.AddStep(&types.Stop{
|
||||
job.AddStep(&Stop{
|
||||
BackgroundID: "TestStep",
|
||||
}, nil)
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
|
@ -15,14 +16,17 @@ var (
|
|||
ErrOrphanSteps = fmt.Errorf("background steps with no corresponding stop")
|
||||
ErrCannotStopStep = fmt.Errorf("cannot stop step")
|
||||
ErrMissingBackroundID = fmt.Errorf("missing background id")
|
||||
ErrNoValue = fmt.Errorf("empty parameter not found saved in values")
|
||||
ErrEmptyScenarioName = fmt.Errorf("scenario name is empty")
|
||||
)
|
||||
|
||||
// A Job is a logical grouping of steps, options and values
|
||||
type Job struct {
|
||||
Values *JobValues
|
||||
values *JobValues
|
||||
Description string
|
||||
Steps []*StepWrapper
|
||||
BackgroundSteps map[string]*StepWrapper
|
||||
Scenarios map[*StepWrapper]*Scenario
|
||||
}
|
||||
|
||||
// A StepWrapper is a coupling of a step and it's options
|
||||
|
@ -34,17 +38,41 @@ type StepWrapper struct {
|
|||
// A Scenario is a logical grouping of steps, used to describe a scenario such as "test drop metrics"
|
||||
// which will require port forwarding, exec'ing, scraping, etc.
|
||||
type Scenario struct {
|
||||
Steps []*StepWrapper
|
||||
name string
|
||||
steps []*StepWrapper
|
||||
values *JobValues
|
||||
}
|
||||
|
||||
func responseDivider(jobname string) {
|
||||
totalWidth := 100
|
||||
func NewScenario(name string, steps ...*StepWrapper) *Scenario {
|
||||
if name == "" {
|
||||
log.Printf("scenario name is empty")
|
||||
}
|
||||
|
||||
return &Scenario{
|
||||
name: name,
|
||||
steps: steps,
|
||||
values: &JobValues{kv: make(map[string]string)},
|
||||
}
|
||||
}
|
||||
|
||||
func (j *Job) GetPrettyStepName(step *StepWrapper) string {
|
||||
prettyname := reflect.TypeOf(step.Step).Elem().Name()
|
||||
if j.Scenarios[step] != nil {
|
||||
prettyname = fmt.Sprintf("%s (scenario: %s)", prettyname, j.Scenarios[step].name)
|
||||
}
|
||||
return prettyname
|
||||
}
|
||||
|
||||
func (j *Job) responseDivider(wrapper *StepWrapper) {
|
||||
totalWidth := 125
|
||||
start := 20
|
||||
i := 0
|
||||
for ; i < start; i++ {
|
||||
fmt.Print("#")
|
||||
}
|
||||
mid := fmt.Sprintf(" %s ", jobname)
|
||||
|
||||
mid := fmt.Sprintf(" %s ", j.GetPrettyStepName(wrapper))
|
||||
|
||||
fmt.Print(mid)
|
||||
for ; i < totalWidth-(start+len(mid)); i++ {
|
||||
fmt.Print("#")
|
||||
|
@ -54,17 +82,19 @@ func responseDivider(jobname string) {
|
|||
|
||||
func NewJob(description string) *Job {
|
||||
return &Job{
|
||||
Values: &JobValues{
|
||||
values: &JobValues{
|
||||
kv: make(map[string]string),
|
||||
},
|
||||
BackgroundSteps: make(map[string]*StepWrapper),
|
||||
Scenarios: make(map[*StepWrapper]*Scenario),
|
||||
Description: description,
|
||||
}
|
||||
}
|
||||
|
||||
func (j *Job) AddScenario(scenario *Scenario) {
|
||||
for _, step := range scenario.Steps {
|
||||
j.AddStep(step.Step, step.Opts)
|
||||
for i, step := range scenario.steps {
|
||||
j.Steps = append(j.Steps, step)
|
||||
j.Scenarios[scenario.steps[i]] = scenario
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,6 +106,56 @@ func (j *Job) AddStep(step Step, opts *StepOptions) {
|
|||
j.Steps = append(j.Steps, stepw)
|
||||
}
|
||||
|
||||
func (j *Job) GetValue(stepw *StepWrapper, key string) (string, bool) {
|
||||
// if step exists in a scenario, use the scenario's values
|
||||
// if the value isn't in the scenario's values, get the root job's value
|
||||
if scenario, exists := j.Scenarios[stepw]; exists {
|
||||
if scenario.values.Contains(key) {
|
||||
return scenario.values.Get(key), true
|
||||
}
|
||||
}
|
||||
if j.values.Contains(key) {
|
||||
return j.values.Get(key), true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// SetGetValues is used when we want to save parameters to job, and also check if
|
||||
// the parameter exists in the scenario's or top level values
|
||||
func (j *Job) SetGetValues(stepw *StepWrapper, key, value string) (string, error) {
|
||||
// if top level step parameter is set, and scenario step is not, inherit
|
||||
// if top level step parameter is not set, and scenario step is, use scenario step
|
||||
// if top level step parameter is set, and scenario step is set, warn and use scenario step
|
||||
|
||||
// check if scenario exists, if it does, check if the value is in the scenario's values
|
||||
if scenario, exists := j.Scenarios[stepw]; exists {
|
||||
scenarioValue, err := scenario.values.SetGet(key, value)
|
||||
if err != nil && !errors.Is(err, ErrEmptyValue) {
|
||||
return "", err
|
||||
}
|
||||
if scenarioValue != "" {
|
||||
return scenarioValue, nil
|
||||
}
|
||||
}
|
||||
|
||||
return j.values.SetGet(key, value)
|
||||
}
|
||||
|
||||
// GetValues is used when we want to skip saving parameters to job, but also check if
|
||||
// the parameter exists in the scenario's or top level values
|
||||
func (j *Job) GetValues(stepw *StepWrapper, key string) string {
|
||||
// check if scenario exists, if it does, check if the value is in the scenario's values
|
||||
if scenario, exists := j.Scenarios[stepw]; exists {
|
||||
scenarioValue := scenario.values.Get(key)
|
||||
if scenarioValue != "" {
|
||||
return scenarioValue
|
||||
}
|
||||
}
|
||||
|
||||
return j.values.Get(key)
|
||||
}
|
||||
|
||||
func (j *Job) Run() error {
|
||||
if j.Description == "" {
|
||||
return ErrEmptyDescription
|
||||
|
@ -95,7 +175,7 @@ func (j *Job) Run() error {
|
|||
}
|
||||
|
||||
for _, wrapper := range j.Steps {
|
||||
responseDivider(reflect.TypeOf(wrapper.Step).Elem().Name())
|
||||
j.responseDivider(wrapper)
|
||||
err := wrapper.Step.Run()
|
||||
if wrapper.Opts.ExpectError && err == nil {
|
||||
return fmt.Errorf("expected error from step %s but got nil: %w", reflect.TypeOf(wrapper.Step).Elem().Name(), ErrNilError)
|
||||
|
@ -137,10 +217,10 @@ func (j *Job) validateBackgroundSteps() error {
|
|||
}
|
||||
|
||||
if j.BackgroundSteps[s.BackgroundID] == nil {
|
||||
return fmt.Errorf("cannot stop step %s, as it won't be started by this time; %w", s.BackgroundID, ErrCannotStopStep)
|
||||
return fmt.Errorf("cannot stop step \"%s\", as it won't be started by this time; %w", s.BackgroundID, ErrCannotStopStep)
|
||||
}
|
||||
if stopped := stoppedBackgroundSteps[s.BackgroundID]; stopped {
|
||||
return fmt.Errorf("cannot stop step %s, as it has already been stopped; %w", s.BackgroundID, ErrCannotStopStep)
|
||||
return fmt.Errorf("cannot stop step \"%s\", as it has already been stopped; %w", s.BackgroundID, ErrCannotStopStep)
|
||||
}
|
||||
|
||||
// track for later on if the stop step is called
|
||||
|
@ -152,7 +232,7 @@ func (j *Job) validateBackgroundSteps() error {
|
|||
default:
|
||||
if stepw.Opts.RunInBackgroundWithID != "" {
|
||||
if _, exists := j.BackgroundSteps[stepw.Opts.RunInBackgroundWithID]; exists {
|
||||
log.Fatalf("step with id %s already exists", stepw.Opts.RunInBackgroundWithID)
|
||||
log.Fatalf("step with id \"%s\" already exists", stepw.Opts.RunInBackgroundWithID)
|
||||
}
|
||||
j.BackgroundSteps[stepw.Opts.RunInBackgroundWithID] = stepw
|
||||
stoppedBackgroundSteps[stepw.Opts.RunInBackgroundWithID] = false
|
||||
|
@ -162,23 +242,22 @@ func (j *Job) validateBackgroundSteps() error {
|
|||
|
||||
for stepName, stopped := range stoppedBackgroundSteps {
|
||||
if !stopped {
|
||||
return fmt.Errorf("step %s was not stopped; %w", stepName, ErrOrphanSteps)
|
||||
return fmt.Errorf("step \"%s\" was not stopped; %w", stepName, ErrOrphanSteps)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (j *Job) validateStep(stepw *StepWrapper) error {
|
||||
stepName := reflect.TypeOf(stepw.Step).Elem().Name()
|
||||
val := reflect.ValueOf(stepw.Step).Elem()
|
||||
func (j *Job) validateStep(step *StepWrapper) error {
|
||||
val := reflect.ValueOf(step.Step).Elem()
|
||||
|
||||
// set default options if none are provided
|
||||
if stepw.Opts == nil {
|
||||
stepw.Opts = &DefaultOpts
|
||||
if step.Opts == nil {
|
||||
step.Opts = &DefaultOpts
|
||||
}
|
||||
|
||||
switch stepw.Step.(type) {
|
||||
switch step.Step.(type) {
|
||||
case *Stop:
|
||||
// don't validate stop steps
|
||||
return nil
|
||||
|
@ -199,30 +278,41 @@ func (j *Job) validateStep(stepw *StepWrapper) error {
|
|||
|
||||
if k == reflect.String {
|
||||
parameter := val.Type().Field(i).Name
|
||||
value := val.Field(i).Interface().(string)
|
||||
storedValue := j.Values.Get(parameter)
|
||||
passedvalue := val.Field(i).Interface().(string)
|
||||
|
||||
if storedValue == "" {
|
||||
// if top level step parameter is set, and scenario step is not, inherit
|
||||
// if top level step parameter is not set, and scenario step is, use scenario step
|
||||
// if top level step parameter is set, and scenario step is set, warn and use scenario step
|
||||
|
||||
switch {
|
||||
case stepw.Opts.SkipSavingParamatersToJob:
|
||||
continue
|
||||
case value != "":
|
||||
fmt.Printf("\"%s\" setting parameter \"%s\" in job context to \"%s\"\n", stepName, parameter, value)
|
||||
j.Values.Set(parameter, value)
|
||||
default:
|
||||
return fmt.Errorf("missing parameter \"%s\" for step \"%s\": %w", parameter, stepName, ErrMissingParameter)
|
||||
var err error
|
||||
var value string
|
||||
if step.Opts.SkipSavingParamatersToJob {
|
||||
retrievedvalue := j.GetValues(step, parameter)
|
||||
|
||||
// if the value is already set, and it's not the same as the one we're trying to set, error
|
||||
if retrievedvalue != "" && passedvalue != "" && retrievedvalue != passedvalue {
|
||||
return fmt.Errorf("parameter \"%s\" was set as \"%s\", but was already saved as \"%s\"; %w", parameter, retrievedvalue, passedvalue, ErrParameterAlreadySet)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if value != "" {
|
||||
return fmt.Errorf("parameter %s for step %s is already set from previous step: %w", parameter, stepName, ErrParameterAlreadySet)
|
||||
if passedvalue == "" {
|
||||
if retrievedvalue == "" {
|
||||
return fmt.Errorf("parameter \"%s\" is empty in step \"%s\"; %w", parameter, j.GetPrettyStepName(step), ErrNoValue)
|
||||
}
|
||||
value = retrievedvalue
|
||||
} else {
|
||||
value = passedvalue
|
||||
}
|
||||
|
||||
} else {
|
||||
value, err = j.SetGetValues(step, parameter, passedvalue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error setting parameter \"%s\": in step \"%s\": %w", parameter, j.GetPrettyStepName(step), err)
|
||||
}
|
||||
}
|
||||
|
||||
// don't use log format since this is technically preexecution and easier to read
|
||||
fmt.Println(stepName, "using previously stored value for parameter", parameter, "set as", j.Values.Get(parameter))
|
||||
val.Field(i).SetString(storedValue)
|
||||
fmt.Printf("%s setting stored value for parameter [%s] set as [%s]\n", j.GetPrettyStepName(step), parameter, value)
|
||||
val.Field(i).SetString(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
package types
|
||||
|
||||
import "sync"
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrValueAlreadySet = fmt.Errorf("parameter already set in values")
|
||||
ErrEmptyValue = fmt.Errorf("empty parameter not found in values")
|
||||
)
|
||||
|
||||
type JobValues struct {
|
||||
RWLock sync.RWMutex
|
||||
|
@ -26,8 +34,21 @@ func (j *JobValues) Get(key string) string {
|
|||
return j.kv[key]
|
||||
}
|
||||
|
||||
func (j *JobValues) Set(key, value string) {
|
||||
func (j *JobValues) SetGet(key, value string) (string, error) {
|
||||
j.RWLock.Lock()
|
||||
defer j.RWLock.Unlock()
|
||||
j.kv[key] = value
|
||||
|
||||
_, ok := j.kv[key]
|
||||
|
||||
switch {
|
||||
case !ok && value != "":
|
||||
j.kv[key] = value
|
||||
return value, nil
|
||||
case ok && value == "":
|
||||
return j.kv[key], nil
|
||||
case ok && value != "":
|
||||
return "", ErrValueAlreadySet
|
||||
}
|
||||
|
||||
return "", ErrEmptyValue
|
||||
}
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test against a BYO cluster with Cilium and Hubble enabled,
|
||||
// create a pod with a deny all network policy and validate
|
||||
// that the drop metrics are present in the prometheus endpoint
|
||||
func TestScenarioValues(t *testing.T) {
|
||||
job := NewJob("Validate that drop metrics are present in the prometheus endpoint")
|
||||
runner := NewRunner(t, job)
|
||||
defer runner.Run()
|
||||
|
||||
// Add top level step
|
||||
job.AddStep(&DummyStep{
|
||||
Parameter1: "Top Level Step 1",
|
||||
Parameter2: "Top Level Step 2",
|
||||
}, nil)
|
||||
|
||||
// Add scenario to ensure that the parameters are set correctly
|
||||
// and inherited without overriding
|
||||
job.AddScenario(NewDummyScenario())
|
||||
|
||||
job.AddStep(&DummyStep{}, nil)
|
||||
}
|
||||
|
||||
// Test against a BYO cluster with Cilium and Hubble enabled,
|
||||
// create a pod with a deny all network policy and validate
|
||||
// that the drop metrics are present in the prometheus endpoint
|
||||
func TestScenarioValuesWithSkip(t *testing.T) {
|
||||
job := NewJob("Validate that drop metrics are present in the prometheus endpoint")
|
||||
runner := NewRunner(t, job)
|
||||
defer runner.Run()
|
||||
|
||||
// Add top level step
|
||||
job.AddStep(&DummyStep{
|
||||
Parameter1: "Top Level Step 1",
|
||||
Parameter2: "Top Level Step 2",
|
||||
}, &StepOptions{
|
||||
SkipSavingParamatersToJob: true,
|
||||
})
|
||||
|
||||
// top level step skips saving parameters, so we should error here
|
||||
// that parameters are missing
|
||||
job.AddScenario(NewDummyScenario())
|
||||
|
||||
job.AddStep(&DummyStep{
|
||||
Parameter1: "Other Level Step 1",
|
||||
Parameter2: "Other Level Step 2",
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func TestScenarioValuesWithScenarioSkip(t *testing.T) {
|
||||
job := NewJob("Validate that drop metrics are present in the prometheus endpoint")
|
||||
runner := NewRunner(t, job)
|
||||
defer runner.Run()
|
||||
|
||||
// Add top level step
|
||||
job.AddStep(&DummyStep{
|
||||
Parameter1: "Kubeconfig path 1",
|
||||
Parameter2: "Kubeconfig path 2",
|
||||
}, nil)
|
||||
|
||||
// top level step skips saving parameters, so we should error here
|
||||
// that parameters are missing
|
||||
job.AddScenario(NewDummyScenarioWithSkipSave())
|
||||
|
||||
// Add top level step
|
||||
job.AddStep(&DummyStep{}, nil)
|
||||
}
|
||||
|
||||
func NewDummyScenario() *Scenario {
|
||||
return NewScenario("Dummy Scenario",
|
||||
&StepWrapper{
|
||||
Step: &DummyStep{
|
||||
Parameter1: "Something in Scenario 1",
|
||||
Parameter2: "Something in Scenario 1",
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func NewDummyScenario2() *Scenario {
|
||||
return NewScenario("Dummy Scenario",
|
||||
&StepWrapper{
|
||||
Step: &DummyStep{
|
||||
Parameter1: "Something 2 in Scenario 1",
|
||||
Parameter2: "Something 2 in Scenario 1",
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func NewDummyScenarioWithSkipSave() *Scenario {
|
||||
return NewScenario("Dummy Scenario",
|
||||
&StepWrapper{
|
||||
Step: &DummyStep{
|
||||
Parameter1: "",
|
||||
Parameter2: "",
|
||||
}, Opts: &StepOptions{
|
||||
SkipSavingParamatersToJob: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
type DummyStep struct {
|
||||
Parameter1 string
|
||||
Parameter2 string
|
||||
}
|
||||
|
||||
func (d *DummyStep) Run() error {
|
||||
fmt.Printf("Running DummyStep with parameter 1 as: %s\n", d.Parameter1)
|
||||
fmt.Printf("Running DummyStep with parameter 2 as: %s\n", d.Parameter2)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DummyStep) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DummyStep) Prevalidate() error {
|
||||
return nil
|
||||
}
|
|
@ -7,29 +7,32 @@ import (
|
|||
|
||||
// todo: once AMA is rolled out
|
||||
func ValidateAMATargets() *types.Scenario {
|
||||
return &types.Scenario{
|
||||
Steps: []*types.StepWrapper{
|
||||
{
|
||||
Step: &k8s.PortForward{
|
||||
Namespace: "kube-system",
|
||||
LabelSelector: "k8s-app=cilium",
|
||||
LocalPort: "9965",
|
||||
RemotePort: "9965",
|
||||
},
|
||||
Opts: &types.StepOptions{
|
||||
RunInBackgroundWithID: "validate-ama-targets",
|
||||
},
|
||||
steps := []*types.StepWrapper{
|
||||
{
|
||||
Step: &k8s.PortForward{
|
||||
Namespace: "kube-system",
|
||||
LabelSelector: "k8s-app=cilium",
|
||||
LocalPort: "9965",
|
||||
RemotePort: "9965",
|
||||
},
|
||||
{
|
||||
Step: &VerifyPrometheusMetrics{
|
||||
Address: "http://localhost:9090",
|
||||
},
|
||||
Opts: &types.StepOptions{
|
||||
RunInBackgroundWithID: "validate-ama-targets",
|
||||
},
|
||||
{
|
||||
Step: &types.Stop{
|
||||
BackgroundID: "validate-ama-targets",
|
||||
},
|
||||
},
|
||||
{
|
||||
Step: &VerifyPrometheusMetrics{
|
||||
Address: "http://localhost:9090",
|
||||
},
|
||||
},
|
||||
{
|
||||
Step: &types.Stop{
|
||||
BackgroundID: "validate-ama-targets",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return types.NewScenario(
|
||||
"Validate that drop metrics are present in the prometheus endpoint",
|
||||
steps...,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -16,79 +16,79 @@ const (
|
|||
)
|
||||
|
||||
func ValidateDropMetric() *types.Scenario {
|
||||
return &types.Scenario{
|
||||
Steps: []*types.StepWrapper{
|
||||
{
|
||||
Step: &k8s.CreateKapingerDeployment{
|
||||
KapingerNamespace: "kube-system",
|
||||
KapingerReplicas: "1",
|
||||
},
|
||||
Name := "Validate that drop metrics are present in the prometheus endpoint"
|
||||
Steps := []*types.StepWrapper{
|
||||
{
|
||||
Step: &k8s.CreateKapingerDeployment{
|
||||
KapingerNamespace: "kube-system",
|
||||
KapingerReplicas: "1",
|
||||
},
|
||||
{
|
||||
Step: &k8s.CreateDenyAllNetworkPolicy{
|
||||
NetworkPolicyNamespace: "kube-system",
|
||||
DenyAllLabelSelector: "app=agnhost-a",
|
||||
},
|
||||
},
|
||||
{
|
||||
Step: &k8s.CreateDenyAllNetworkPolicy{
|
||||
NetworkPolicyNamespace: "kube-system",
|
||||
DenyAllLabelSelector: "app=agnhost-a",
|
||||
},
|
||||
{
|
||||
Step: &k8s.CreateAgnhostStatefulSet{
|
||||
AgnhostName: "agnhost-a",
|
||||
AgnhostNamespace: "kube-system",
|
||||
},
|
||||
},
|
||||
{
|
||||
Step: &k8s.CreateAgnhostStatefulSet{
|
||||
AgnhostName: "agnhost-a",
|
||||
AgnhostNamespace: "kube-system",
|
||||
},
|
||||
{
|
||||
Step: &k8s.ExecInPod{
|
||||
PodName: "agnhost-a-0",
|
||||
PodNamespace: "kube-system",
|
||||
Command: "curl -s -m 5 bing.com",
|
||||
},
|
||||
Opts: &types.StepOptions{
|
||||
ExpectError: true,
|
||||
SkipSavingParamatersToJob: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Step: &k8s.ExecInPod{
|
||||
PodName: "agnhost-a-0",
|
||||
PodNamespace: "kube-system",
|
||||
Command: "curl -s -m 5 bing.com",
|
||||
},
|
||||
{
|
||||
Step: &types.Sleep{
|
||||
Duration: sleepDelay,
|
||||
},
|
||||
Opts: &types.StepOptions{
|
||||
ExpectError: true,
|
||||
SkipSavingParamatersToJob: true,
|
||||
},
|
||||
// run curl again
|
||||
{
|
||||
Step: &k8s.ExecInPod{
|
||||
PodName: "agnhost-a-0",
|
||||
PodNamespace: "kube-system",
|
||||
Command: "curl -s -m 5 bing.com",
|
||||
},
|
||||
Opts: &types.StepOptions{
|
||||
ExpectError: true,
|
||||
SkipSavingParamatersToJob: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Step: &types.Sleep{
|
||||
Duration: sleepDelay,
|
||||
},
|
||||
{
|
||||
Step: &k8s.PortForward{
|
||||
Namespace: "kube-system",
|
||||
LabelSelector: "k8s-app=cilium",
|
||||
LocalPort: "9965",
|
||||
RemotePort: "9965",
|
||||
OptionalLabelAffinity: "app=agnhost-a", // port forward to a pod on a node that also has this pod with this label, assuming same namespace
|
||||
},
|
||||
Opts: &types.StepOptions{
|
||||
RunInBackgroundWithID: "hubble-drop-port-forward",
|
||||
},
|
||||
},
|
||||
// run curl again
|
||||
{
|
||||
Step: &k8s.ExecInPod{
|
||||
PodName: "agnhost-a-0",
|
||||
PodNamespace: "kube-system",
|
||||
Command: "curl -s -m 5 bing.com",
|
||||
},
|
||||
{
|
||||
Step: &ValidateHubbleDropMetric{
|
||||
PortForwardedHubblePort: "9965",
|
||||
Source: "agnhost-a",
|
||||
Reason: PolicyDenied,
|
||||
Protocol: UDP,
|
||||
},
|
||||
Opts: &types.StepOptions{
|
||||
ExpectError: true,
|
||||
SkipSavingParamatersToJob: true,
|
||||
},
|
||||
{
|
||||
Step: &types.Stop{
|
||||
BackgroundID: "hubble-drop-port-forward",
|
||||
},
|
||||
},
|
||||
{
|
||||
Step: &k8s.PortForward{
|
||||
Namespace: "kube-system",
|
||||
LabelSelector: "k8s-app=cilium",
|
||||
LocalPort: "9965",
|
||||
RemotePort: "9965",
|
||||
OptionalLabelAffinity: "app=agnhost-a", // port forward to a pod on a node that also has this pod with this label, assuming same namespace
|
||||
},
|
||||
Opts: &types.StepOptions{
|
||||
RunInBackgroundWithID: "hubble-drop-port-forward",
|
||||
},
|
||||
},
|
||||
{
|
||||
Step: &ValidateHubbleDropMetric{
|
||||
PortForwardedHubblePort: "9965",
|
||||
Source: "agnhost-a",
|
||||
Reason: PolicyDenied,
|
||||
Protocol: UDP,
|
||||
},
|
||||
},
|
||||
{
|
||||
Step: &types.Stop{
|
||||
BackgroundID: "hubble-drop-port-forward",
|
||||
},
|
||||
},
|
||||
}
|
||||
return types.NewScenario(Name, Steps...)
|
||||
}
|
||||
|
|
|
@ -4,13 +4,13 @@ import "github.com/Azure/azure-container-networking/test/e2e/framework/types"
|
|||
|
||||
// todo: once AMA is rolled out
|
||||
func ValidateAMATargets() *types.Scenario {
|
||||
return &types.Scenario{
|
||||
Steps: []*types.StepWrapper{
|
||||
{
|
||||
Step: &ValidateHubbleFlowMetric{
|
||||
LocalPort: "9090",
|
||||
},
|
||||
name := "Validate that flow metrics are present in the prometheus endpoint"
|
||||
steps := []*types.StepWrapper{
|
||||
{
|
||||
Step: &ValidateHubbleFlowMetric{
|
||||
LocalPort: "9090",
|
||||
},
|
||||
},
|
||||
}
|
||||
return types.NewScenario(name, steps...)
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче