Skip to content

Commit

Permalink
Validate OTLPSpec
Browse files Browse the repository at this point in the history
  • Loading branch information
periklis committed Sep 3, 2024
1 parent 9f8dcdf commit 9914bdb
Show file tree
Hide file tree
Showing 3 changed files with 333 additions and 0 deletions.
7 changes: 7 additions & 0 deletions operator/apis/loki/v1/v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ var (
// ErrIPv6InstanceAddrTypeNotAllowed when the default InstanceAddrType is used with enableIPv6.
ErrIPv6InstanceAddrTypeNotAllowed = errors.New(`instanceAddrType "default" cannot be used with enableIPv6 at the same time`)

// ErrOTLPResourceAttributesEmptyNotAllowed when the OTLP ResourceAttributes are empty when ingoreDefaults is enabled.
ErrOTLPResourceAttributesEmptyNotAllowed = errors.New(`resourceAttributes cannot be empty when ignoreDefaults is true`)
// ErrDescriptionAnnotationMissing when OTLP ResourceAttributes does not contain an OTLPAttributeActionIndexLabel action when ingnoreDefaults is enabled.a
ErrOTLPResourceAttributesIndexLabelActionMissing = errors.New(`resourceAttributes does not contain at least one action of type "index_label"`)
// ErrOTLPAttributesSpecInvalid when the OTLPAttributesSpec attibutes and regex fields are both empty.
ErrOTLPAttributesSpecInvalid = errors.New(`attributes and regex cannot be empty at the same time`)

// ErrRuleMustMatchNamespace indicates that an expression used in an alerting or recording rule is missing
// matchers for a namespace.
ErrRuleMustMatchNamespace = errors.New("rule needs to have a matcher for the namespace")
Expand Down
92 changes: 92 additions & 0 deletions operator/internal/validation/lokistack.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ func (v *LokiStackValidator) validate(ctx context.Context, obj runtime.Object) (
allErrs = append(allErrs, errors...)
}

if stack.Spec.Limits != nil {
if stack.Spec.Limits.Global != nil && stack.Spec.Limits.Global.OTLP != nil {
allErrs = append(allErrs, v.validateGlobalOTLPSpec(stack.Spec.Limits.Global.OTLP)...)
}

if stack.Spec.Limits.Tenants != nil {
allErrs = append(allErrs, v.validatePerTenantOTLPSpec(stack.Spec.Limits.Tenants)...)
}
}

if v.ExtendedValidator != nil {
allErrs = append(allErrs, v.ExtendedValidator(ctx, stack)...)
}
Expand All @@ -93,6 +103,88 @@ func (v *LokiStackValidator) validate(ctx context.Context, obj runtime.Object) (
)
}

func (v *LokiStackValidator) validateGlobalOTLPSpec(s *lokiv1.GlobalOTLPSpec) field.ErrorList {
basePath := field.NewPath("spec", "limits", "global")

return v.validateOTLPSpec(basePath, &s.OTLPSpec)
}

func (v *LokiStackValidator) validatePerTenantOTLPSpec(tenants map[string]lokiv1.PerTenantLimitsTemplateSpec) field.ErrorList {
var allErrs field.ErrorList

for key, tenant := range tenants {
basePath := field.NewPath("spec", "limits", "tenants").Key(key)
allErrs = append(allErrs, v.validateOTLPSpec(basePath, tenant.OTLP)...)
}

return allErrs
}

func (v *LokiStackValidator) validateOTLPSpec(parent *field.Path, s *lokiv1.OTLPSpec) field.ErrorList {
var allErrs field.ErrorList

if s.ResourceAttributes != nil && s.ResourceAttributes.IgnoreDefaults {
switch {
case len(s.ResourceAttributes.Attributes) == 0:
allErrs = append(allErrs,
field.Invalid(
parent.Child("otlp", "resourceAttributes"),
[]lokiv1.OTLPAttributesSpec{},
lokiv1.ErrOTLPResourceAttributesEmptyNotAllowed.Error(),
),
)
default:
var indexLabelActionFound bool
for _, attr := range s.ResourceAttributes.Attributes {
if attr.Action == lokiv1.OTLPAttributeActionIndexLabel && (len(attr.Attributes) != 0 || attr.Regex != "") {
indexLabelActionFound = true
break
}
}

if !indexLabelActionFound {
allErrs = append(allErrs,
field.Invalid(
parent.Child("otlp", "resourceAttributes"),
s.ResourceAttributes.Attributes,
lokiv1.ErrOTLPResourceAttributesIndexLabelActionMissing.Error(),
),
)
}
}
}

if len(s.ScopeAttributes) != 0 {
for idx, attr := range s.ScopeAttributes {
if len(attr.Attributes) == 0 && attr.Regex == "" {
allErrs = append(allErrs,
field.Invalid(
parent.Child("otlp", "scopeAttributes").Index(idx),
[]string{},
lokiv1.ErrOTLPAttributesSpecInvalid.Error(),
),
)
}
}
}

if len(s.LogAttributes) != 0 {
for idx, attr := range s.LogAttributes {
if len(attr.Attributes) == 0 && attr.Regex == "" {
allErrs = append(allErrs,
field.Invalid(
parent.Child("otlp", "logAttributes").Index(idx),
[]string{},
lokiv1.ErrOTLPAttributesSpecInvalid.Error(),
),
)
}
}
}

return allErrs
}

func (v *LokiStackValidator) validateHashRingSpec(s lokiv1.LokiStackSpec) field.ErrorList {
if s.HashRing == nil {
return nil
Expand Down
234 changes: 234 additions & 0 deletions operator/internal/validation/lokistack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,240 @@ var ltt = []struct {
},
),
},
{
desc: "enabling global limits OTLP IgnoreDefaults without resource attributes",
spec: lokiv1.LokiStack{
Spec: lokiv1.LokiStackSpec{
Storage: lokiv1.ObjectStorageSpec{
Schemas: []lokiv1.ObjectStorageSchema{
{
Version: lokiv1.ObjectStorageSchemaV11,
EffectiveDate: "2020-10-11",
},
{
Version: lokiv1.ObjectStorageSchemaV12,
EffectiveDate: "2020-10-13",
},
},
},
Limits: &lokiv1.LimitsSpec{
Global: &lokiv1.LimitsTemplateSpec{
OTLP: &lokiv1.GlobalOTLPSpec{
OTLPSpec: lokiv1.OTLPSpec{
ResourceAttributes: &lokiv1.OTLPResourceAttributesSpec{
IgnoreDefaults: true,
},
},
},
},
},
},
},
err: apierrors.NewInvalid(
schema.GroupKind{Group: "loki.grafana.com", Kind: "LokiStack"},
"testing-stack",
field.ErrorList{
field.Invalid(
field.NewPath("spec", "limits", "global", "otlp", "resourceAttributes"),
[]lokiv1.OTLPAttributesSpec{},
lokiv1.ErrOTLPResourceAttributesEmptyNotAllowed.Error(),
),
},
),
},
{
desc: "enabling global limits OTLP IgnoreDefaults without index label action for resource attributes",
spec: lokiv1.LokiStack{
Spec: lokiv1.LokiStackSpec{
Storage: lokiv1.ObjectStorageSpec{
Schemas: []lokiv1.ObjectStorageSchema{
{
Version: lokiv1.ObjectStorageSchemaV11,
EffectiveDate: "2020-10-11",
},
{
Version: lokiv1.ObjectStorageSchemaV12,
EffectiveDate: "2020-10-13",
},
},
},
Limits: &lokiv1.LimitsSpec{
Global: &lokiv1.LimitsTemplateSpec{
OTLP: &lokiv1.GlobalOTLPSpec{
OTLPSpec: lokiv1.OTLPSpec{
ResourceAttributes: &lokiv1.OTLPResourceAttributesSpec{
IgnoreDefaults: true,
Attributes: []lokiv1.OTLPAttributesSpec{
{
Action: lokiv1.OTLPAttributeActionIndexLabel,
},
},
},
},
},
},
},
},
},
err: apierrors.NewInvalid(
schema.GroupKind{Group: "loki.grafana.com", Kind: "LokiStack"},
"testing-stack",
field.ErrorList{
field.Invalid(
field.NewPath("spec", "limits", "global", "otlp", "resourceAttributes"),
[]lokiv1.OTLPAttributesSpec{
{
Action: lokiv1.OTLPAttributeActionIndexLabel,
},
},
lokiv1.ErrOTLPResourceAttributesIndexLabelActionMissing.Error(),
),
},
),
},
{
desc: "invalid global OTLP scope attribute specs",
spec: lokiv1.LokiStack{
Spec: lokiv1.LokiStackSpec{
Storage: lokiv1.ObjectStorageSpec{
Schemas: []lokiv1.ObjectStorageSchema{
{
Version: lokiv1.ObjectStorageSchemaV11,
EffectiveDate: "2020-10-11",
},
{
Version: lokiv1.ObjectStorageSchemaV12,
EffectiveDate: "2020-10-13",
},
},
},
Limits: &lokiv1.LimitsSpec{
Global: &lokiv1.LimitsTemplateSpec{
OTLP: &lokiv1.GlobalOTLPSpec{
OTLPSpec: lokiv1.OTLPSpec{
ScopeAttributes: []lokiv1.OTLPAttributesSpec{
{
Action: lokiv1.OTLPAttributeActionIndexLabel,
},
{
Action: lokiv1.OTLPAttributeActionStructuredMetadata,
},
},
},
},
},
},
},
},
err: apierrors.NewInvalid(
schema.GroupKind{Group: "loki.grafana.com", Kind: "LokiStack"},
"testing-stack",
field.ErrorList{
field.Invalid(
field.NewPath("spec", "limits", "global", "otlp", "scopeAttributes").Index(0),
[]string{},
lokiv1.ErrOTLPAttributesSpecInvalid.Error(),
),
field.Invalid(
field.NewPath("spec", "limits", "global", "otlp", "scopeAttributes").Index(1),
[]string{},
lokiv1.ErrOTLPAttributesSpecInvalid.Error(),
),
},
),
},
{
desc: "invalid global OTLP log attribute specs",
spec: lokiv1.LokiStack{
Spec: lokiv1.LokiStackSpec{
Storage: lokiv1.ObjectStorageSpec{
Schemas: []lokiv1.ObjectStorageSchema{
{
Version: lokiv1.ObjectStorageSchemaV11,
EffectiveDate: "2020-10-11",
},
{
Version: lokiv1.ObjectStorageSchemaV12,
EffectiveDate: "2020-10-13",
},
},
},
Limits: &lokiv1.LimitsSpec{
Global: &lokiv1.LimitsTemplateSpec{
OTLP: &lokiv1.GlobalOTLPSpec{
OTLPSpec: lokiv1.OTLPSpec{
LogAttributes: []lokiv1.OTLPAttributesSpec{
{
Action: lokiv1.OTLPAttributeActionIndexLabel,
},
{
Action: lokiv1.OTLPAttributeActionStructuredMetadata,
},
},
},
},
},
},
},
},
err: apierrors.NewInvalid(
schema.GroupKind{Group: "loki.grafana.com", Kind: "LokiStack"},
"testing-stack",
field.ErrorList{
field.Invalid(
field.NewPath("spec", "limits", "global", "otlp", "logAttributes").Index(0),
[]string{},
lokiv1.ErrOTLPAttributesSpecInvalid.Error(),
),
field.Invalid(
field.NewPath("spec", "limits", "global", "otlp", "logAttributes").Index(1),
[]string{},
lokiv1.ErrOTLPAttributesSpecInvalid.Error(),
),
},
),
},
{
desc: "enabling per-tenant limits OTLP IgnoreDefaults without resource attributes",
spec: lokiv1.LokiStack{
Spec: lokiv1.LokiStackSpec{
Storage: lokiv1.ObjectStorageSpec{
Schemas: []lokiv1.ObjectStorageSchema{
{
Version: lokiv1.ObjectStorageSchemaV11,
EffectiveDate: "2020-10-11",
},
{
Version: lokiv1.ObjectStorageSchemaV12,
EffectiveDate: "2020-10-13",
},
},
},
Limits: &lokiv1.LimitsSpec{
Tenants: map[string]lokiv1.PerTenantLimitsTemplateSpec{
"tenant-a": {
OTLP: &lokiv1.OTLPSpec{
ResourceAttributes: &lokiv1.OTLPResourceAttributesSpec{
IgnoreDefaults: true,
},
},
},
},
},
},
},
err: apierrors.NewInvalid(
schema.GroupKind{Group: "loki.grafana.com", Kind: "LokiStack"},
"testing-stack",
field.ErrorList{
field.Invalid(
field.NewPath("spec", "limits", "tenants").Key("tenant-a").Child("otlp", "resourceAttributes"),
[]lokiv1.OTLPAttributesSpec{},
lokiv1.ErrOTLPResourceAttributesEmptyNotAllowed.Error(),
),
},
),
},
}

func TestLokiStackValidationWebhook_ValidateCreate(t *testing.T) {
Expand Down

0 comments on commit 9914bdb

Please sign in to comment.