265 lines
9.9 KiB
Go
265 lines
9.9 KiB
Go
// Mgmt
|
|
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
|
// Written by James Shubin <james@shubin.ca> and the project contributors
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
//
|
|
// Additional permission under GNU GPL version 3 section 7
|
|
//
|
|
// If you modify this program, or any covered work, by linking or combining it
|
|
// with embedded mcl code and modules (and that the embedded mcl code and
|
|
// modules which link with this program, contain a copy of their source code in
|
|
// the authoritative form) containing parts covered by the terms of any other
|
|
// license, the licensors of this program grant you additional permission to
|
|
// convey the resulting work. Furthermore, the licensors of this program grant
|
|
// the original author, James Shubin, additional permission to update this
|
|
// additional permission if he deems it necessary to achieve the goals of this
|
|
// additional permission.
|
|
|
|
package engine
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/purpleidea/mgmt/util"
|
|
"github.com/purpleidea/mgmt/util/errwrap"
|
|
|
|
"golang.org/x/time/rate"
|
|
)
|
|
|
|
// DefaultMetaParams are the defaults that are used for undefined metaparams.
|
|
// Don't modify this variable. Use .Copy() if you'd like some for yourself.
|
|
var DefaultMetaParams = &MetaParams{
|
|
Noop: false,
|
|
Retry: 0,
|
|
Delay: 0,
|
|
Poll: 0, // defaults to watching for events
|
|
Limit: rate.Inf, // defaults to no limit
|
|
Burst: 0, // no burst needed on an infinite rate
|
|
Reset: false,
|
|
//Sema: []string{},
|
|
Rewatch: false,
|
|
Realize: false, // true would be more awesome, but unexpected for users
|
|
Dollar: false,
|
|
}
|
|
|
|
// MetaRes is the interface a resource must implement to support meta params.
|
|
// All resources must implement this.
|
|
type MetaRes interface {
|
|
// MetaParams lets you get or set meta params for the resource.
|
|
MetaParams() *MetaParams
|
|
|
|
// SetMetaParams lets you set all of the meta params for the resource in
|
|
// a single call.
|
|
SetMetaParams(*MetaParams)
|
|
}
|
|
|
|
// MetaParams provides some meta parameters that apply to every resource.
|
|
type MetaParams struct {
|
|
// Noop specifies that no changes should be made by the resource. It
|
|
// relies on the individual resource implementation, and can't protect
|
|
// you from a poorly or maliciously implemented resource.
|
|
Noop bool `yaml:"noop"`
|
|
|
|
// NOTE: there are separate Watch and CheckApply retry and delay values,
|
|
// but I've decided to use the same ones for both until there's a proper
|
|
// reason to want to do something differently for the Watch errors.
|
|
|
|
// Retry is the number of times to retry on error. Use -1 for infinite.
|
|
// This value is used for both Watch and CheckApply.
|
|
Retry int16 `yaml:"retry"`
|
|
|
|
// RetryReset resets the retry count for CheckApply if it succeeds. This
|
|
// value is currently different from the count used for Watch.
|
|
// TODO: Consider resetting retry count for watch if it sends an event?
|
|
RetryReset bool `yaml:"retryreset"`
|
|
|
|
// Delay is the number of milliseconds to wait between retries. This
|
|
// value is used for both Watch and CheckApply.
|
|
Delay uint64 `yaml:"delay"`
|
|
|
|
// Poll is the number of seconds between poll intervals. Use 0 to Watch.
|
|
Poll uint32 `yaml:"poll"`
|
|
|
|
// Limit is the number of events per second to allow through.
|
|
Limit rate.Limit `yaml:"limit"`
|
|
|
|
// Burst is the number of events to allow in a burst.
|
|
Burst int `yaml:"burst"`
|
|
|
|
// Reset causes the meta param state to reset when the resource changes.
|
|
// What this means is if you have a resource of a specific kind and name
|
|
// and in the subsequent graph it changes because one of its params
|
|
// changed, normally the Retry, and other params will remember their
|
|
// state, and you'll not reset the retry counter, however if this is
|
|
// true, then it will get reset. Note that any normal reset mechanisms
|
|
// built into retry are not affected by this.
|
|
Reset bool `yaml:"reset"`
|
|
|
|
// Sema is a list of semaphore ids in the form `id` or `id:count`. If
|
|
// you don't specify a count, then 1 is assumed. The sema of `foo` which
|
|
// has a count equal to 1, is different from a sema named `foo:1` which
|
|
// also has a count equal to 1, but is a different semaphore.
|
|
Sema []string `yaml:"sema"`
|
|
|
|
// Rewatch specifies whether we re-run the Watch worker during a swap
|
|
// if it has errored. When doing a GraphCmp to swap the graphs, if this
|
|
// is true, and this particular worker has errored, then we'll remove it
|
|
// and add it back as a new vertex, thus causing it to run again. This
|
|
// is different from the Retry metaparam which applies during the normal
|
|
// execution. It is only when this is exhausted that we're in permanent
|
|
// worker failure, and only then can we rely on this metaparam. This is
|
|
// false by default, as the frequency of graph changes makes it unlikely
|
|
// that you wanted this enabled on most resources.
|
|
Rewatch bool `yaml:"rewatch"`
|
|
|
|
// Realize ensures that the resource is guaranteed to converge at least
|
|
// once before a potential graph swap removes or changes it. This
|
|
// guarantee is useful for fast changing graphs, to ensure that the
|
|
// brief creation of a resource is seen. This guarantee does not prevent
|
|
// against the engine quitting normally, and it can't guarantee it if
|
|
// the resource is blocked because of a failed pre-requisite resource.
|
|
// XXX: Not implemented!
|
|
Realize bool `yaml:"realize"`
|
|
|
|
// Dollar allows you to name a resource to start with the dollar
|
|
// character. We don't allow this by default since it's probably not
|
|
// needed, and is more likely to be a typo where the user forgot to
|
|
// interpolate a variable name. In the rare case when it's needed, you
|
|
// can disable that check with this meta param.
|
|
Dollar bool `yaml:"dollar"`
|
|
}
|
|
|
|
// Cmp compares two AutoGroupMeta structs and determines if they're equivalent.
|
|
func (obj *MetaParams) Cmp(meta *MetaParams) error {
|
|
if obj.Noop != meta.Noop {
|
|
return fmt.Errorf("values for Noop are different")
|
|
}
|
|
// XXX: add a one way cmp like we used to have ?
|
|
//if obj.Noop != meta.Noop {
|
|
// // obj is the existing res, res is the *new* resource
|
|
// // if we go from no-noop -> noop, we can re-use the obj
|
|
// // if we go from noop -> no-noop, we need to regenerate
|
|
// if obj.Noop { // asymmetrical
|
|
// return fmt.Errorf("values for Noop are different") // going from noop to no-noop!
|
|
// }
|
|
//}
|
|
|
|
if obj.Retry != meta.Retry {
|
|
return fmt.Errorf("values for Retry are different")
|
|
}
|
|
if obj.Delay != meta.Delay {
|
|
return fmt.Errorf("values for Delay are different")
|
|
}
|
|
if obj.Poll != meta.Poll {
|
|
return fmt.Errorf("values for Poll are different")
|
|
}
|
|
if obj.Limit != meta.Limit {
|
|
return fmt.Errorf("values for Limit are different")
|
|
}
|
|
if obj.Burst != meta.Burst {
|
|
return fmt.Errorf("values for Burst are different")
|
|
}
|
|
if obj.Reset != meta.Reset {
|
|
return fmt.Errorf("values for Reset are different")
|
|
}
|
|
|
|
if err := util.SortedStrSliceCompare(obj.Sema, meta.Sema); err != nil {
|
|
return errwrap.Wrapf(err, "values for Sema are different")
|
|
}
|
|
|
|
if obj.Rewatch != meta.Rewatch {
|
|
return fmt.Errorf("values for Rewatch are different")
|
|
}
|
|
if obj.Realize != meta.Realize {
|
|
return fmt.Errorf("values for Realize are different")
|
|
}
|
|
if obj.Dollar != meta.Dollar {
|
|
return fmt.Errorf("values for Dollar are different")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Validate runs some validation on the meta params.
|
|
func (obj *MetaParams) Validate() error {
|
|
if obj.Burst == 0 && !(obj.Limit == rate.Inf) { // blocked
|
|
return fmt.Errorf("permanently limited (rate != Inf, burst = 0)")
|
|
}
|
|
|
|
for _, s := range obj.Sema {
|
|
if s == "" {
|
|
return fmt.Errorf("semaphore is empty")
|
|
}
|
|
if _, err := strconv.Atoi(s); err == nil { // standalone int
|
|
return fmt.Errorf("semaphore format is invalid")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Copy copies this struct and returns a new one.
|
|
func (obj *MetaParams) Copy() *MetaParams {
|
|
sema := []string{}
|
|
if obj.Sema != nil {
|
|
sema = make([]string, len(obj.Sema))
|
|
copy(sema, obj.Sema)
|
|
}
|
|
return &MetaParams{
|
|
Noop: obj.Noop,
|
|
Retry: obj.Retry,
|
|
Delay: obj.Delay,
|
|
Poll: obj.Poll,
|
|
Limit: obj.Limit, // FIXME: can we copy this type like this? test me!
|
|
Burst: obj.Burst,
|
|
Reset: obj.Reset,
|
|
Sema: sema,
|
|
Rewatch: obj.Rewatch,
|
|
Realize: obj.Realize,
|
|
Dollar: obj.Dollar,
|
|
}
|
|
}
|
|
|
|
// UnmarshalYAML is the custom unmarshal handler for the MetaParams struct. It
|
|
// is primarily useful for setting the defaults.
|
|
// TODO: this is untested
|
|
func (obj *MetaParams) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
type rawMetaParams MetaParams // indirection to avoid infinite recursion
|
|
raw := rawMetaParams(*DefaultMetaParams) // convert; the defaults go here
|
|
|
|
if err := unmarshal(&raw); err != nil {
|
|
return err
|
|
}
|
|
|
|
*obj = MetaParams(raw) // restore from indirection with type conversion!
|
|
return nil
|
|
}
|
|
|
|
// MetaState is some local meta param state that is saved between resources of
|
|
// the same kind+name unique ID. Even though the vertex/resource pointer might
|
|
// change during a graph switch (because the params changed) we might be
|
|
// logically referring to the same resource, and we might want to preserve some
|
|
// data across that switch. The common example is the resource retry count. If a
|
|
// resource failed three times, and we had a limit of five before it would be a
|
|
// permanent failure, then we don't want to reset this counter just because we
|
|
// changed a parameter (field) of the resource. This doesn't mean we don't want
|
|
// to ever reset these counts. For that, flip on the reset meta param.
|
|
type MetaState struct {
|
|
|
|
// CheckApplyRetry is the current retry count for CheckApply.
|
|
CheckApplyRetry int16
|
|
}
|