[ 
https://issues.apache.org/jira/browse/CASSANDRA-15254?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17705661#comment-17705661
 ] 

Maxim Muzafarov commented on CASSANDRA-15254:
---------------------------------------------

cc [~dcapwell]

I had a productive discussion with David, so I would like to report valuable 
notes and combine them with comments left by David in the pull requests. The 
scope of this work described above remains unchanged.

The changes highlighted the problem we have with validating configuration 
properties, which I'll describe below, and will schedule another round of 
discussion on the dev list.
h2. Details for the scope
 # A user interacts with Cassandra's internal configuration using API: JMX, 
YAML file, and SettingsTable (under development). We need to perform the 
following operations for each property regardless of which interface is used:
 ## validation (bounds checking, null value acceptance, etc.)
 ## type conversion (type -> string, or string -> type);
 ## setting a default value when a condition is met;
 # The publicly available user interfaces such as JMX, file and SettingsTable 
(YAML may not be suitable in all checks) should use the ConfigurationRegistry 
(introduced by these changes) to access the Config instance to ensure that all 
the necessary checks are performed.
 # The DatabaseDescriptor (GuardrailsOptions) has methods to access the Config 
instance (e.g. DatabaseDescriptor#getCompactionTombstoneWarningThreshold()). 
These methods must remain unchanged for performance reasons and are used by 
internal subsystems to read values directly.

h2. The property validation problem
h3. What we have

Currently, we are validating a configuration property in the following parts of 
the code, which looks a bit messy:
 - in a property type itself if it's not primitive, e.g. 
DataStorageSpec#validateQuantity
 - rarely in nested configuration classes e.g. 
AuditLogOptions#validateCategories
 - during the configuration load from yaml-file for null, and non-null, see 
YamlConfigurationLoader.PropertiesChecker#check
 - during applying the configuration, e.g. DatabaseDescriptor#applySimpleConfig
 - in DatabaseDescriptor setter methods e.g. 
DatabaseDescriptor#setDenylistMaxKeysTotal
 - inside custom classes e.g. SSLFactory#validateSslContext
 - rarely in JMX methods itself, e.g. StorageService#setRepairRpcTimeout

We also perform at least two different types of property validation:
 - simple validation such as checking value bounds, checking null or not null, 
or matching pattern etc. (e.g. conf.memtable_cleanup_threshold < 0.01f);
 - validation of a property whose value depends on the values of other 
properties, see DatabaseDescriptor#setDefaultKeyspaceRF (this is out of the 
scope of this work);

*What we have. Example #1:*

{code:java}
// We perform three validations: 
//   - inside the type IntMebibytesBound;
//   - after loading YAML check for @Nullable;
//   - when applying the simple config;
//   - and each time when JMX method is called;
public volatile DataStorageSpec.IntMebibytesBound repair_session_space = null;

private static void applySimpleConfig() {
    ...
    if (conf.repair_session_space.toMebibytes() < 1)
        throw new ConfigurationException("repair_session_space must be > 0, but 
was " + conf.repair_session_space);
    ...
}

public static void setRepairSessionSpaceInMiB(int sizeInMiB) {
    if (sizeInMiB < 1)
        throw new ConfigurationException("Cannot set repair_session_space to " 
+ sizeInMiB + " < 1 mebibyte");
     ...
}
{code}

*What we have. Example #2:*

{code:java}
// We perform validations:
//   - inside the type LongMillisecondsBound;
//   - when applying the simple config;
//   - the LOWEST_ACCEPTED_TIMEOUT check for JMX is missed;
public volatile DurationSpec.LongMillisecondsBound read_request_timeout = new 
DurationSpec.LongMillisecondsBound("5000ms");

static void checkForLowestAcceptedTimeouts(Config conf)
{
    if(conf.read_request_timeout.toMilliseconds() < 
LOWEST_ACCEPTED_TIMEOUT.toMilliseconds())
    {
        logInfo("read_request_timeout", conf.read_request_timeout, 
LOWEST_ACCEPTED_TIMEOUT);
        conf.read_request_timeout = new 
DurationSpec.LongMillisecondsBound("10ms");
    }
}

public static void setRangeRpcTimeout(long timeOutInMillis)
{
    conf.range_request_timeout = new 
DurationSpec.LongMillisecondsBound(timeOutInMillis);
}
{code}

h3. What to do

In order to use the same validation path for configuration properties that are 
going to be changed through {{SettingsTable}}, we need to arrange a common 
validation process for each property to rely on, so that the validation path 
will be the same regardless of the public interface used.

In general, I'd like to avoid building a complex validation framework for 
Cassandra's configuration, as the purpose of the project is not to maintain the 
configuration itself, so the simpler and tiny the validation of the properties 
will be, the easier the configuration itself will be. We might have the 
following options for building the validation process, each of them has its own 
pros and cons (including implementation difficulties).

h4. 1. New annotations

Add new annotations to build the property's validation rules (as it was 
suggested by David)
@Max, @Min, @NotNull, @Size, @Nullable (already have this one), etc.

{code:java}
@NotNull @Min(5.0) @Max(16.0)
public volatile double phi_convict_threshold = 8.0;
{code}

An example of such an approach is the Dropwizard Configuration library (or 
Hibernate, Spring)
[https://www.dropwizard.io/en/latest/manual/validation.html#annotations]

h4. 2. OR add validators to @Mutable

Add to the @Mutable the set (or single implementation) of validations it 
performs, which is closer to what we have now.
As an alternative to having a single class for each constraint, we can have an 
enumeration list that stores the same implementations.
{code:java}
public @interface Mutable {
  Class<? extends ConfigurationConstraint<?>>[] constraints() default {};
}

public class NotNullConstraint implements ConfigurationConstraint<Object> {
    public void validate(Object newValue) {
        if (newValue == null)
            throw new IllegalArgumentException("Value cannot be null");
    }
}

public class PositiveConstraint implements ConfigurationConstraint<Object> {
    public void validate(Object newValue) {
        if (newValue instanceof Number && ((Number) newValue).intValue() <= 0)
            throw new IllegalArgumentException("Value must be positive");
    }
}

@Mutable(constraints = { NotNullConstraint.class, PositiveConstraint.class })
public volatile Integer concurrent_compactors;
{code}

Something similar is performed for Custom Constraints in Hibernate.
[https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#section-constraint-validator]

h4. 3. OR enforce setter method names

Enforce the setters method names to match the corresponding property name. This 
will allow us to call the setter method with reflection, and the method itself 
will do all the validation we need.

{code:java}
public volatile int key_cache_keys_to_save = Integer.MAX_VALUE;

public static void setKeyCacheKeysToSave(int keyCacheKeysToSave)
{
    conf.key_cache_keys_to_save = keyCacheKeysToSave;
}
{code}



> Allow UPDATE on settings virtual table to change running configurations
> -----------------------------------------------------------------------
>
>                 Key: CASSANDRA-15254
>                 URL: https://issues.apache.org/jira/browse/CASSANDRA-15254
>             Project: Cassandra
>          Issue Type: New Feature
>          Components: Feature/Virtual Tables
>            Reporter: Chris Lohfink
>            Assignee: Maxim Muzafarov
>            Priority: Normal
>             Fix For: 5.x
>
>         Attachments: Configuration Registry Diagram.png
>
>          Time Spent: 2h 10m
>  Remaining Estimate: 0h
>
> Allow using UPDATE on the system_views.settings virtual table to update 
> configs at runtime for the equivalent of the dispersed JMX 
> attributes/operations.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org
For additional commands, e-mail: commits-h...@cassandra.apache.org

Reply via email to