mirror of
https://github.com/rancher/norman.git
synced 2025-07-06 20:10:09 +00:00
Norman Condition automatically generate condition information based on the error the handler would return in Condition.Do function. handler function usually return 2 types of error. the error can be ignored and the error can not be ignored. According to current implementation, Condition.Do function generate condition with error state even if handler return error can be ignored. The error that can be ignored should be ignored in the context of condition as well. So this commit introduce new field which is Reason to ForgetError so that the developer can put special reason other than Error when ForgetError is expected to happen by expected procedure like provisioning and wait for something and Norman Condintion respect to this field when try to generate condition information based on error This solution will help us to fix this rancher bug https://github.com/rancher/rancher/issues/15907
297 lines
6.7 KiB
Go
297 lines
6.7 KiB
Go
package condition
|
|
|
|
import (
|
|
"reflect"
|
|
"regexp"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/rancher/norman/controller"
|
|
err2 "k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
)
|
|
|
|
type Cond string
|
|
|
|
var temfileRegexp = regexp.MustCompile("/tmp/[-_a-zA-Z0-9]+")
|
|
|
|
func (c Cond) True(obj runtime.Object) {
|
|
setStatus(obj, string(c), "True")
|
|
}
|
|
|
|
func (c Cond) IsTrue(obj runtime.Object) bool {
|
|
return getStatus(obj, string(c)) == "True"
|
|
}
|
|
|
|
func (c Cond) LastUpdated(obj runtime.Object, ts string) {
|
|
setTS(obj, string(c), ts)
|
|
}
|
|
|
|
func (c Cond) GetLastUpdated(obj runtime.Object) string {
|
|
return getTS(obj, string(c))
|
|
}
|
|
|
|
func (c Cond) False(obj runtime.Object) {
|
|
setStatus(obj, string(c), "False")
|
|
}
|
|
|
|
func (c Cond) IsFalse(obj runtime.Object) bool {
|
|
return getStatus(obj, string(c)) == "False"
|
|
}
|
|
|
|
func (c Cond) GetStatus(obj runtime.Object) string {
|
|
return getStatus(obj, string(c))
|
|
}
|
|
|
|
func (c Cond) Unknown(obj runtime.Object) {
|
|
setStatus(obj, string(c), "Unknown")
|
|
}
|
|
|
|
func (c Cond) CreateUnknownIfNotExists(obj runtime.Object) {
|
|
condSlice := getValue(obj, "Status", "Conditions")
|
|
cond := findCond(condSlice, string(c))
|
|
if cond == nil {
|
|
c.Unknown(obj)
|
|
}
|
|
}
|
|
|
|
func (c Cond) IsUnknown(obj runtime.Object) bool {
|
|
return getStatus(obj, string(c)) == "Unknown"
|
|
}
|
|
|
|
func (c Cond) Reason(obj runtime.Object, reason string) {
|
|
cond := findOrCreateCond(obj, string(c))
|
|
getFieldValue(cond, "Reason").SetString(reason)
|
|
}
|
|
|
|
func (c Cond) SetMessageIfBlank(obj runtime.Object, message string) {
|
|
if c.GetMessage(obj) == "" {
|
|
c.Message(obj, message)
|
|
}
|
|
}
|
|
|
|
func (c Cond) Message(obj runtime.Object, message string) {
|
|
cond := findOrCreateCond(obj, string(c))
|
|
setValue(cond, "Message", message)
|
|
}
|
|
|
|
func (c Cond) GetMessage(obj runtime.Object) string {
|
|
cond := findOrNotCreateCond(obj, string(c))
|
|
if cond == nil {
|
|
return ""
|
|
}
|
|
return getFieldValue(*cond, "Message").String()
|
|
}
|
|
|
|
func (c Cond) ReasonAndMessageFromError(obj runtime.Object, err error) {
|
|
if err2.IsConflict(err) {
|
|
return
|
|
}
|
|
cond := findOrCreateCond(obj, string(c))
|
|
setValue(cond, "Message", err.Error())
|
|
switch ce := err.(type) {
|
|
case *conditionError:
|
|
setValue(cond, "Reason", ce.reason)
|
|
case *controller.ForgetError:
|
|
if ce.Reason != "" {
|
|
setValue(cond, "Reason", ce.Reason)
|
|
} else {
|
|
setValue(cond, "Reason", "Error")
|
|
}
|
|
default:
|
|
setValue(cond, "Reason", "Error")
|
|
}
|
|
}
|
|
|
|
func (c Cond) GetReason(obj runtime.Object) string {
|
|
cond := findOrNotCreateCond(obj, string(c))
|
|
if cond == nil {
|
|
return ""
|
|
}
|
|
return getFieldValue(*cond, "Reason").String()
|
|
}
|
|
|
|
func (c Cond) Once(obj runtime.Object, f func() (runtime.Object, error)) (runtime.Object, error) {
|
|
if c.IsFalse(obj) {
|
|
return obj, &controller.ForgetError{
|
|
Err: errors.New(c.GetReason(obj)),
|
|
}
|
|
}
|
|
|
|
return c.DoUntilTrue(obj, f)
|
|
}
|
|
|
|
func (c Cond) DoUntilTrue(obj runtime.Object, f func() (runtime.Object, error)) (runtime.Object, error) {
|
|
if c.IsTrue(obj) {
|
|
return obj, nil
|
|
}
|
|
|
|
return c.do(obj, f)
|
|
}
|
|
|
|
func (c Cond) Do(obj runtime.Object, f func() (runtime.Object, error)) (runtime.Object, error) {
|
|
return c.do(obj, f)
|
|
}
|
|
|
|
func (c Cond) do(obj runtime.Object, f func() (runtime.Object, error)) (runtime.Object, error) {
|
|
status := c.GetStatus(obj)
|
|
ts := c.GetLastUpdated(obj)
|
|
reason := c.GetReason(obj)
|
|
message := c.GetMessage(obj)
|
|
|
|
obj, err := c.doInternal(obj, f)
|
|
|
|
// This is to prevent non stop flapping of states and update
|
|
if status == c.GetStatus(obj) &&
|
|
reason == c.GetReason(obj) {
|
|
if message != c.GetMessage(obj) {
|
|
replaced := temfileRegexp.ReplaceAllString(c.GetMessage(obj), "file_path_redacted")
|
|
c.Message(obj, replaced)
|
|
}
|
|
if message == c.GetMessage(obj) {
|
|
c.LastUpdated(obj, ts)
|
|
}
|
|
}
|
|
|
|
return obj, err
|
|
}
|
|
|
|
func (c Cond) doInternal(obj runtime.Object, f func() (runtime.Object, error)) (runtime.Object, error) {
|
|
if !c.IsFalse(obj) {
|
|
c.Unknown(obj)
|
|
}
|
|
|
|
newObj, err := f()
|
|
if newObj != nil && !reflect.ValueOf(newObj).IsNil() {
|
|
obj = newObj
|
|
}
|
|
|
|
if err != nil {
|
|
if _, ok := err.(*controller.ForgetError); ok {
|
|
if c.GetMessage(obj) == "" {
|
|
c.ReasonAndMessageFromError(obj, err)
|
|
}
|
|
return obj, err
|
|
}
|
|
c.False(obj)
|
|
c.ReasonAndMessageFromError(obj, err)
|
|
return obj, err
|
|
}
|
|
c.True(obj)
|
|
c.Reason(obj, "")
|
|
c.Message(obj, "")
|
|
return obj, nil
|
|
}
|
|
|
|
func touchTS(value reflect.Value) {
|
|
now := time.Now().Format(time.RFC3339)
|
|
getFieldValue(value, "LastUpdateTime").SetString(now)
|
|
}
|
|
|
|
func getStatus(obj interface{}, condName string) string {
|
|
cond := findOrNotCreateCond(obj, condName)
|
|
if cond == nil {
|
|
return ""
|
|
}
|
|
return getFieldValue(*cond, "Status").String()
|
|
}
|
|
|
|
func setTS(obj interface{}, condName, ts string) {
|
|
cond := findOrCreateCond(obj, condName)
|
|
getFieldValue(cond, "LastUpdateTime").SetString(ts)
|
|
}
|
|
|
|
func getTS(obj interface{}, condName string) string {
|
|
cond := findOrNotCreateCond(obj, condName)
|
|
if cond == nil {
|
|
return ""
|
|
}
|
|
return getFieldValue(*cond, "LastUpdateTime").String()
|
|
}
|
|
|
|
func setStatus(obj interface{}, condName, status string) {
|
|
cond := findOrCreateCond(obj, condName)
|
|
setValue(cond, "Status", status)
|
|
}
|
|
|
|
func setValue(cond reflect.Value, fieldName, newValue string) {
|
|
value := getFieldValue(cond, fieldName)
|
|
if value.String() != newValue {
|
|
value.SetString(newValue)
|
|
touchTS(cond)
|
|
}
|
|
}
|
|
|
|
func findOrNotCreateCond(obj interface{}, condName string) *reflect.Value {
|
|
condSlice := getValue(obj, "Status", "Conditions")
|
|
return findCond(condSlice, condName)
|
|
}
|
|
|
|
func findOrCreateCond(obj interface{}, condName string) reflect.Value {
|
|
condSlice := getValue(obj, "Status", "Conditions")
|
|
cond := findCond(condSlice, condName)
|
|
if cond != nil {
|
|
return *cond
|
|
}
|
|
|
|
newCond := reflect.New(condSlice.Type().Elem()).Elem()
|
|
newCond.FieldByName("Type").SetString(condName)
|
|
newCond.FieldByName("Status").SetString("Unknown")
|
|
condSlice.Set(reflect.Append(condSlice, newCond))
|
|
return *findCond(condSlice, condName)
|
|
}
|
|
|
|
func findCond(val reflect.Value, name string) *reflect.Value {
|
|
for i := 0; i < val.Len(); i++ {
|
|
cond := val.Index(i)
|
|
typeVal := getFieldValue(cond, "Type")
|
|
if typeVal.String() == name {
|
|
return &cond
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getValue(obj interface{}, name ...string) reflect.Value {
|
|
if obj == nil {
|
|
return reflect.Value{}
|
|
}
|
|
v := reflect.ValueOf(obj)
|
|
t := v.Type()
|
|
if t.Kind() == reflect.Ptr {
|
|
v = v.Elem()
|
|
t = v.Type()
|
|
}
|
|
|
|
field := v.FieldByName(name[0])
|
|
if len(name) == 1 {
|
|
return field
|
|
}
|
|
return getFieldValue(field, name[1:]...)
|
|
}
|
|
|
|
func getFieldValue(v reflect.Value, name ...string) reflect.Value {
|
|
field := v.FieldByName(name[0])
|
|
if len(name) == 1 {
|
|
return field
|
|
}
|
|
return getFieldValue(field, name[1:]...)
|
|
}
|
|
|
|
func Error(reason string, err error) error {
|
|
return &conditionError{
|
|
reason: reason,
|
|
message: err.Error(),
|
|
}
|
|
}
|
|
|
|
type conditionError struct {
|
|
reason string
|
|
message string
|
|
}
|
|
|
|
func (e *conditionError) Error() string {
|
|
return e.message
|
|
}
|