Skip to content

Commit

Permalink
[KOGITO-8648] [KSW-Operator] Implement the Knative Addressable interf… (
Browse files Browse the repository at this point in the history
#124)

* [KOGITO-8648] [KSW-Operator] Implement the Knative Addressable interface in dev profile

Signed-off-by: Davide Salerno <dsalerno@redhat.com>

Update controllers/profiles/status_enricher_dev_test.go

Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com>

Fixing namespace into unit tests

Signed-off-by: Davide Salerno <dsalerno@redhat.com>

Update utils/kubernetes/service.go

Co-authored-by: Filippe Spolti <filippespolti@gmail.com>

Changes coming from code review

Signed-off-by: Davide Salerno <dsalerno@redhat.com>

* fix generate-all

---------

Signed-off-by: Davide Salerno <dsalerno@redhat.com>
Co-authored-by: radtriste <tradisso@redhat.com>
  • Loading branch information
davidesalerno and radtriste authored Jun 2, 2023
1 parent 0190a5f commit 46e17e7
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: sw.kogito.kie.org/v1alpha08
kind: KogitoServerlessPlatform
metadata:
name: kogito-workflow-platform
spec:
platform:
buildStrategy: operator
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ metadata:
name: kogito-workflow-platform
spec:
platform:

baseImage: quay.io/kiegroup/kogito-swf-builder-nightly:latest
buildStrategyOptions:
KanikoBuildCacheEnabled: "true"
19 changes: 19 additions & 0 deletions controllers/profiles/status_enricher_dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

openshiftv1 "github.com/openshift/api/route/v1"
"knative.dev/pkg/apis"
duckv1 "knative.dev/pkg/apis/duck/v1"

"github.com/kiegroup/kogito-serverless-operator/controllers/workflowdef"

Expand All @@ -28,6 +29,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"

operatorapi "github.com/kiegroup/kogito-serverless-operator/api/v1alpha08"
"github.com/kiegroup/kogito-serverless-operator/utils/kubernetes"
)

func defaultDevStatusEnricher(ctx context.Context, c client.Client, workflow *operatorapi.KogitoServerlessWorkflow) (client.Object, error) {
Expand All @@ -36,10 +38,12 @@ func defaultDevStatusEnricher(ctx context.Context, c client.Client, workflow *op
// - Address the service can be reached
// - Node port used
service := &v1.Service{}

err := c.Get(ctx, types.NamespacedName{Namespace: workflow.Namespace, Name: workflow.Name}, service)
if err != nil {
return nil, err
}

//If the service has got a Port that is a nodePort we have to use it to create the workflow's NodePort Endpoint
if service.Spec.Ports != nil && len(service.Spec.Ports) > 0 {
if port := findNodePortFromPorts(service.Spec.Ports); port > 0 {
Expand All @@ -66,6 +70,14 @@ func defaultDevStatusEnricher(ctx context.Context, c client.Client, workflow *op
}
workflow.Status.Endpoint = url
}

address, err := kubernetes.RetrieveServiceURL(service)
if err != nil {
return nil, err
}
workflow.Status.Address = duckv1.Addressable{
URL: address,
}
}

return workflow, nil
Expand All @@ -84,8 +96,15 @@ func devStatusEnricherForOpenShift(ctx context.Context, client client.Client, wo
} else {
url = apis.HTTP(route.Spec.Host)
}
url.Path = workflow.Name

workflow.Status.Endpoint = url

if err != nil {
return nil, err
}
workflow.Status.Address = duckv1.Addressable{
URL: url,
}
return workflow, nil
}
84 changes: 84 additions & 0 deletions controllers/profiles/status_enricher_dev_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright 2023 Red Hat, Inc. and/or its affiliates
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package profiles

import (
"context"
"strings"
"testing"

openshiftv1 "github.com/openshift/api/route/v1"
"github.com/stretchr/testify/assert"
"knative.dev/pkg/apis"

apiv08 "github.com/kiegroup/kogito-serverless-operator/api/v1alpha08"
"github.com/kiegroup/kogito-serverless-operator/test"
)

func Test_enrichmentStatusOnK8s(t *testing.T) {
t.Run("verify that the service URL is returned with the default cluster name on default namespace", func(t *testing.T) {

workflow := test.GetKogitoServerlessWorkflow("../../config/samples/"+test.KogitoServerlessWorkflowSampleDevModeYamlCR, t.Name())
workflow.Namespace = toK8SNamespace(t.Name())
service, err := defaultServiceCreator(workflow)
client := test.NewKogitoClientBuilder().WithRuntimeObjects(workflow, service).Build()
obj, err := defaultDevStatusEnricher(context.TODO(), client, workflow)

reflectWorkflow := obj.(*apiv08.KogitoServerlessWorkflow)
assert.NoError(t, err)
assert.NotNil(t, obj)
assert.NotNil(t, reflectWorkflow.Status.Address)
assert.Equal(t, reflectWorkflow.Status.Address.URL.String(), "http://"+workflow.Name+"."+workflow.Namespace+".svc.cluster.local/"+workflow.Name)

})

t.Run("verify that the service URL won't be generated if an invalid namespace is used", func(t *testing.T) {

workflow := test.GetKogitoServerlessWorkflow("../../config/samples/"+test.KogitoServerlessWorkflowSampleDevModeYamlCR, t.Name())
workflow.Namespace = t.Name()
service, err := defaultServiceCreator(workflow)
client := test.NewKogitoClientBuilder().WithRuntimeObjects(workflow, service).Build()
_, err = defaultDevStatusEnricher(context.TODO(), client, workflow)
assert.Error(t, err)

})
}

func Test_enrichmentStatusOnOCP(t *testing.T) {
t.Run("verify that the service URL is returned with the default cluster name on default namespace", func(t *testing.T) {
workflow := test.GetKogitoServerlessWorkflow("../../config/samples/"+test.KogitoServerlessWorkflowSampleDevModeYamlCR, t.Name())
workflow.Namespace = toK8SNamespace(t.Name())
service, err := defaultServiceCreator(workflow)
route := &openshiftv1.Route{}
route.Name = workflow.Name
route.Namespace = workflow.Namespace
route.Spec.Host = workflow.Name + "." + workflow.Namespace + ".apps-crc.testing"
client := test.NewKogitoClientBuilderWithOpenShift().WithRuntimeObjects(workflow, service, route).Build()
obj, err := devStatusEnricherForOpenShift(context.TODO(), client, workflow)

reflectWorkflow := obj.(*apiv08.KogitoServerlessWorkflow)
assert.NoError(t, err)
assert.NotNil(t, obj)
assert.NotNil(t, reflectWorkflow.Status.Address)
expectedURL := apis.HTTP(route.Spec.Host)
expectedURL.Path = workflow.Name
assert.Equal(t, reflectWorkflow.Status.Address.URL.String(), expectedURL.String())

})
}

func toK8SNamespace(testName string) string {
return strings.ToLower(strings.Replace(strings.Split(testName, "/")[0], "_", "-", 1))
}
2 changes: 1 addition & 1 deletion hack/local/run-e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ echo "Using minikube profile ${MINIKUBE_PROFILE}"

export OPERATOR_IMAGE_NAME=localhost/kogito-serverless-operator:0.0.1
eval "$(minikube -p "${MINIKUBE_PROFILE}" docker-env)"
if ! make container-build BUILDER=docker IMG="${OPERATOR_IMAGE_NAME}"; then
if ! make docker-build IMG="${OPERATOR_IMAGE_NAME}"; then
echo "Failure: Failed to build image, exiting " >&2
exit 1
fi
Expand Down
29 changes: 28 additions & 1 deletion test/e2e/workflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package e2e

import (
"fmt"
"net/url"
"os/exec"
"path/filepath"
"strconv"
Expand Down Expand Up @@ -169,6 +170,9 @@ var _ = Describe("Kogito Serverless Operator", Ordered, func() {
By("removing manager namespace")
cmd := exec.Command("make", "undeploy")
_, _ = utils.Run(cmd)
By("uninstalling CRDs")
cmd = exec.Command("make", "uninstall")
_, _ = utils.Run(cmd)
})

Describe("ensure that Operator and Operand(s) can run in restricted namespaces", func() {
Expand All @@ -178,7 +182,7 @@ var _ = Describe("Kogito Serverless Operator", Ordered, func() {
By("creating an instance of the Kogito Serverless Platform")
EventuallyWithOffset(1, func() error {
cmd := exec.Command("kubectl", "apply", "-f", filepath.Join(projectDir,
"config/samples/"+test.KogitoServerlessPlatformWithCacheMinikubeYamlCR), "-n", namespace)
"config/samples/"+test.KogitoServerlessPlatformMinikubeYamlCR), "-n", namespace)
_, err := utils.Run(cmd)
return err
}, time.Minute, time.Second).Should(Succeed())
Expand Down Expand Up @@ -222,6 +226,9 @@ var _ = Describe("Kogito Serverless Operator", Ordered, func() {
GinkgoWriter.Println(fmt.Sprintf("builder podlog %s", responseLog))
}

By("check that the workflow is addressable")
EventuallyWithOffset(1, verifyWorkflowIsAddressable, 5*time.Minute, 30*time.Second).Should(BeTrue())

EventuallyWithOffset(1, func() error {
cmd := exec.Command("kubectl", "delete", "-f", filepath.Join(projectDir,
"config/samples/"+test.KogitoServerlessWorkflowSampleDevModeYamlCR), "-n", namespace)
Expand Down Expand Up @@ -251,3 +258,23 @@ func verifyWorkflowIsInRunningState() bool {
return false
}
}

func verifyWorkflowIsAddressable() bool {
cmd := exec.Command("kubectl", "get", "workflow", "greeting", "-n", namespace, "-o", "jsonpath={.status.address.url}")
if response, err := utils.Run(cmd); err != nil {
GinkgoWriter.Println(fmt.Errorf("failed to check if greeting workflow is running: %v", err))
return false
} else {
GinkgoWriter.Println(fmt.Sprintf("Got response %s", response))
if len(strings.TrimSpace(string(response))) > 0 {
_, err := url.ParseRequestURI(string(response))
if err != nil {
GinkgoWriter.Println(fmt.Errorf("failed to parse result %v", err))
return false
}
// The response is a valid URL so the test is passed
return true
}
return false
}
}
1 change: 1 addition & 0 deletions test/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const (
KogitoServerlessWorkflowSampleDevModeWithExternalResourceYamlCR = "sw.kogito_v1alpha08_kogitoserverlessworkflow_devmodeWithExternalResource.yaml"
KogitoServerlessWorkflowProdProfileSampleYamlCR = "sw.kogito_v1alpha08_kogitoserverlessworkflow_withExplicitProdProfile.yaml"
KogitoServerlessPlatformWithCacheYamlCR = "sw.kogito_v1alpha08_kogitoserverlessplatform_withCacheAndCustomization.yaml"
KogitoServerlessPlatformMinikubeYamlCR = "sw.kogito_v1alpha08_kogitoserverlessplatform_minikube.yaml"
KogitoServerlessPlatformWithCacheMinikubeYamlCR = "sw.kogito_v1alpha08_kogitoserverlessplatform_withCache_minikube.yaml"
KogitoServerlessPlatformYamlCR = "sw.kogito_v1alpha08_kogitoserverlessplatform.yaml"
KogitoServerlessPlatformWithBaseImageYamlCR = "sw.kogito_v1alpha08_kogitoserverlessplatformWithBaseImage.yaml"
Expand Down
44 changes: 44 additions & 0 deletions utils/kubernetes/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2023 Red Hat, Inc. and/or its affiliates
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubernetes

import (
"net/url"

v1 "k8s.io/api/core/v1"
"knative.dev/pkg/apis"
)

// TODO: retrieve the cluster domain from the /etc/resolve inside the pod or from the Platform CRD - will be addressed by KOGITO-9198
var defaultClusterDomain = "svc.cluster.local"

// retrieveServiceHost function that based on the service name, namespace and eventually the nodeport, will provide the service URI
func retrieveServiceHost(service *v1.Service) string {
namespace := service.Namespace
if len(namespace) == 0 {
namespace = "default"
}
// TODO: Retrieve the cluster domain or use the default one
return service.Name + "." + namespace + "." + defaultClusterDomain
}

// RetrieveServiceURL function that based on the service name, namespace and eventually the nodeport, will provide the service URI
func RetrieveServiceURL(service *v1.Service) (*apis.URL, error) {
url := url.URL{
Scheme: "http",
Host: retrieveServiceHost(service),
Path: service.Name}
return apis.ParseURL(url.String())
}
75 changes: 75 additions & 0 deletions utils/kubernetes/service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright 2023 Red Hat, Inc. and/or its affiliates
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubernetes

import (
"testing"

"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
)

func Test_retrievingKubernetesServiceHost(t *testing.T) {
t.Run("verify that the service host is returned with the default cluster name on default namespace", func(t *testing.T) {
svc := &v1.Service{}
svc.Name = "workflow"
host := retrieveServiceHost(svc)

assert.NotNil(t, host)
assert.Equal(t, host, svc.Name+".default.svc.cluster.local")

})

t.Run("verify that the service host is returned with the default cluster name on non-default namespace", func(t *testing.T) {
svc := &v1.Service{}
svc.Name = "workflow"
svc.Namespace = "ns"
host := retrieveServiceHost(svc)

assert.NotNil(t, host)
assert.Equal(t, host, svc.Name+"."+svc.Namespace+".svc.cluster.local")

})
}

func Test_retrievingKubernetesServiceURL(t *testing.T) {
t.Run("verify that the service URL is returned with the default cluster name on default namespace", func(t *testing.T) {
svc := &v1.Service{}
svc.Name = "workflow"
RetrieveServiceURL(svc)

url, err := RetrieveServiceURL(svc)

assert.NoError(t, err)
assert.NotNil(t, url)
assert.Equal(t, url.String(), "http://"+svc.Name+".default.svc.cluster.local/"+svc.Name)

})

t.Run("verify that the service URL is returned with the default cluster name on non-default namespace", func(t *testing.T) {
svc := &v1.Service{}
svc.Name = "workflow"
svc.Namespace = "ns"
RetrieveServiceURL(svc)

url, err := RetrieveServiceURL(svc)

assert.NoError(t, err)
assert.NotNil(t, url)
assert.Equal(t, url.String(), "http://"+svc.Name+"."+svc.Namespace+".svc.cluster.local/"+svc.Name)

})

}

0 comments on commit 46e17e7

Please sign in to comment.