Skip to content

Commit

Permalink
feat: Version limitation (#1493)
Browse files Browse the repository at this point in the history
  • Loading branch information
yodigos authored Sep 11, 2024
1 parent 8052e3d commit 3a19cef
Show file tree
Hide file tree
Showing 11 changed files with 389 additions and 19 deletions.
47 changes: 29 additions & 18 deletions instrumentor/controllers/instrumentationdevice/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
odigosv1 "github.com/odigos-io/odigos/api/odigos/v1alpha1"
"github.com/odigos-io/odigos/common/consts"
"github.com/odigos-io/odigos/instrumentor/controllers/utils/versionsupport"
"github.com/odigos-io/odigos/instrumentor/instrumentation"
"github.com/odigos-io/odigos/k8sutils/pkg/conditions"
"github.com/odigos-io/odigos/k8sutils/pkg/env"
Expand All @@ -21,11 +22,11 @@ import (
type ApplyInstrumentationDeviceReason string

const (
ApplyInstrumentationDeviceReasonDataCollectionNotReady ApplyInstrumentationDeviceReason = "DataCollectionNotReady"
ApplyInstrumentationDeviceReasonNoRuntimeDetails ApplyInstrumentationDeviceReason = "NoRuntimeDetails"
ApplyInstrumentationDeviceReasonErrApplying ApplyInstrumentationDeviceReason = "ErrApplyingInstrumentationDevice"
ApplyInstrumentationDeviceReasonErrRemoving ApplyInstrumentationDeviceReason = "ErrRemovingInstrumentationDevice"
ApplyInstrumentationDeviceReasonNotSupported ApplyInstrumentationDeviceReason = "ErrVersionNotSupported"
ApplyInstrumentationDeviceReasonDataCollectionNotReady ApplyInstrumentationDeviceReason = "DataCollectionNotReady"
ApplyInstrumentationDeviceReasonNoRuntimeDetails ApplyInstrumentationDeviceReason = "NoRuntimeDetails"
ApplyInstrumentationDeviceReasonErrApplying ApplyInstrumentationDeviceReason = "ErrApplyingInstrumentationDevice"
ApplyInstrumentationDeviceReasonErrRemoving ApplyInstrumentationDeviceReason = "ErrRemovingInstrumentationDevice"
ApplyInstrumentationDeviceReasonRuntimeVersionNotSupported ApplyInstrumentationDeviceReason = "RuntimeVersionNotSupported"
)

const (
Expand Down Expand Up @@ -182,39 +183,49 @@ func getPodSpecFromObject(obj client.Object) (*corev1.PodTemplateSpec, error) {
// reconciles a single workload, which might be triggered by a change in multiple resources.
// each time a relevant resource changes, this function is called to reconcile the workload
// and always writes the status into the InstrumentedApplication CR
func reconcileSingleWorkload(ctx context.Context, kubeClient client.Client, runtimeDetails *odigosv1.InstrumentedApplication, isNodeCollectorReady bool) error {
func reconcileSingleWorkload(ctx context.Context, kubeClient client.Client, instrumentedApplication *odigosv1.InstrumentedApplication, isNodeCollectorReady bool) error {

workloadName, workloadKind, err := workload.ExtractWorkloadInfoFromRuntimeObjectName(runtimeDetails.Name)
workloadName, workloadKind, err := workload.ExtractWorkloadInfoFromRuntimeObjectName(instrumentedApplication.Name)
if err != nil {
conditions.UpdateStatusConditions(ctx, kubeClient, runtimeDetails, &runtimeDetails.Status.Conditions, metav1.ConditionFalse, appliedInstrumentationDeviceType, string(ApplyInstrumentationDeviceReasonErrRemoving), err.Error())
conditions.UpdateStatusConditions(ctx, kubeClient, instrumentedApplication, &instrumentedApplication.Status.Conditions, metav1.ConditionFalse, appliedInstrumentationDeviceType, string(ApplyInstrumentationDeviceReasonErrRemoving), err.Error())
return err
}

if !isNodeCollectorReady {
err := removeInstrumentationDeviceFromWorkload(ctx, kubeClient, runtimeDetails.Namespace, workloadKind, workloadName, ApplyInstrumentationDeviceReasonDataCollectionNotReady)
err := removeInstrumentationDeviceFromWorkload(ctx, kubeClient, instrumentedApplication.Namespace, workloadKind, workloadName, ApplyInstrumentationDeviceReasonDataCollectionNotReady)
if err == nil {
conditions.UpdateStatusConditions(ctx, kubeClient, runtimeDetails, &runtimeDetails.Status.Conditions, metav1.ConditionFalse, appliedInstrumentationDeviceType, string(ApplyInstrumentationDeviceReasonDataCollectionNotReady), "OpenTelemetry pipeline not yet ready to receive data")
conditions.UpdateStatusConditions(ctx, kubeClient, instrumentedApplication, &instrumentedApplication.Status.Conditions, metav1.ConditionFalse, appliedInstrumentationDeviceType, string(ApplyInstrumentationDeviceReasonDataCollectionNotReady), "OpenTelemetry pipeline not yet ready to receive data")
} else {
conditions.UpdateStatusConditions(ctx, kubeClient, runtimeDetails, &runtimeDetails.Status.Conditions, metav1.ConditionFalse, appliedInstrumentationDeviceType, string(ApplyInstrumentationDeviceReasonErrRemoving), err.Error())
conditions.UpdateStatusConditions(ctx, kubeClient, instrumentedApplication, &instrumentedApplication.Status.Conditions, metav1.ConditionFalse, appliedInstrumentationDeviceType, string(ApplyInstrumentationDeviceReasonErrRemoving), err.Error())
}
return err
}

if len(runtimeDetails.Spec.RuntimeDetails) == 0 {
err := removeInstrumentationDeviceFromWorkload(ctx, kubeClient, runtimeDetails.Namespace, workloadKind, workloadName, ApplyInstrumentationDeviceReasonNoRuntimeDetails)
if len(instrumentedApplication.Spec.RuntimeDetails) == 0 {
err := removeInstrumentationDeviceFromWorkload(ctx, kubeClient, instrumentedApplication.Namespace, workloadKind, workloadName, ApplyInstrumentationDeviceReasonNoRuntimeDetails)
if err == nil {
conditions.UpdateStatusConditions(ctx, kubeClient, runtimeDetails, &runtimeDetails.Status.Conditions, metav1.ConditionFalse, appliedInstrumentationDeviceType, string(ApplyInstrumentationDeviceReasonNoRuntimeDetails), "No runtime details found")
conditions.UpdateStatusConditions(ctx, kubeClient, instrumentedApplication, &instrumentedApplication.Status.Conditions, metav1.ConditionFalse, appliedInstrumentationDeviceType, string(ApplyInstrumentationDeviceReasonNoRuntimeDetails), "No runtime details found")
} else {
conditions.UpdateStatusConditions(ctx, kubeClient, runtimeDetails, &runtimeDetails.Status.Conditions, metav1.ConditionFalse, appliedInstrumentationDeviceType, string(ApplyInstrumentationDeviceReasonErrRemoving), err.Error())
conditions.UpdateStatusConditions(ctx, kubeClient, instrumentedApplication, &instrumentedApplication.Status.Conditions, metav1.ConditionFalse, appliedInstrumentationDeviceType, string(ApplyInstrumentationDeviceReasonErrRemoving), err.Error())
}
return err
}
runtimeVersionSupport, err := versionsupport.IsRuntimeVersionSupported(ctx, instrumentedApplication.Spec.RuntimeDetails)
if !runtimeVersionSupport {
err := removeInstrumentationDeviceFromWorkload(ctx, kubeClient, instrumentedApplication.Namespace, workloadKind, workloadName, ApplyInstrumentationDeviceReasonRuntimeVersionNotSupported)
if err == nil {
conditions.UpdateStatusConditions(ctx, kubeClient, instrumentedApplication, &instrumentedApplication.Status.Conditions, metav1.ConditionFalse, appliedInstrumentationDeviceType, string(ApplyInstrumentationDeviceReasonRuntimeVersionNotSupported), err.Error())
} else {
conditions.UpdateStatusConditions(ctx, kubeClient, instrumentedApplication, &instrumentedApplication.Status.Conditions, metav1.ConditionFalse, appliedInstrumentationDeviceType, string(ApplyInstrumentationDeviceReasonErrRemoving), err.Error())
}
return err
}

err = addInstrumentationDeviceToWorkload(ctx, kubeClient, runtimeDetails)
err = addInstrumentationDeviceToWorkload(ctx, kubeClient, instrumentedApplication)
if err == nil {
conditions.UpdateStatusConditions(ctx, kubeClient, runtimeDetails, &runtimeDetails.Status.Conditions, metav1.ConditionTrue, appliedInstrumentationDeviceType, "InstrumentationDeviceApplied", "Instrumentation device applied successfully")
conditions.UpdateStatusConditions(ctx, kubeClient, instrumentedApplication, &instrumentedApplication.Status.Conditions, metav1.ConditionTrue, appliedInstrumentationDeviceType, "InstrumentationDeviceApplied", "Instrumentation device applied successfully")
} else {
conditions.UpdateStatusConditions(ctx, kubeClient, runtimeDetails, &runtimeDetails.Status.Conditions, metav1.ConditionFalse, appliedInstrumentationDeviceType, string(ApplyInstrumentationDeviceReasonErrApplying), err.Error())
conditions.UpdateStatusConditions(ctx, kubeClient, instrumentedApplication, &instrumentedApplication.Status.Conditions, metav1.ConditionFalse, appliedInstrumentationDeviceType, string(ApplyInstrumentationDeviceReasonErrApplying), err.Error())
}
return err
}
13 changes: 13 additions & 0 deletions instrumentor/controllers/utils/versionsupport/dotnet_support.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package versionsupport

import "github.com/hashicorp/go-version"

type DotNetVersionCheck struct{}

func (g DotNetVersionCheck) IsVersionSupported(version *version.Version) bool {
return true
}

func (g DotNetVersionCheck) GetSupportedVersion() string {
return "0.0.0"
}
15 changes: 15 additions & 0 deletions instrumentor/controllers/utils/versionsupport/go_support.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package versionsupport

import "github.com/hashicorp/go-version"

type GoVersionCheck struct{}

var goMinVersion, _ = version.NewVersion("1.17")

func (g GoVersionCheck) IsVersionSupported(version *version.Version) bool {
return version.GreaterThanOrEqual(goMinVersion)
}

func (g GoVersionCheck) GetSupportedVersion() string {
return goMinVersion.String()
}
16 changes: 16 additions & 0 deletions instrumentor/controllers/utils/versionsupport/java_support.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package versionsupport

import "github.com/hashicorp/go-version"

type JavaVersionChecker struct{}

var JavaMinVersion, _ = version.NewVersion("17.0.11+8")

func (j JavaVersionChecker) IsVersionSupported(version *version.Version) bool {
return version.Metadata() >= JavaMinVersion.Metadata() &&
version.GreaterThanOrEqual(JavaMinVersion)
}

func (j JavaVersionChecker) GetSupportedVersion() string {
return JavaMinVersion.String()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package versionsupport

import (
"context"
"fmt"
"github.com/hashicorp/go-version"
"github.com/odigos-io/odigos/api/odigos/v1alpha1"
"github.com/odigos-io/odigos/common"
"sigs.k8s.io/controller-runtime/pkg/log"
)

type RuntimeVersionChecker interface {
IsVersionSupported(version *version.Version) bool
GetSupportedVersion() string
}

func IsRuntimeVersionSupported(ctx context.Context, details []v1alpha1.RuntimeDetailsByContainer) (bool, error) {
logger := log.FromContext(ctx)

for _, runtimeDetailsByContainer := range details {
runtimeVersionSupporter := getRuntimeVersionCheck(runtimeDetailsByContainer.Language)
if runtimeVersionSupporter == nil {
logger.Info("Unsupported language", "language", runtimeDetailsByContainer.Language)
return false, fmt.Errorf("Unsupported language: %s", runtimeDetailsByContainer.Language)
}

if runtimeDetailsByContainer.RuntimeVersion == "" {
continue
}

runtimeVersion, err := version.NewVersion(runtimeDetailsByContainer.RuntimeVersion)
if err != nil {
logger.Info("Version format error: %s is not a valid version for language %s",
runtimeDetailsByContainer.RuntimeVersion, runtimeDetailsByContainer.Language)
return false, fmt.Errorf("Version format error: %s is not a valid version for language %s",
runtimeDetailsByContainer.RuntimeVersion, runtimeDetailsByContainer.Language)
}

if !runtimeVersionSupporter.IsVersionSupported(runtimeVersion) {
runtimeVersionOtelSDKSupport := runtimeVersionSupporter.GetSupportedVersion()
logger.Info("%s runtime version not supported by OpenTelemetry SDK. Found: %s, supports: %s",
runtimeDetailsByContainer.Language, runtimeDetailsByContainer.RuntimeVersion, runtimeVersionOtelSDKSupport)
return false, fmt.Errorf("%s runtime version not supported by OpenTelemetry SDK. Found: %s, supports: %s",
runtimeDetailsByContainer.Language, runtimeDetailsByContainer.RuntimeVersion, runtimeVersionOtelSDKSupport)
}
}

return true, nil
}

func getRuntimeVersionCheck(language common.ProgrammingLanguage) RuntimeVersionChecker {
switch language {
case common.JavaProgrammingLanguage:
return &JavaVersionChecker{}
case common.GoProgrammingLanguage:
return &GoVersionCheck{}
case common.PythonProgrammingLanguage:
return &PythonVersionCheck{}
case common.DotNetProgrammingLanguage:
return &DotNetVersionCheck{}
case common.JavascriptProgrammingLanguage:
return &NodeVersionCheck{}
case common.NginxProgrammingLanguage:
return &NginxVersionCheck{}
case common.MySQLProgrammingLanguage:
return &MySQLVersionCheck{}
default:
return nil
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package versionsupport

import (
"github.com/odigos-io/odigos/api/odigos/v1alpha1"
"github.com/odigos-io/odigos/common"
"github.com/stretchr/testify/assert"
"testing"
)

func TestIsRuntimeVersionSupported_NotSupported(t *testing.T) {
testCases := []struct {
name string
language common.ProgrammingLanguage
version string
errorMsg string
}{
{"java version not supported", common.JavaProgrammingLanguage, "17.0.10+9", "java runtime version not supported by OpenTelemetry SDK. Found: 17.0.10+9, supports: 17.0.11+8"},
{"jdk version not supported", common.JavaProgrammingLanguage, "17.0.11+7", "java runtime version not supported by OpenTelemetry SDK. Found: 17.0.11+7, supports: 17.0.11+8"},
{"go version not supported", common.GoProgrammingLanguage, "1.14", "go runtime version not supported by OpenTelemetry SDK. Found: 1.14, supports: 1.17.0"},
{"javascript version not supported", common.JavascriptProgrammingLanguage, "13.9.9", "javascript runtime version not supported by OpenTelemetry SDK. Found: 13.9.9, supports: 14.0.0"},
{"python version not supported", common.PythonProgrammingLanguage, "3.7", "python runtime version not supported by OpenTelemetry SDK. Found: 3.7, supports: 3.8.0"},
{"nginx version not supported", common.NginxProgrammingLanguage, "1.25.4", "nginx runtime version not supported by OpenTelemetry SDK. Found: 1.25.4, supports: 1.25.5, 1.26.0"},
{"nginx version not supported", common.NginxProgrammingLanguage, "1.26.1", "nginx runtime version not supported by OpenTelemetry SDK. Found: 1.26.1, supports: 1.25.5, 1.26.0"},
{"unknown language", common.UnknownProgrammingLanguage, "0.0.0", "Unsupported language: unknown"},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {

runtimeDetails := []v1alpha1.RuntimeDetailsByContainer{
{Language: tc.language, RuntimeVersion: tc.version},
}

supported, err := IsRuntimeVersionSupported(nil, runtimeDetails)
assert.Equal(t, false, supported)
assert.Error(t, err)
assert.Equal(t, err.Error(), tc.errorMsg)
})
}
}

func TestIsRuntimeVersionSupported_Support(t *testing.T) {
testCases := []struct {
name string
language common.ProgrammingLanguage
version string
}{
{"java version not supported", common.JavaProgrammingLanguage, "17.0.11+9"},
{"go version not supported", common.GoProgrammingLanguage, "1.18"},
{"dotnet version not supported", common.DotNetProgrammingLanguage, "0.0.0"},
{"javascript version not supported", common.JavascriptProgrammingLanguage, "14.0.1"},
{"python version not supported", common.PythonProgrammingLanguage, "3.9"},
{"mySQL version not supported", common.MySQLProgrammingLanguage, "1.14.1"},
{"nginx version not supported", common.NginxProgrammingLanguage, "1.25.5"},
{"nginx version not supported", common.NginxProgrammingLanguage, "1.26"},
}

for _, tc := range testCases {

t.Run(tc.name, func(t *testing.T) {

runtimeDetails := []v1alpha1.RuntimeDetailsByContainer{
{Language: tc.language, RuntimeVersion: tc.version},
}

supported, err := IsRuntimeVersionSupported(nil, runtimeDetails)
assert.Equal(t, true, supported)
assert.Nil(t, err)
})
}
}

func TestIsRuntimeVersionSupported_MultiRuntimeContainer_NotSupport(t *testing.T) {
testCases := []struct {
name string
language common.ProgrammingLanguage
version string
}{
{"java version not supported", common.JavaProgrammingLanguage, "17.0.11+9"},
{"go version not supported", common.GoProgrammingLanguage, "1.18"},
{"dotnet version not supported", common.DotNetProgrammingLanguage, "0.0.0"},
{"javascript version not supported", common.JavascriptProgrammingLanguage, "14.0.1"},
{"python version not supported", common.PythonProgrammingLanguage, "3.7"},
{"mySQL version not supported", common.MySQLProgrammingLanguage, "1.14.1"},
{"nginx version not supported", common.NginxProgrammingLanguage, "1.25.5"},
{"nginx version not supported", common.NginxProgrammingLanguage, "1.26"},
}

runtimeDetails := []v1alpha1.RuntimeDetailsByContainer{}

for _, tc := range testCases {
runtimeDetails = append(runtimeDetails, v1alpha1.RuntimeDetailsByContainer{
Language: tc.language,
RuntimeVersion: tc.version,
})
}

// Run the test for the combined runtimeDetails
t.Run("multiple runtimes not supported", func(t *testing.T) {
supported, err := IsRuntimeVersionSupported(nil, runtimeDetails)
assert.Equal(t, false, supported)
assert.Error(t, err)
assert.Equal(t, err.Error(), "python runtime version not supported by OpenTelemetry SDK. Found: 3.7, supports: 3.8.0")
})
}

func TestIsRuntimeVersionSupported_MultiRuntimeContainer_Support(t *testing.T) {
testCases := []struct {
name string
language common.ProgrammingLanguage
version string
}{
{"java version not supported", common.JavaProgrammingLanguage, "17.0.11+9"},
{"go version not supported", common.GoProgrammingLanguage, "1.18"},
{"dotnet version not supported", common.DotNetProgrammingLanguage, "0.0.0"},
{"javascript version not supported", common.JavascriptProgrammingLanguage, "14.0.1"},
{"python version not supported", common.PythonProgrammingLanguage, "3.9"},
{"mySQL version not supported", common.MySQLProgrammingLanguage, "1.14.1"},
{"nginx version not supported", common.NginxProgrammingLanguage, "1.25.5"},
{"nginx version not supported", common.NginxProgrammingLanguage, "1.26"},
}

runtimeDetails := []v1alpha1.RuntimeDetailsByContainer{}

for _, tc := range testCases {
runtimeDetails = append(runtimeDetails, v1alpha1.RuntimeDetailsByContainer{
Language: tc.language,
RuntimeVersion: tc.version,
})
}

// Run the test for the combined runtimeDetails
t.Run("multiple runtimes not supported", func(t *testing.T) {
supported, err := IsRuntimeVersionSupported(nil, runtimeDetails)
assert.Equal(t, true, supported)
assert.Nil(t, err)
})
}

func TestIsRuntimeVersionSupported_LanguageDoesNotExist_NotSupported(t *testing.T) {
testCase := struct {
name string
language common.ProgrammingLanguage
version string
}{"ruby language is not supported", "ruby", "17.0.10+9"}

t.Run(testCase.name, func(t *testing.T) {

runtimeDetails := []v1alpha1.RuntimeDetailsByContainer{
{Language: testCase.language, RuntimeVersion: testCase.version},
}

supported, err := IsRuntimeVersionSupported(nil, runtimeDetails)
assert.Equal(t, false, supported)
assert.Error(t, err)
assert.Equal(t, err.Error(), "Unsupported language: ruby")
})
}

func TestIsRuntimeVersionSupported_VersionNotFound_Supported(t *testing.T) {
testCase := struct {
name string
language common.ProgrammingLanguage
version string
}{"java version not supported", common.JavaProgrammingLanguage, ""}

t.Run(testCase.name, func(t *testing.T) {

runtimeDetails := []v1alpha1.RuntimeDetailsByContainer{
{Language: testCase.language, RuntimeVersion: testCase.version},
}

supported, err := IsRuntimeVersionSupported(nil, runtimeDetails)
assert.Equal(t, true, supported)
assert.Nil(t, err)
})
}
13 changes: 13 additions & 0 deletions instrumentor/controllers/utils/versionsupport/mysql_support.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package versionsupport

import "github.com/hashicorp/go-version"

type MySQLVersionCheck struct{}

func (g MySQLVersionCheck) IsVersionSupported(version *version.Version) bool {
return true
}

func (g MySQLVersionCheck) GetSupportedVersion() string {
return "0.0.0"
}
Loading

0 comments on commit 3a19cef

Please sign in to comment.