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

CI: Enable upstream virtualization operator E2E tests. #1556

Merged
merged 11 commits into from
Oct 22, 2024
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ OC_CLI = $(shell which oc)
TEST_VIRT ?= false
KVM_EMULATION ?= true
TEST_UPGRADE ?= false
HCO_UPSTREAM ?= false

ifdef CLI_DIR
OC_CLI = ${CLI_DIR}/oc
Expand Down Expand Up @@ -524,6 +525,7 @@ test-e2e: test-e2e-setup install-ginkgo
-artifact_dir=$(ARTIFACT_DIR) \
-oc_cli=$(OC_CLI) \
-kvm_emulation=$(KVM_EMULATION) \
-hco_upstream=$(HCO_UPSTREAM) \
--ginkgo.vv \
--ginkgo.no-color=$(OPENSHIFT_CI) \
--ginkgo.label-filter="$(TEST_FILTER)" \
Expand Down
11 changes: 10 additions & 1 deletion tests/e2e/e2e_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ var (
knownFlake bool
accumulatedTestLogs []string

kvmEmulation bool
kvmEmulation bool
useUpstreamHco bool
)

func init() {
Expand All @@ -65,6 +66,7 @@ func init() {
flag.StringVar(&oc_cli, "oc_cli", "oc", "OC CLI Client")
flag.Int64Var(&flakeAttempts, "flakeAttempts", 3, "Customize the number of flake retries (3)")
flag.BoolVar(&kvmEmulation, "kvm_emulation", true, "Enable or disable KVM emulation for virtualization testing")
flag.BoolVar(&useUpstreamHco, "hco_upstream", false, "Force use of upstream virtualization operator")

// helps with launching debug sessions from IDE
if os.Getenv("E2E_USE_ENV_FLAGS") == "true" {
Expand Down Expand Up @@ -110,6 +112,13 @@ func init() {
log.Println("Error parsing KVM_EMULATION, it will be enabled by default: ", err)
}
}
if envValue := os.Getenv("HCO_UPSTREAM"); envValue != "" {
if parsedValue, err := strconv.ParseBool(envValue); err == nil {
useUpstreamHco = parsedValue
} else {
log.Println("Error parsing HCO_UPSTREAM, it will be disabled by default: ", err)
}
}
}

}
Expand Down
3 changes: 3 additions & 0 deletions tests/e2e/lib/subscription_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ func getOperatorSubscription(c client.Client, namespace, label string) (*Subscri

func (v *VirtOperator) getOperatorSubscription() (*Subscription, error) {
label := "operators.coreos.com/kubevirt-hyperconverged.openshift-cnv"
if v.Upstream {
label = "operators.coreos.com/community-kubevirt-hyperconverged.kubevirt-hyperconverged"
}
return getOperatorSubscription(v.Client, v.Namespace, label)
}

Expand Down
110 changes: 68 additions & 42 deletions tests/e2e/lib/virt_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,19 @@ type VirtOperator struct {
Namespace string
Csv string
Version *version.Version
Upstream bool
}

// GetVirtOperator fills out a new VirtOperator
func GetVirtOperator(c client.Client, clientset *kubernetes.Clientset, dynamicClient dynamic.Interface) (*VirtOperator, error) {
func GetVirtOperator(c client.Client, clientset *kubernetes.Clientset, dynamicClient dynamic.Interface, upstream bool) (*VirtOperator, error) {
namespace := "openshift-cnv"
manifest := "kubevirt-hyperconverged"
if upstream {
namespace = "kubevirt-hyperconverged"
manifest = "community-kubevirt-hyperconverged"
}

csv, operatorVersion, err := getCsvFromPackageManifest(dynamicClient, "kubevirt-hyperconverged")
csv, operatorVersion, err := getCsvFromPackageManifest(dynamicClient, manifest)
if err != nil {
log.Printf("Failed to get CSV from package manifest")
return nil, err
Expand All @@ -78,6 +84,7 @@ func GetVirtOperator(c client.Client, clientset *kubernetes.Clientset, dynamicCl
Namespace: namespace,
Csv: csv,
Version: operatorVersion,
Upstream: upstream,
}

return v, nil
Expand All @@ -86,16 +93,23 @@ func GetVirtOperator(c client.Client, clientset *kubernetes.Clientset, dynamicCl
// Helper to create an operator group object, common to installOperatorGroup
// and removeOperatorGroup.
func (v *VirtOperator) makeOperatorGroup() *operatorsv1.OperatorGroup {
// Community operator fails with "cannot configure to watch own namespace",
// need to remove target namespaces.
spec := operatorsv1.OperatorGroupSpec{}
if !v.Upstream {
spec = operatorsv1.OperatorGroupSpec{
TargetNamespaces: []string{
v.Namespace,
},
}
}

return &operatorsv1.OperatorGroup{
ObjectMeta: metav1.ObjectMeta{
Name: "kubevirt-hyperconverged-group",
Namespace: v.Namespace,
},
Spec: operatorsv1.OperatorGroupSpec{
TargetNamespaces: []string{
v.Namespace,
},
},
Spec: spec,
}
}

Expand Down Expand Up @@ -174,9 +188,9 @@ func getCsvFromPackageManifest(dynamicClient dynamic.Interface, name string) (st
}

// Checks the existence of the operator's target namespace
func (v *VirtOperator) checkNamespace() bool {
func (v *VirtOperator) checkNamespace(ns string) bool {
// First check that the namespace exists
exists, _ := DoesNamespaceExist(v.Clientset, v.Namespace)
exists, _ := DoesNamespaceExist(v.Clientset, ns)
return exists
}

Expand Down Expand Up @@ -236,7 +250,7 @@ func (v *VirtOperator) checkHco() bool {

// Check if KVM emulation is enabled.
func (v *VirtOperator) checkEmulation() bool {
hco, err := v.Dynamic.Resource(hyperConvergedGvr).Namespace("openshift-cnv").Get(context.Background(), "kubevirt-hyperconverged", metav1.GetOptions{})
hco, err := v.Dynamic.Resource(hyperConvergedGvr).Namespace(v.Namespace).Get(context.Background(), "kubevirt-hyperconverged", metav1.GetOptions{})
if err != nil {
return false
}
Expand All @@ -260,11 +274,12 @@ func (v *VirtOperator) checkEmulation() bool {
return false
}

// Creates the target virtualization namespace, likely openshift-cnv or kubevirt-hyperconverged
func (v *VirtOperator) installNamespace() error {
err := v.Client.Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: v.Namespace}})
// Creates the target namespace, likely openshift-cnv or kubevirt-hyperconverged,
// but also used for openshift-virtualization-os-images if not already present.
func (v *VirtOperator) installNamespace(ns string) error {
err := v.Client.Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}})
if err != nil {
log.Printf("Failed to create namespace %s: %v", v.Namespace, err)
log.Printf("Failed to create namespace %s: %v", ns, err)
return err
}
return nil
Expand All @@ -285,19 +300,30 @@ func (v *VirtOperator) installOperatorGroup() error {

// Creates the subscription, which triggers creation of the ClusterServiceVersion.
func (v *VirtOperator) installSubscription() error {
subscription := &operatorsv1alpha1.Subscription{
ObjectMeta: metav1.ObjectMeta{
Name: "hco-operatorhub",
Namespace: v.Namespace,
},
Spec: &operatorsv1alpha1.SubscriptionSpec{
CatalogSource: "redhat-operators",
spec := &operatorsv1alpha1.SubscriptionSpec{
CatalogSource: "redhat-operators",
CatalogSourceNamespace: "openshift-marketplace",
Package: "kubevirt-hyperconverged",
Channel: "stable",
StartingCSV: v.Csv,
InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic,
}
if v.Upstream {
spec = &operatorsv1alpha1.SubscriptionSpec{
CatalogSource: "community-operators",
CatalogSourceNamespace: "openshift-marketplace",
Package: "kubevirt-hyperconverged",
Package: "community-kubevirt-hyperconverged",
Channel: "stable",
StartingCSV: v.Csv,
InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic,
}
}
subscription := &operatorsv1alpha1.Subscription{
ObjectMeta: metav1.ObjectMeta{
Name: "hco-operatorhub",
Namespace: v.Namespace,
},
Spec: spec,
}
err := v.Client.Create(context.Background(), subscription)
if err != nil {
Expand Down Expand Up @@ -332,7 +358,7 @@ func (v *VirtOperator) installHco() error {
}

func (v *VirtOperator) configureEmulation() error {
hco, err := v.Dynamic.Resource(hyperConvergedGvr).Namespace("openshift-cnv").Get(context.Background(), "kubevirt-hyperconverged", metav1.GetOptions{})
hco, err := v.Dynamic.Resource(hyperConvergedGvr).Namespace(v.Namespace).Get(context.Background(), "kubevirt-hyperconverged", metav1.GetOptions{})
if err != nil {
return err
}
Expand All @@ -353,7 +379,7 @@ func (v *VirtOperator) configureEmulation() error {
return err
}

_, err = v.Dynamic.Resource(hyperConvergedGvr).Namespace("openshift-cnv").Update(context.Background(), hco, metav1.UpdateOptions{})
_, err = v.Dynamic.Resource(hyperConvergedGvr).Namespace(v.Namespace).Update(context.Background(), hco, metav1.UpdateOptions{})
if err != nil {
return err
}
Expand All @@ -362,19 +388,19 @@ func (v *VirtOperator) configureEmulation() error {
}

// Creates target namespace if needed, and waits for it to exist
func (v *VirtOperator) ensureNamespace(timeout time.Duration) error {
if !v.checkNamespace() {
if err := v.installNamespace(); err != nil {
func (v *VirtOperator) EnsureNamespace(ns string, timeout time.Duration) error {
if !v.checkNamespace(ns) {
if err := v.installNamespace(ns); err != nil {
return err
}
err := wait.PollImmediate(time.Second, timeout, func() (bool, error) {
return v.checkNamespace(), nil
return v.checkNamespace(ns), nil
})
if err != nil {
return fmt.Errorf("timed out waiting to create namespace %s: %w", v.Namespace, err)
return fmt.Errorf("timed out waiting to create namespace %s: %w", ns, err)
}
} else {
log.Printf("Namespace %s already present, no action required", v.Namespace)
log.Printf("Namespace %s already present, no action required", ns)
}

return nil
Expand Down Expand Up @@ -449,10 +475,10 @@ func (v *VirtOperator) ensureHco(timeout time.Duration) error {
}

// Deletes the virtualization operator namespace (likely openshift-cnv).
func (v *VirtOperator) removeNamespace() error {
err := v.Client.Delete(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: v.Namespace}})
func (v *VirtOperator) removeNamespace(ns string) error {
err := v.Client.Delete(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}})
if err != nil {
log.Printf("Failed to delete namespace %s: %v", v.Namespace, err)
log.Printf("Failed to delete namespace %s: %v", ns, err)
return err
}
return nil
Expand Down Expand Up @@ -494,21 +520,21 @@ func (v *VirtOperator) removeHco() error {
}

// Makes sure the virtualization operator's namespace is removed.
func (v *VirtOperator) ensureNamespaceRemoved(timeout time.Duration) error {
if !v.checkNamespace() {
log.Printf("Namespace %s already removed, no action required", v.Namespace)
func (v *VirtOperator) ensureNamespaceRemoved(ns string, timeout time.Duration) error {
if !v.checkNamespace(ns) {
log.Printf("Namespace %s already removed, no action required", ns)
return nil
}

if err := v.removeNamespace(); err != nil {
if err := v.removeNamespace(ns); err != nil {
return err
}

err := wait.PollImmediate(5*time.Second, timeout, func() (bool, error) {
return !v.checkNamespace(), nil
return !v.checkNamespace(ns), nil
})
if err != nil {
return fmt.Errorf("timed out waiting to delete namespace %s: %w", v.Namespace, err)
return fmt.Errorf("timed out waiting to delete namespace %s: %w", ns, err)
}

return nil
Expand Down Expand Up @@ -696,7 +722,7 @@ func (v *VirtOperator) EnsureEmulation(timeout time.Duration) error {
// IsVirtInstalled returns whether or not the OpenShift Virtualization operator
// is installed and ready, by checking for a HyperConverged operator resource.
func (v *VirtOperator) IsVirtInstalled() bool {
if !v.checkNamespace() {
if !v.checkNamespace(v.Namespace) {
return false
}

Expand All @@ -712,7 +738,7 @@ func (v *VirtOperator) EnsureVirtInstallation() error {
}

log.Printf("Creating virtualization namespace %s", v.Namespace)
if err := v.ensureNamespace(10 * time.Second); err != nil {
if err := v.EnsureNamespace(v.Namespace, 10*time.Second); err != nil {
return err
}
log.Printf("Created namespace %s", v.Namespace)
Expand Down Expand Up @@ -771,7 +797,7 @@ func (v *VirtOperator) EnsureVirtRemoval() error {
log.Println("Deleted operator group")

log.Printf("Deleting virtualization namespace %s", v.Namespace)
if err := v.ensureNamespaceRemoved(3 * time.Minute); err != nil {
if err := v.ensureNamespaceRemoved(v.Namespace, 3*time.Minute); err != nil {
return err
}
log.Printf("Deleting namespace %s", v.Namespace)
Expand Down
47 changes: 42 additions & 5 deletions tests/e2e/lib/virt_storage_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,26 +173,30 @@ func (v *VirtOperator) RemoveDataSource(namespace, name string) error {
// Create a DataSource from an existing PVC, with the same name and namespace.
// This way, the PVC can be specified as a sourceRef in the VM spec.
func (v *VirtOperator) CreateDataSourceFromPvc(namespace, name string) error {
return v.CreateTargetDataSourceFromPvc(namespace, namespace, name, name)
}

func (v *VirtOperator) CreateTargetDataSourceFromPvc(sourceNamespace, destinationNamespace, sourcePvcName, destinationDataSourceName string) error {
unstructuredDataSource := unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "cdi.kubevirt.io/v1beta1",
"kind": "DataSource",
"metadata": map[string]interface{}{
"name": name,
"namespace": namespace,
"name": destinationDataSourceName,
"namespace": destinationNamespace,
},
"spec": map[string]interface{}{
"source": map[string]interface{}{
"pvc": map[string]interface{}{
"name": name,
"namespace": namespace,
"name": sourcePvcName,
"namespace": sourceNamespace,
},
},
},
},
}

_, err := v.Dynamic.Resource(dataSourceGVR).Namespace(namespace).Create(context.Background(), &unstructuredDataSource, metav1.CreateOptions{})
_, err := v.Dynamic.Resource(dataSourceGVR).Namespace(destinationNamespace).Create(context.Background(), &unstructuredDataSource, metav1.CreateOptions{})
if err != nil {
if apierrors.IsAlreadyExists(err) {
return nil
Expand All @@ -207,6 +211,36 @@ func (v *VirtOperator) CreateDataSourceFromPvc(namespace, name string) error {
return nil
}

// Find the given DataSource, and return the PVC it points to
func (v *VirtOperator) GetDataSourcePvc(ns, name string) (string, string, error) {
unstructuredDataSource, err := v.Dynamic.Resource(dataSourceGVR).Namespace(ns).Get(context.Background(), name, metav1.GetOptions{})
if err != nil {
log.Printf("Error getting DataSource %s: %v", name, err)
return "", "", err
}

pvcName, ok, err := unstructured.NestedString(unstructuredDataSource.UnstructuredContent(), "status", "source", "pvc", "name")
if err != nil {
log.Printf("Error getting PVC from DataSource: %v", err)
return "", "", err
}
if !ok {
return "", "", errors.New("failed to get PVC from " + name + " DataSource")
}

pvcNamespace, ok, err := unstructured.NestedString(unstructuredDataSource.UnstructuredContent(), "status", "source", "pvc", "namespace")
if err != nil {
log.Printf("Error getting PVC namespace from DataSource: %v", err)
return "", "", err
}
if !ok {
return "", "", errors.New("failed to get PVC namespace from " + name + " DataSource")
}

return pvcNamespace, pvcName, nil

}

// Find the default storage class
func (v *VirtOperator) GetDefaultStorageClass() (*storagev1.StorageClass, error) {
storageClasses, err := v.Clientset.StorageV1().StorageClasses().List(context.Background(), metav1.ListOptions{})
Expand Down Expand Up @@ -241,6 +275,9 @@ func (v *VirtOperator) CreateImmediateModeStorageClass(name string) error {
immediateStorageClass.Annotations["storageclass.kubernetes.io/is-default-class"] = "false"

_, err = v.Clientset.StorageV1().StorageClasses().Create(context.Background(), immediateStorageClass, metav1.CreateOptions{})
if apierrors.IsAlreadyExists(err) {
return nil
}
return err
}

Expand Down
Loading