Skip to content

Commit

Permalink
converter for istio in-cluster operator config to sail operator config
Browse files Browse the repository at this point in the history
Signed-off-by: ctartici <[email protected]>
Signed-off-by: ctartici <[email protected]>
  • Loading branch information
ctartici committed Feb 7, 2025
1 parent 2a9e854 commit 6579d00
Show file tree
Hide file tree
Showing 5 changed files with 527 additions and 1 deletion.
18 changes: 17 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ spec:
pilot:
env:
PILOT_ENABLE_STATUS: "true"
version: v1.23.0
version: v1.24.0
namespace: istio-system
```

Expand All @@ -303,6 +303,22 @@ Note the quotes around the value of `spec.values.pilot.env.PILOT_ENABLE_STATUS`.

Sail Operator's Istio resource does not have a `spec.components` field. Instead, you can enable and disable components directly by setting `spec.values.<component>.enabled: true/false`. Other functionality exposed through `spec.components` like the k8s overlays is not currently available.

### Converter Script
This script is used to convert an Istio in-cluster operator configuration to a Sail Operator configuration. Upon execution, a new YAML file will be created in the same directory which ISTIO-OPERATOR-CONFIG-YAML is present. The new file will be named: sail-operator-config.yaml

#### Usage
It is necessary to set ISTIO_VERSION and ISTIO_NAMESPACE as environment variables to be able to run the script.

```bash
export ISTIO_VERSION=v1.23.0
export ISTIO_NAMESPACE=istio-system
./tools/configuration-converter.sh <ISTIO_OPERATOR_CONFIG_YAML_WITH_PATH>
```

> [!WARNING]
> This script is still under development.
> Please verify the resulting configuration carefully after conversion to ensure it meets your expectations and requirements.

### CNI

The CNI plugin's lifecycle is managed separately from the control plane. You will have to create a [IstioCNI resource](#istiocni-resource) to use CNI.
Expand Down
291 changes: 291 additions & 0 deletions tests/e2e/controlplane/control_plane_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
. "github.com/istio-ecosystem/sail-operator/pkg/test/util/ginkgo"
"github.com/istio-ecosystem/sail-operator/pkg/test/util/supportedversion"
"github.com/istio-ecosystem/sail-operator/tests/e2e/util/common"
"github.com/istio-ecosystem/sail-operator/tests/e2e/util/configconverter"
. "github.com/istio-ecosystem/sail-operator/tests/e2e/util/gomega"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -312,6 +313,295 @@ spec:
})
})

Describe("given Istio in-cluster operator configuration", func() {
var istioYamlFileWithPath string
var err error
for _, version := range supportedversion.List {
Context(version.Name, func() {
BeforeAll(func() {
Expect(k.CreateNamespace(controlPlaneNamespace)).To(Succeed(), "Istio namespace failed to be created")
Expect(k.CreateNamespace(istioCniNamespace)).To(Succeed(), "IstioCNI namespace failed to be created")
})

When("the IstioCNI CR is created", func() {
BeforeAll(func() {
yaml := `
apiVersion: sailoperator.io/v1
kind: IstioCNI
metadata:
name: default
spec:
version: %s
namespace: %s`
yaml = fmt.Sprintf(yaml, version.Name, istioCniNamespace)
Log("IstioCNI YAML:", indent(2, yaml))
Expect(k.CreateFromString(yaml)).To(Succeed(), "IstioCNI creation failed")
Success("IstioCNI created")
})

It("deploys the CNI DaemonSet", func(ctx SpecContext) {
Eventually(func(g Gomega) {
daemonset := &appsv1.DaemonSet{}
g.Expect(cl.Get(ctx, kube.Key("istio-cni-node", istioCniNamespace), daemonset)).To(Succeed(), "Error getting IstioCNI DaemonSet")
g.Expect(daemonset.Status.NumberAvailable).
To(Equal(daemonset.Status.CurrentNumberScheduled), "CNI DaemonSet Pods not Available; expected numberAvailable to be equal to currentNumberScheduled")
}).Should(Succeed(), "CNI DaemonSet Pods are not Available")
Success("CNI DaemonSet is deployed in the namespace and Running")
})

It("uses the correct image", func(ctx SpecContext) {
Expect(common.GetObject(ctx, cl, kube.Key("istio-cni-node", istioCniNamespace), &appsv1.DaemonSet{})).
To(HaveContainersThat(HaveEach(ImageFromRegistry(expectedRegistry))))
})

It("updates the status to Reconciled", func(ctx SpecContext) {
Eventually(common.GetObject).WithArguments(ctx, cl, kube.Key(istioCniName), &v1.IstioCNI{}).
Should(HaveCondition(v1.IstioCNIConditionReconciled, metav1.ConditionTrue), "IstioCNI is not Reconciled; unexpected Condition")
Success("IstioCNI is Reconciled")
})

It("updates the status to Ready", func(ctx SpecContext) {
Eventually(common.GetObject).WithArguments(ctx, cl, kube.Key(istioCniName), &v1.IstioCNI{}).
Should(HaveCondition(v1.IstioCNIConditionReady, metav1.ConditionTrue), "IstioCNI is not Ready; unexpected Condition")
Success("IstioCNI is Ready")
})

It("doesn't continuously reconcile the IstioCNI CR", func() {
Eventually(k.WithNamespace(namespace).Logs).WithArguments("deploy/"+deploymentName, ptr.Of(30*time.Second)).
ShouldNot(ContainSubstring("Reconciliation done"), "IstioCNI is continuously reconciling")
Success("IstioCNI stopped reconciling")
})
})

When("Sail Operator configuration", func() {
BeforeAll(func() {
Expect(configconverter.SetEnvVariables("ISTIO_VERSION", version.Name)).To(Succeed(), "error adding env variable: ISTIO_VERSION")
Expect(configconverter.SetEnvVariables("ISTIO_NAMESPACE", controlPlaneNamespace)).To(Succeed(), "error adding env variable: ISTIO_NAMESPACE")
istioYamlText := `
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
name: default
spec:
components:
pilot:
enabled: true
k8s:
resources:
requests:
cpu: 1000m # override from default 500m
memory: 4096Mi # ... default 2048Mi
hpaSpec:
maxReplicas: 10 # ... default 5
minReplicas: 2 # ... default 1
nodeSelector: # ... default empty
master: "true"
tolerations: # ... default empty
- key: dedicated
operator: Exists
effect: NoSchedule
- key: CriticalAddonsOnly
operator: Exists
values:
global:
istiod:
enableAnalysis: true
pilot:
env:
PILOT_ENABLE_STATUS: true`
Log("Istio in-cluster Operator Config YAML:", indent(2, istioYamlText))
istioYamlFileWithPath, err = configconverter.SaveYamlToFile(istioYamlText)
Expect(err).To(Succeed(), "failed to write YAML file")
Success("Provided istio config saved to file")
})

It("is created", func() {
Expect(configconverter.ExecuteConfigConverter(istioYamlFileWithPath)).To(Succeed(), "error in execution of ./configuration-converter.sh")
Success("Sail Operator Yaml Created")
})

It("is validated", func() {
sailYamlText := `
apiVersion: sailoperator.io/v1
kind: Istio
metadata:
name: default
spec:
values:
global:
istiod:
enableAnalysis: true
pilot:
env:
PILOT_ENABLE_STATUS: "true"
enabled: true
version: %s
namespace: %s`
sailYamlText = fmt.Sprintf(sailYamlText, version.Name, controlPlaneNamespace)
isConversionValidated, err := configconverter.ValidateYamlContent(sailYamlText)
Expect(err).To(Succeed(), "failed to read file")
Expect(isConversionValidated).To(BeTrue(), "YAML content is not as wanted")
Success("Conversion completed")
})
})

When("the Istio CR is created", func() {
BeforeAll(func() {
sailYAML := `
apiVersion: sailoperator.io/v1
kind: Istio
metadata:
name: default
spec:
values:
global:
istiod:
enableAnalysis: true
pilot:
env:
PILOT_ENABLE_STATUS: "true"
enabled: true
version: %s
namespace: %s`
istioYAML := fmt.Sprintf(sailYAML, version.Name, controlPlaneNamespace)
Log("Istio YAML:", indent(2, istioYAML))
Expect(k.CreateFromString(istioYAML)).
To(Succeed(), "Istio CR failed to be created")
Success("Istio CR created")
})

It("updates the Istio CR status to Reconciled", func(ctx SpecContext) {
Eventually(common.GetObject).WithArguments(ctx, cl, kube.Key(istioName), &v1.Istio{}).
Should(HaveCondition(v1.IstioConditionReconciled, metav1.ConditionTrue), "Istio is not Reconciled; unexpected Condition")
Success("Istio CR is Reconciled")
})

It("updates the Istio CR status to Ready", func(ctx SpecContext) {
Eventually(common.GetObject).WithArguments(ctx, cl, kube.Key(istioName), &v1.Istio{}).
Should(HaveCondition(v1.IstioConditionReady, metav1.ConditionTrue), "Istio is not Ready; unexpected Condition")
Success("Istio CR is Ready")
})

It("deploys istiod", func(ctx SpecContext) {
Eventually(common.GetObject).WithArguments(ctx, cl, kube.Key("istiod", controlPlaneNamespace), &appsv1.Deployment{}).
Should(HaveCondition(appsv1.DeploymentAvailable, metav1.ConditionTrue), "Istiod is not Available; unexpected Condition")
Expect(common.GetVersionFromIstiod()).To(Equal(version.Version), "Unexpected istiod version")
Success("Istiod is deployed in the namespace and Running")
})

It("uses the correct image", func(ctx SpecContext) {
Expect(common.GetObject(ctx, cl, kube.Key("istiod", controlPlaneNamespace), &appsv1.Deployment{})).
To(HaveContainersThat(HaveEach(ImageFromRegistry(expectedRegistry))))
})

It("doesn't continuously reconcile the Istio CR", func() {
Eventually(k.WithNamespace(namespace).Logs).WithArguments("deploy/"+deploymentName, ptr.Of(30*time.Second)).
ShouldNot(ContainSubstring("Reconciliation done"), "Istio CR is continuously reconciling")
Success("Istio CR stopped reconciling")
})
})

When("sample pod is deployed", func() {
BeforeAll(func() {
Expect(k.CreateNamespace(sampleNamespace)).To(Succeed(), "Sample namespace failed to be created")
Expect(k.Patch("namespace", sampleNamespace, "merge", `{"metadata":{"labels":{"istio-injection":"enabled"}}}`)).
To(Succeed(), "Error patching sample namespace")
Expect(k.WithNamespace(sampleNamespace).
ApplyWithLabels(common.GetSampleYAML(version, sampleNamespace), "version=v1")).
To(Succeed(), "Error deploying sample")
Success("sample deployed")
})

samplePods := &corev1.PodList{}

It("updates the pods status to Running", func(ctx SpecContext) {
Eventually(func() bool {
// Wait until the sample pod exists. Is wraped inside a function to avoid failure on the first iteration
Expect(cl.List(ctx, samplePods, client.InNamespace(sampleNamespace))).To(Succeed())
return len(samplePods.Items) > 0
}).Should(BeTrue(), "No sample pods found")

Expect(cl.List(ctx, samplePods, client.InNamespace(sampleNamespace))).To(Succeed())
Expect(samplePods.Items).ToNot(BeEmpty(), "No pods found in sample namespace")

for _, pod := range samplePods.Items {
Eventually(common.GetObject).WithArguments(ctx, cl, kube.Key(pod.Name, sampleNamespace), &corev1.Pod{}).
Should(HaveCondition(corev1.PodReady, metav1.ConditionTrue), "Pod is not Ready")
}
Success("sample pods are ready")
})

It("has sidecars with the correct istio version", func(ctx SpecContext) {
for _, pod := range samplePods.Items {
sidecarVersion, err := getProxyVersion(pod.Name, sampleNamespace)
Expect(err).NotTo(HaveOccurred(), "Error getting sidecar version")
Expect(sidecarVersion).To(Equal(version.Version), "Sidecar Istio version does not match the expected version")
}
Success("Istio sidecar version matches the expected Istio version")
})

AfterAll(func(ctx SpecContext) {
By("Deleting sample")
Expect(k.DeleteNamespace(sampleNamespace)).To(Succeed(), "sample namespace failed to be deleted")
Success("sample deleted")
})
})

When("the Istio CR is deleted", func() {
BeforeEach(func() {
Expect(k.Delete("istio", istioName)).To(Succeed(), "Istio CR failed to be deleted")
Success("Istio CR deleted")
})

It("removes everything from the namespace", func(ctx SpecContext) {
Eventually(cl.Get).WithArguments(ctx, kube.Key("istiod", controlPlaneNamespace), &appsv1.Deployment{}).
Should(ReturnNotFoundError(), "Istiod should not exist anymore")
common.CheckNamespaceEmpty(ctx, cl, controlPlaneNamespace)
Success("Namespace is empty")
})
})

When("the IstioCNI CR is deleted", func() {
BeforeEach(func() {
Expect(k.Delete("istiocni", istioCniName)).To(Succeed(), "IstioCNI CR failed to be deleted")
Success("IstioCNI deleted")
})

It("removes everything from the CNI namespace", func(ctx SpecContext) {
daemonset := &appsv1.DaemonSet{}
Eventually(cl.Get).WithArguments(ctx, kube.Key("istio-cni-node", istioCniNamespace), daemonset).
Should(ReturnNotFoundError(), "IstioCNI DaemonSet should not exist anymore")
common.CheckNamespaceEmpty(ctx, cl, istioCniNamespace)
Success("CNI namespace is empty")
})
})
})
}
AfterAll(func(ctx SpecContext) {
if CurrentSpecReport().Failed() {
common.LogDebugInfo(k)
debugInfoLogged = true
}

By("Cleaning up the Istio namespace")
Expect(k.DeleteNamespace(controlPlaneNamespace)).To(Succeed(), "Istio Namespace failed to be deleted")

By("Cleaning up the IstioCNI namespace")
Expect(k.DeleteNamespace(istioCniNamespace)).To(Succeed(), "IstioCNI Namespace failed to be deleted")

By("Deleting any left-over Istio and IstioRevision resources")
Expect(forceDeleteIstioResources()).To(Succeed())
Success("Resources deleted")

By("Cleaning up the Istio namespace")
Expect(configconverter.DeleteFile("IstioConfig.yaml")).To(Succeed(), "Created file for converter failed to be deleted")
Expect(configconverter.DeleteFile("sail-operator-config.yaml")).To(Succeed(), "Created file for converter failed to be deleted")
Success("Configuration Converter files deleted")

Success("Cleanup done")
})
})

AfterAll(func() {
if CurrentSpecReport().Failed() && !debugInfoLogged {
common.LogDebugInfo(k)
Expand Down Expand Up @@ -341,6 +631,7 @@ func ImageFromRegistry(regexp string) types.GomegaMatcher {
return HaveField("Image", MatchRegexp(regexp))
}

//nolint:unparam
func indent(level int, str string) string {
indent := strings.Repeat(" ", level)
return indent + strings.ReplaceAll(str, "\n", "\n"+indent)
Expand Down
Loading

0 comments on commit 6579d00

Please sign in to comment.