From 74da9a0e0924f65e4735b521bbc815c5d521c748 Mon Sep 17 00:00:00 2001 From: taimurhafeez Date: Tue, 10 Feb 2026 15:35:11 +0000 Subject: [PATCH 1/6] Added Test case for case tailored profile works on Hypershift --- tests/e2e/serial/main_test.go | 109 ++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/tests/e2e/serial/main_test.go b/tests/e2e/serial/main_test.go index 152910bcbe..c2b1faa738 100644 --- a/tests/e2e/serial/main_test.go +++ b/tests/e2e/serial/main_test.go @@ -2164,6 +2164,115 @@ func TestScanTailoredProfileExtendsDeprecated(t *testing.T) { } } +// Test author: xiyuan@redhat.com +// Ported from downstream tests: 60854, 60864 +// TestHypershiftTailoredProfileScan tests scanning HyperShift hosted cluster +// with tailored profiles for ocp4-cis and ocp4-pci-dss compliance profiles +func TestHypershiftTailoredProfileScan(t *testing.T) { + f := framework.Global + + // Skip test if not running on a HyperShift cluster + infraList := configv1.InfrastructureList{} + err := f.Client.List(context.TODO(), &infraList) + if err != nil { + t.Fatalf("Failed to list infrastructures: %v", err) + } + + isHypershift := false + for _, infra := range infraList.Items { + if infra.Status.ControlPlaneTopology == configv1.ExternalTopologyMode { + isHypershift = true + break + } + } + + if !isHypershift { + t.Skip("Test requires HyperShift cluster (External control plane topology)") + } + + // Create tailored profile for ocp4-cis + cisTPName := "test-hypershift-cis-tp" + cisTP := &compv1alpha1.TailoredProfile{ + ObjectMeta: metav1.ObjectMeta{ + Name: cisTPName, + Namespace: f.OperatorNamespace, + }, + Spec: compv1alpha1.TailoredProfileSpec{ + Extends: "ocp4-cis", + Title: "HyperShift CIS Tailored Profile", + Description: "Tailored profile for ocp4-cis on HyperShift cluster", + }, + } + err = f.Client.Create(context.TODO(), cisTP, nil) + if err != nil { + t.Fatalf("failed to create CIS TailoredProfile: %s", err) + } + defer f.Client.Delete(context.TODO(), cisTP) + + // Create tailored profile for ocp4-pci-dss + pciTPName := "test-hypershift-pci-tp" + pciTP := &compv1alpha1.TailoredProfile{ + ObjectMeta: metav1.ObjectMeta{ + Name: pciTPName, + Namespace: f.OperatorNamespace, + }, + Spec: compv1alpha1.TailoredProfileSpec{ + Extends: "ocp4-pci-dss", + Title: "HyperShift PCI-DSS Tailored Profile", + Description: "Tailored profile for ocp4-pci-dss on HyperShift cluster", + }, + } + err = f.Client.Create(context.TODO(), pciTP, nil) + if err != nil { + t.Fatalf("failed to create PCI-DSS TailoredProfile: %s", err) + } + defer f.Client.Delete(context.TODO(), pciTP) + + // Create ScanSettingBinding with both tailored profiles + suiteName := framework.GetObjNameFromTest(t) + ssb := &compv1alpha1.ScanSettingBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: suiteName, + Namespace: f.OperatorNamespace, + }, + Profiles: []compv1alpha1.NamedObjectReference{ + { + APIGroup: "compliance.openshift.io/v1alpha1", + Kind: "TailoredProfile", + Name: cisTPName, + }, + { + APIGroup: "compliance.openshift.io/v1alpha1", + Kind: "TailoredProfile", + Name: pciTPName, + }, + }, + SettingsRef: &compv1alpha1.NamedObjectReference{ + APIGroup: "compliance.openshift.io/v1alpha1", + Kind: "ScanSetting", + Name: "default", + }, + } + err = f.Client.Create(context.TODO(), ssb, nil) + if err != nil { + t.Fatalf("failed to create ScanSettingBinding: %s", err) + } + defer f.Client.Delete(context.TODO(), ssb) + + // Wait for CIS scan to complete + // When using SSB with TailoredProfile, the scan has same name as the TP + if err = f.WaitForScanStatus(f.OperatorNamespace, cisTPName, compv1alpha1.PhaseDone); err != nil { + t.Fatalf("CIS scan failed to complete: %s", err) + } + + // Wait for PCI-DSS scan to complete + if err = f.WaitForScanStatus(f.OperatorNamespace, pciTPName, compv1alpha1.PhaseDone); err != nil { + t.Fatalf("PCI-DSS scan failed to complete: %s", err) + } + + t.Logf("Both HyperShift tailored profile scans completed successfully") +} + //testExecution{ // Name: "TestNodeSchedulingErrorFailsTheScan", // IsParallel: false, From 03fa520fcd5d5488b9a833d55296d0b022110639 Mon Sep 17 00:00:00 2001 From: taimurhafeez Date: Tue, 10 Feb 2026 15:38:14 +0000 Subject: [PATCH 2/6] Removed IDs --- tests/e2e/serial/main_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/e2e/serial/main_test.go b/tests/e2e/serial/main_test.go index c2b1faa738..67e06db0e9 100644 --- a/tests/e2e/serial/main_test.go +++ b/tests/e2e/serial/main_test.go @@ -2164,8 +2164,6 @@ func TestScanTailoredProfileExtendsDeprecated(t *testing.T) { } } -// Test author: xiyuan@redhat.com -// Ported from downstream tests: 60854, 60864 // TestHypershiftTailoredProfileScan tests scanning HyperShift hosted cluster // with tailored profiles for ocp4-cis and ocp4-pci-dss compliance profiles func TestHypershiftTailoredProfileScan(t *testing.T) { From 721840e497ac10ad14e346e61ea6c57f3b7e80e1 Mon Sep 17 00:00:00 2001 From: taimurhafeez Date: Wed, 11 Feb 2026 13:27:20 +0000 Subject: [PATCH 3/6] Modified framework to handle HyperShift clusters while creating/deleting resources --- tests/e2e/framework/common.go | 130 ++++++++++++++++------------------ 1 file changed, 63 insertions(+), 67 deletions(-) diff --git a/tests/e2e/framework/common.go b/tests/e2e/framework/common.go index 1c393d5ff8..c15a57bf6e 100644 --- a/tests/e2e/framework/common.go +++ b/tests/e2e/framework/common.go @@ -20,6 +20,7 @@ import ( imagev1 "github.com/openshift/api/image/v1" mcfgapi "github.com/openshift/api/machineconfiguration" mcfgv1 "github.com/openshift/api/machineconfiguration/v1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" batchv1 "k8s.io/api/batch/v1" core "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1" @@ -290,7 +291,7 @@ func (f *Framework) addFrameworks() error { } // MCO objects - if f.Platform != "rosa" { + if f.Platform != "rosa" && f.Platform != "HyperShift" { mcoObjs := [2]dynclient.ObjectList{ &mcfgv1.MachineConfigPoolList{}, &mcfgv1.MachineConfigList{}, @@ -337,6 +338,16 @@ func (f *Framework) addFrameworks() error { } } + // ValidatingAdmissionPolicy objects + vapObjs := [1]dynclient.ObjectList{ + &admissionregistrationv1.ValidatingAdmissionPolicyList{}, + } + for _, obj := range vapObjs { + if err := AddToFrameworkScheme(admissionregistrationv1.AddToScheme, obj); err != nil { + return fmt.Errorf("failed to add admissionregistration resource scheme to framework: %v", err) + } + } + return nil } @@ -600,7 +611,7 @@ func (f *Framework) GetReadyProfileBundle(name, namespace string) (*compv1alpha1 } func (f *Framework) updateScanSettingsForDebug() error { - if f.Platform == "rosa" { + if f.Platform == "rosa" || f.Platform == "HyperShift" { fmt.Printf("bypassing ScanSettings test setup because it's not supported on %s\n", f.Platform) return nil } @@ -622,7 +633,7 @@ func (f *Framework) updateScanSettingsForDebug() error { } func (f *Framework) ensureE2EScanSettings() error { - if f.Platform == "rosa" { + if f.Platform == "rosa" || f.Platform == "HyperShift" { fmt.Printf("bypassing ScanSettings test setup because it's not supported on %s\n", f.Platform) return nil } @@ -652,7 +663,7 @@ func (f *Framework) ensureE2EScanSettings() error { } func (f *Framework) deleteScanSettings(name string) error { - if f.Platform == "rosa" { + if f.Platform == "rosa" || f.Platform == "HyperShift" { fmt.Printf("bypassing ScanSettings test setup because it's not supported on %s\n", f.Platform) return nil } @@ -670,7 +681,7 @@ func (f *Framework) deleteScanSettings(name string) error { } func (f *Framework) createMachineConfigPool(n string) error { - if f.Platform == "rosa" { + if f.Platform == "rosa" || f.Platform == "HyperShift" { fmt.Printf("bypassing MachineConfigPool test setup because it's not supported on %s\n", f.Platform) return nil } @@ -791,11 +802,31 @@ func (f *Framework) createMachineConfigPool(n string) error { return nil } +// validatingAdmissionPolicyExists checks if a ValidatingAdmissionPolicy with the given name exists +func (f *Framework) validatingAdmissionPolicyExists(name string) (bool, error) { + vap := &admissionregistrationv1.ValidatingAdmissionPolicy{} + err := f.Client.Get(context.TODO(), types.NamespacedName{Name: name}, vap) + if err != nil { + if apierrors.IsNotFound(err) { + return false, nil + } + return false, err + } + return true, nil +} + func (f *Framework) createInvalidMachineConfigPool(n string) error { - if f.Platform == "rosa" { + if f.Platform == "rosa" || f.Platform == "HyperShift" { fmt.Printf("bypassing MachineConfigPool test setup because it's not supported on %s\n", f.Platform) return nil } + + // Check if ValidatingAdmissionPolicy exists + vapExists, err := f.validatingAdmissionPolicyExists("custom-machine-config-pool-selector") + if err != nil { + return fmt.Errorf("failed to check ValidatingAdmissionPolicy: %w", err) + } + p := &mcfgv1.MachineConfigPool{ ObjectMeta: metav1.ObjectMeta{Name: n}, Spec: mcfgv1.MachineConfigPoolSpec{ @@ -803,6 +834,30 @@ func (f *Framework) createInvalidMachineConfigPool(n string) error { }, } + // Only add selectors if ValidatingAdmissionPolicy exists + // This ensures backward compatibility with older clusters + if vapExists { + log.Printf("ValidatingAdmissionPolicy 'custom-machine-config-pool-selector' exists, adding minimal selectors to MachineConfigPool %s\n", n) + // Add minimal selectors to pass ValidatingAdmissionPolicy + // This pool is still "invalid" for testing as no nodes match this selector + p.Spec.NodeSelector = &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "node-role.kubernetes.io/e2e-invalid": "", + }, + } + p.Spec.MachineConfigSelector = &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "machineconfiguration.openshift.io/role", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"worker"}, + }, + }, + } + } else { + log.Printf("ValidatingAdmissionPolicy 'custom-machine-config-pool-selector' not found, creating MachineConfigPool %s without selectors (legacy mode)\n", n) + } + createErr := backoff.RetryNotify( func() error { err := f.Client.Create(context.TODO(), p, nil) @@ -823,7 +878,7 @@ func (f *Framework) createInvalidMachineConfigPool(n string) error { } func (f *Framework) cleanUpMachineConfigPool(n string) error { - if f.Platform == "rosa" { + if f.Platform == "rosa" || f.Platform == "HyperShift" { fmt.Printf("bypassing MachineConfigPool cleanup because it's not supported on %s\n", f.Platform) return nil } @@ -841,7 +896,7 @@ func (f *Framework) cleanUpMachineConfigPool(n string) error { } func (f *Framework) restoreNodeLabelsForPool(n string) error { - if f.Platform == "rosa" { + if f.Platform == "rosa" || f.Platform == "HyperShift" { fmt.Printf("bypassing node label restoration because MachineConfigPools are not supported on %s\n", f.Platform) return nil } @@ -1540,65 +1595,6 @@ func (f *Framework) AssertScanHasValidPVCReferenceWithSize(scanName, size, names return nil } -func (f *Framework) AssertARFReportExistsInPVC(scanName, namespace string) error { - pvcName, err := f.GetRawResultClaimNameFromScan(namespace, scanName) - if err != nil { - return err - } - - arfFormatCheckerPod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: scanName + "-arf-checker", - Namespace: namespace, - }, - Spec: core.PodSpec{ - RestartPolicy: core.RestartPolicyNever, - Containers: []core.Container{ - { - Name: "format-checker", - Image: "registry.access.redhat.com/ubi8/ubi-minimal", - Command: []string{"/bin/bash", "-c", "ls /scan-results/0 2>/dev/null | grep -q '.xml.bzip2' && exit 0 || exit 1"}, - VolumeMounts: []core.VolumeMount{ - { - Name: "scan-results", - MountPath: "/scan-results", - }, - }, - }, - }, - Volumes: []core.Volume{ - { - Name: "scan-results", - VolumeSource: core.VolumeSource{ - PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ - ClaimName: pvcName, - }, - }, - }, - }, - }, - } - if err = f.Client.Create(context.TODO(), arfFormatCheckerPod, nil); err != nil { - return fmt.Errorf("failed to create %s pod: %s", arfFormatCheckerPod.Name, err) - } - defer f.Client.Delete(context.TODO(), arfFormatCheckerPod) - - pod := &core.Pod{} - key := types.NamespacedName{Name: arfFormatCheckerPod.Name, Namespace: namespace} - return wait.Poll(2*time.Second, 10*time.Second, func() (bool, error) { - if err := f.Client.Get(context.TODO(), key, pod); err != nil { - return false, nil - } - if pod.Status.Phase == core.PodSucceeded { - return true, nil - } - if pod.Status.Phase == core.PodFailed { - return false, fmt.Errorf("Scan results in the PVC are not in the required ARF format (scan: %s, pvc: %s, pod: %s)", scanName, pvcName, pod.Name) - } - return false, nil - }) -} - func (f *Framework) AssertScanExists(name, namespace string) error { cs := &compv1alpha1.ComplianceScan{} defer f.logContainerOutput(namespace, name) From c2c0ed521acdfe5766d9925bfeffef7c8473eea6 Mon Sep 17 00:00:00 2001 From: taimurhafeez Date: Wed, 11 Feb 2026 14:04:55 +0000 Subject: [PATCH 4/6] Simplified platform check logic --- tests/e2e/serial/main_test.go | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/tests/e2e/serial/main_test.go b/tests/e2e/serial/main_test.go index 67e06db0e9..3a1a939486 100644 --- a/tests/e2e/serial/main_test.go +++ b/tests/e2e/serial/main_test.go @@ -2170,25 +2170,12 @@ func TestHypershiftTailoredProfileScan(t *testing.T) { f := framework.Global // Skip test if not running on a HyperShift cluster - infraList := configv1.InfrastructureList{} - err := f.Client.List(context.TODO(), &infraList) - if err != nil { - t.Fatalf("Failed to list infrastructures: %v", err) - } - - isHypershift := false - for _, infra := range infraList.Items { - if infra.Status.ControlPlaneTopology == configv1.ExternalTopologyMode { - isHypershift = true - break - } - } - - if !isHypershift { - t.Skip("Test requires HyperShift cluster (External control plane topology)") + if f.Platform != "HyperShift" { + t.Skip("Test requires HyperShift platform") } // Create tailored profile for ocp4-cis + var err error cisTPName := "test-hypershift-cis-tp" cisTP := &compv1alpha1.TailoredProfile{ ObjectMeta: metav1.ObjectMeta{ From 0677b360106d915a2026d58df491e88e6825ca11 Mon Sep 17 00:00:00 2001 From: taimurhafeez Date: Thu, 12 Feb 2026 12:44:42 +0000 Subject: [PATCH 5/6] Previously used common.go from pr-960, now used the one from master branch and added platform check for hypershift in conditons where rosa is being checked --- tests/e2e/framework/common.go | 118 ++++++++++++++++++---------------- 1 file changed, 61 insertions(+), 57 deletions(-) diff --git a/tests/e2e/framework/common.go b/tests/e2e/framework/common.go index c15a57bf6e..3ac099df83 100644 --- a/tests/e2e/framework/common.go +++ b/tests/e2e/framework/common.go @@ -20,7 +20,6 @@ import ( imagev1 "github.com/openshift/api/image/v1" mcfgapi "github.com/openshift/api/machineconfiguration" mcfgv1 "github.com/openshift/api/machineconfiguration/v1" - admissionregistrationv1 "k8s.io/api/admissionregistration/v1" batchv1 "k8s.io/api/batch/v1" core "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1" @@ -291,7 +290,7 @@ func (f *Framework) addFrameworks() error { } // MCO objects - if f.Platform != "rosa" && f.Platform != "HyperShift" { + if f.Platform != "rosa" || f.Platform == "HyperShift" { mcoObjs := [2]dynclient.ObjectList{ &mcfgv1.MachineConfigPoolList{}, &mcfgv1.MachineConfigList{}, @@ -305,7 +304,7 @@ func (f *Framework) addFrameworks() error { } // ClusterClaim objects - if f.Platform == "rosa" { + if f.Platform == "rosa" || f.Platform == "HyperShift" { ccObjs := [1]dynclient.ObjectList{ &clusterv1alpha1.ClusterClaimList{}, } @@ -338,16 +337,6 @@ func (f *Framework) addFrameworks() error { } } - // ValidatingAdmissionPolicy objects - vapObjs := [1]dynclient.ObjectList{ - &admissionregistrationv1.ValidatingAdmissionPolicyList{}, - } - for _, obj := range vapObjs { - if err := AddToFrameworkScheme(admissionregistrationv1.AddToScheme, obj); err != nil { - return fmt.Errorf("failed to add admissionregistration resource scheme to framework: %v", err) - } - } - return nil } @@ -802,31 +791,11 @@ func (f *Framework) createMachineConfigPool(n string) error { return nil } -// validatingAdmissionPolicyExists checks if a ValidatingAdmissionPolicy with the given name exists -func (f *Framework) validatingAdmissionPolicyExists(name string) (bool, error) { - vap := &admissionregistrationv1.ValidatingAdmissionPolicy{} - err := f.Client.Get(context.TODO(), types.NamespacedName{Name: name}, vap) - if err != nil { - if apierrors.IsNotFound(err) { - return false, nil - } - return false, err - } - return true, nil -} - func (f *Framework) createInvalidMachineConfigPool(n string) error { if f.Platform == "rosa" || f.Platform == "HyperShift" { fmt.Printf("bypassing MachineConfigPool test setup because it's not supported on %s\n", f.Platform) return nil } - - // Check if ValidatingAdmissionPolicy exists - vapExists, err := f.validatingAdmissionPolicyExists("custom-machine-config-pool-selector") - if err != nil { - return fmt.Errorf("failed to check ValidatingAdmissionPolicy: %w", err) - } - p := &mcfgv1.MachineConfigPool{ ObjectMeta: metav1.ObjectMeta{Name: n}, Spec: mcfgv1.MachineConfigPoolSpec{ @@ -834,30 +803,6 @@ func (f *Framework) createInvalidMachineConfigPool(n string) error { }, } - // Only add selectors if ValidatingAdmissionPolicy exists - // This ensures backward compatibility with older clusters - if vapExists { - log.Printf("ValidatingAdmissionPolicy 'custom-machine-config-pool-selector' exists, adding minimal selectors to MachineConfigPool %s\n", n) - // Add minimal selectors to pass ValidatingAdmissionPolicy - // This pool is still "invalid" for testing as no nodes match this selector - p.Spec.NodeSelector = &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "node-role.kubernetes.io/e2e-invalid": "", - }, - } - p.Spec.MachineConfigSelector = &metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: "machineconfiguration.openshift.io/role", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"worker"}, - }, - }, - } - } else { - log.Printf("ValidatingAdmissionPolicy 'custom-machine-config-pool-selector' not found, creating MachineConfigPool %s without selectors (legacy mode)\n", n) - } - createErr := backoff.RetryNotify( func() error { err := f.Client.Create(context.TODO(), p, nil) @@ -1595,6 +1540,65 @@ func (f *Framework) AssertScanHasValidPVCReferenceWithSize(scanName, size, names return nil } +func (f *Framework) AssertARFReportExistsInPVC(scanName, namespace string) error { + pvcName, err := f.GetRawResultClaimNameFromScan(namespace, scanName) + if err != nil { + return err + } + + arfFormatCheckerPod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: scanName + "-arf-checker", + Namespace: namespace, + }, + Spec: core.PodSpec{ + RestartPolicy: core.RestartPolicyNever, + Containers: []core.Container{ + { + Name: "format-checker", + Image: "registry.access.redhat.com/ubi8/ubi-minimal", + Command: []string{"/bin/bash", "-c", "ls /scan-results/0 2>/dev/null | grep -q '.xml.bzip2' && exit 0 || exit 1"}, + VolumeMounts: []core.VolumeMount{ + { + Name: "scan-results", + MountPath: "/scan-results", + }, + }, + }, + }, + Volumes: []core.Volume{ + { + Name: "scan-results", + VolumeSource: core.VolumeSource{ + PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ + ClaimName: pvcName, + }, + }, + }, + }, + }, + } + if err = f.Client.Create(context.TODO(), arfFormatCheckerPod, nil); err != nil { + return fmt.Errorf("failed to create %s pod: %s", arfFormatCheckerPod.Name, err) + } + defer f.Client.Delete(context.TODO(), arfFormatCheckerPod) + + pod := &core.Pod{} + key := types.NamespacedName{Name: arfFormatCheckerPod.Name, Namespace: namespace} + return wait.Poll(2*time.Second, 10*time.Second, func() (bool, error) { + if err := f.Client.Get(context.TODO(), key, pod); err != nil { + return false, nil + } + if pod.Status.Phase == core.PodSucceeded { + return true, nil + } + if pod.Status.Phase == core.PodFailed { + return false, fmt.Errorf("Scan results in the PVC are not in the required ARF format (scan: %s, pvc: %s, pod: %s)", scanName, pvcName, pod.Name) + } + return false, nil + }) +} + func (f *Framework) AssertScanExists(name, namespace string) error { cs := &compv1alpha1.ComplianceScan{} defer f.logContainerOutput(namespace, name) From 32fb6b61ce8b7a0b717b2cd4a02b4919e9385836 Mon Sep 17 00:00:00 2001 From: taimurhafeez Date: Mon, 16 Feb 2026 15:28:24 +0000 Subject: [PATCH 6/6] Fixed a if conditon where platforms are being checked --- tests/e2e/framework/common.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/framework/common.go b/tests/e2e/framework/common.go index 3ac099df83..e144d91c46 100644 --- a/tests/e2e/framework/common.go +++ b/tests/e2e/framework/common.go @@ -290,7 +290,7 @@ func (f *Framework) addFrameworks() error { } // MCO objects - if f.Platform != "rosa" || f.Platform == "HyperShift" { + if f.Platform != "rosa" && f.Platform != "HyperShift" { mcoObjs := [2]dynclient.ObjectList{ &mcfgv1.MachineConfigPoolList{}, &mcfgv1.MachineConfigList{}, @@ -304,7 +304,7 @@ func (f *Framework) addFrameworks() error { } // ClusterClaim objects - if f.Platform == "rosa" || f.Platform == "HyperShift" { + if f.Platform == "rosa" { ccObjs := [1]dynclient.ObjectList{ &clusterv1alpha1.ClusterClaimList{}, }