Skip to content

Commit

Permalink
Add dc.Spec.Config validation for properties that are not available i…
Browse files Browse the repository at this point in the history
…n defined version
  • Loading branch information
burmanm committed Sep 2, 2022
1 parent 0acbfce commit e11efa3
Show file tree
Hide file tree
Showing 9 changed files with 923 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Changelog for Cass Operator, new PRs should update the `main / unreleased` secti
## unreleased

* [CHANGE] [#354](https://github.com/k8ssandra/cass-operator/issues/354) Remove oldDefunctLabel support since we recreate StS. Fix #335 created-by value to match expected value.
* [ENHANCEMENT] [#224](https://github.com/k8ssandra/cass-operator/pull/224) Validate dc.Spec.Config properties against the Cassandra's allowed config value list
* [ENHANCEMENT] [#383](https://github.com/k8ssandra/cass-operator/pull/383) Add UpgradeSSTables, Compaction and Scrub to management-api client. Improve CassandraTasks to have the ability to validate input parameters, filter target pods and do processing outside of pods.
* [ENHANCEMENT] [#384](https://github.com/k8ssandra/cass-operator/issues/384) Add a new CassandraTask operation "replacenode" that removes the existing PVCs from the pod, deletes the pod and starts a replacement process.
* [ENHANCEMENT] [#387](https://github.com/k8ssandra/cass-operator/issues/387) Add a new CassandraTask operation "upgradesstables" that allows to do SSTable upgrades after Cassandra version upgrade.
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and Cust
generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."

.PHONY: generate-configs
generate-configs:
cd hack/config && ./generate.sh

.PHONY: fmt
fmt: ## Run go fmt against code.
go fmt ./...
Expand Down
725 changes: 725 additions & 0 deletions apis/cassandra/v1beta1/cassandra_config_generated.go

Large diffs are not rendered by default.

44 changes: 43 additions & 1 deletion apis/cassandra/v1beta1/cassandradatacenter_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,17 @@ func ValidateSingleDatacenter(dc CassandraDatacenter) error {
return err
}

return ValidateFQLConfig(dc)
if err := ValidateFQLConfig(dc); err != nil {
return err
}
if !isDse {
err := ValidateConfig(&dc)
if err != nil {
return err
}
}

return nil
}

// ValidateDatacenterFieldChanges checks that no values are improperly changing while updating
Expand Down Expand Up @@ -238,6 +248,38 @@ var (
ErrFQLNotSupported = fmt.Errorf("full query logging is only supported on OSS Cassandra 4.0+")
)

func ValidateConfig(dc *CassandraDatacenter) error {
// TODO Cleanup to more common processing after ModelValues is moved to apis
if dc.Spec.Config != nil {
var dcConfig map[string]interface{}
if err := json.Unmarshal(dc.Spec.Config, &dcConfig); err != nil {
return err
}
casYaml, found := dcConfig["cassandra-yaml"]
if !found {
return nil
}

casYamlMap, ok := casYaml.(map[string]interface{})
if !ok {
err := fmt.Errorf("failed to parse cassandra-yaml")
return err
}

configValues, err := GetCassandraConfigValues(dc.Spec.ServerVersion)
if err != nil {
return err
}
for k := range casYamlMap {
if !configValues.HasProperty(k) {
// We should probably add an event to tell the user that they're using old values
return fmt.Errorf("property %s is not valid for serverVersion %s", k, dc.Spec.ServerVersion)
}
}
}
return nil
}

func ValidateFQLConfig(dc CassandraDatacenter) error {
if dc.Spec.Config != nil {
enabled, err := dc.FullQueryEnabled()
Expand Down
42 changes: 42 additions & 0 deletions apis/cassandra/v1beta1/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,48 @@ func Test_ValidateSingleDatacenter(t *testing.T) {
},
errString: "attempted to define config jvm-server-options with cassandra-3.11.7",
},
{
name: "Cassandra 3.11 invalid cassandra-yaml full_query_options",
dc: &CassandraDatacenter{
ObjectMeta: metav1.ObjectMeta{
Name: "exampleDC",
},
Spec: CassandraDatacenterSpec{
ServerType: "cassandra",
ServerVersion: "3.11.11",
Config: json.RawMessage(`
{
"cassandra-yaml": {
"full_query_logging_options": {
"log_dir": "/var/log/cassandra/fql"
}
}
}
`),
},
},
errString: "property full_query_logging_options is not valid for serverVersion 3.11.11",
},
{
name: "Cassandra 4.0.1 invalid cassandra-yaml thrift_max_message_length_in_mb",
dc: &CassandraDatacenter{
ObjectMeta: metav1.ObjectMeta{
Name: "exampleDC",
},
Spec: CassandraDatacenterSpec{
ServerType: "cassandra",
ServerVersion: "4.0.1",
Config: json.RawMessage(`
{
"cassandra-yaml": {
"thrift_max_message_length_in_mb": "256"
}
}
`),
},
},
errString: "property thrift_max_message_length_in_mb is not valid for serverVersion 4.0.1",
},
{
name: "DSE 6.8 invalid config file jvm-options",
dc: &CassandraDatacenter{
Expand Down
12 changes: 12 additions & 0 deletions hack/config/Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
javalang = "*"

[dev-packages]

[requires]
python_version = "3.9"
8 changes: 8 additions & 0 deletions hack/config/generate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
OUTPUTFILE=../../apis/cassandra/v1beta1/cassandra_config_generated.go
curl -sL https://raw.githubusercontent.com/apache/cassandra/cassandra-4.0/src/java/org/apache/cassandra/config/Config.java --output Config-40.java
curl -sL https://raw.githubusercontent.com/apache/cassandra/cassandra-3.11/src/java/org/apache/cassandra/config/Config.java --output Config-311.java
curl -sL https://raw.githubusercontent.com/apache/cassandra/trunk/src/java/org/apache/cassandra/config/Config.java --output Config-trunk.java
pipenv run python parse.py > $OUTPUTFILE
go fmt $OUTPUTFILE
rm -f *.java
85 changes: 85 additions & 0 deletions hack/config/parse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# This script generates golang structs for Cassandra configurations
import javalang
from pathlib import Path
from typing import List

def parse_file(file: str):
config_file = Path(file).read_text()
tree = javalang.parse.parse(config_file)
for path, node in tree.filter(javalang.tree.ClassDeclaration):
if node.name == "Config":
for field in node.fields:
if 'public' in field.modifiers and not 'static' in field.modifiers:
for declarator in field.declarators:
print(f'"{declarator.name}": true,')
# for annotation in field.annotations:
# if annotation.name == "Deprecated":
# print('Deprecated!')

def generate_configs(versions: List[str]):
print("""
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
// Code is generated with hack/config. DO NOT EDIT.
package v1beta1
import (
"strconv"
"strings"
)
type CassandraConfigValues struct {
accepted map[string]interface{}
}
func (c *CassandraConfigValues) HasProperty(propertyName string) bool {
_, found := c.accepted[propertyName]
return found
}
func GetCassandraConfigValues(serverVersion string) (*CassandraConfigValues, error) {
versionParts := strings.Split(serverVersion, ".")
serverMajorVersion, err := strconv.ParseInt(versionParts[0], 10, 8)
if err != nil {
return nil, err
}
if serverMajorVersion == 3 {
// Return configuration for 3.11, regardless of the real version (we don't support <3.11)
return &config311, nil
}
if serverMajorVersion == 4 {
serverMinorVersion, err := strconv.ParseInt(versionParts[1], 10, 8)
if err != nil {
return nil, err
}
if serverMinorVersion == 0 {
// Return config40
return &config40, nil
}
}
// Something brand new..
return &configtrunk, nil
}
var(
""")

for ver in versions:
create_config_values(ver)

print(')')

def create_config_values(version: str):
print(f"""
config{version} = CassandraConfigValues{{
accepted: map[string]interface{{}}{{""")
parse_file(f'Config-{version}.java')
print("""
},
}
""")

generate_configs(['311', '40', 'trunk'])
3 changes: 3 additions & 0 deletions pkg/reconciliation/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ func (rc *ReconciliationContext) IsValid(dc *api.CassandraDatacenter) error {
// Validate Service labels and annotations
errs = append(errs, api.ValidateServiceLabelsAndAnnotations(*dc))

// Validate spec.Config
errs = append(errs, api.ValidateConfig(dc))

// Validate Management API config
errs = append(errs, httphelper.ValidateManagementApiConfig(dc, rc.Client, rc.Ctx)...)
if len(errs) > 0 {
Expand Down

0 comments on commit e11efa3

Please sign in to comment.