Allow feature flags to be disabled on an actor-by-actor basis

Open Alex Kalderimis requested to merge ajk-feature-allow-negative-overrides into master

See: #375170

What does this MR do and why?

Prior to this change, if a flag has been globally enabled, then disabling it for an actor would not do anything, making it impossible to have flag opt-outs.

This change adds a special opt-out actor for each actor. When an actor opts out, a new actor gate is added for the given flag. If this opt-out gate is present for a given actor, then we return disabled, even if the feature is globally enabled, allowing individual opt-outs.

Risks

This introduces new complexity to the feature flags model (since we have new gates that behave differently to the normal ones). Maybe this is too complex for the value it brings?

This requires co-ordination when merging FF clean up MRs (arguably this could be improved for our current functionality too) - specifically we really want to prevent merging a clean-up MR if we have any opt-outs active in prod. This can be best addressed by adding some logic to Danger for this.

We will need to update our Slack chatops integration to allow seeing these new states (since we call the same methods we currently use, I think, no changes for how to set states are needed). We need to make the opt-outs visible when using chat-ops so that developers can see the active state correctly.

How to set up and validate locally

  1. Set up a feature flag - enable it globally
  2. Choose an appropriate actor and then either using the console or the REST API, opt out of the flag: Feature.opt_out(flag, actor)
  3. See that the feature is disabled for that actor, but enabled for all others.

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Alex Kalderimis