diff --git a/test/e2e/framework/kubernetes/create-agnhost-statefulset.go b/test/e2e/framework/kubernetes/create-agnhost-statefulset.go index b35649c9d7..1499eb807d 100644 --- a/test/e2e/framework/kubernetes/create-agnhost-statefulset.go +++ b/test/e2e/framework/kubernetes/create-agnhost-statefulset.go @@ -120,6 +120,9 @@ func (c *CreateAgnhostStatefulSet) getAgnhostDeployment() *appsv1.StatefulSet { }, }, }, + NodeSelector: map[string]string{ + "kubernetes.io/os": "linux", + }, Containers: []v1.Container{ { Name: c.AgnhostName, diff --git a/test/e2e/framework/kubernetes/create-resource.go b/test/e2e/framework/kubernetes/create-resource.go index 8e7eafda46..ac829ad757 100644 --- a/test/e2e/framework/kubernetes/create-resource.go +++ b/test/e2e/framework/kubernetes/create-resource.go @@ -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) } diff --git a/test/e2e/framework/kubernetes/delete-resource.go b/test/e2e/framework/kubernetes/delete-resource.go index d0b6a0adc5..75e9b13bff 100644 --- a/test/e2e/framework/kubernetes/delete-resource.go +++ b/test/e2e/framework/kubernetes/delete-resource.go @@ -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) } diff --git a/test/e2e/framework/kubernetes/port-forward.go b/test/e2e/framework/kubernetes/port-forward.go index 05a5f2afe7..2693c8cdbf 100644 --- a/test/e2e/framework/kubernetes/port-forward.go +++ b/test/e2e/framework/kubernetes/port-forward.go @@ -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, diff --git a/test/e2e/framework/types/examples/background_test.go b/test/e2e/framework/types/background_test.go similarity index 83% rename from test/e2e/framework/types/examples/background_test.go rename to test/e2e/framework/types/background_test.go index b1c63e07c0..b564d5d298 100644 --- a/test/e2e/framework/types/examples/background_test.go +++ b/test/e2e/framework/types/background_test.go @@ -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) } diff --git a/test/e2e/framework/types/job.go b/test/e2e/framework/types/job.go index edd10fc75b..aefb96fae8 100644 --- a/test/e2e/framework/types/job.go +++ b/test/e2e/framework/types/job.go @@ -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) - - if storedValue == "" { - - 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) + passedvalue := val.Field(i).Interface().(string) + + // 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 + + 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) + } + + 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 } - continue - } - if value != "" { - return fmt.Errorf("parameter %s for step %s is already set from previous step: %w", parameter, stepName, ErrParameterAlreadySet) + } 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) } } } diff --git a/test/e2e/framework/types/jobvalues.go b/test/e2e/framework/types/jobvalues.go index 3f09cec1ec..4769c9e477 100644 --- a/test/e2e/framework/types/jobvalues.go +++ b/test/e2e/framework/types/jobvalues.go @@ -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 } diff --git a/test/e2e/framework/types/scenarios_test.go b/test/e2e/framework/types/scenarios_test.go new file mode 100644 index 0000000000..f6d0768e8e --- /dev/null +++ b/test/e2e/framework/types/scenarios_test.go @@ -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 +} diff --git a/test/e2e/scenarios/hubble/azuremonitor/scenario.go b/test/e2e/scenarios/hubble/azuremonitor/scenario.go index 889a3ed217..2702b09122 100644 --- a/test/e2e/scenarios/hubble/azuremonitor/scenario.go +++ b/test/e2e/scenarios/hubble/azuremonitor/scenario.go @@ -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..., + ) } diff --git a/test/e2e/scenarios/hubble/drop/scenario.go b/test/e2e/scenarios/hubble/drop/scenario.go index 3efd78af04..9b1c3f36f6 100644 --- a/test/e2e/scenarios/hubble/drop/scenario.go +++ b/test/e2e/scenarios/hubble/drop/scenario.go @@ -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...) } diff --git a/test/e2e/scenarios/hubble/flow/scenario.go b/test/e2e/scenarios/hubble/flow/scenario.go index b89a056ced..8f33dd8f7a 100644 --- a/test/e2e/scenarios/hubble/flow/scenario.go +++ b/test/e2e/scenarios/hubble/flow/scenario.go @@ -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...) }