718df344 |
cmd.MarkFlagFilename("filename", "yaml", "yml", "json")
return cmd
}
// Complete takes command line information to fill out BackendOptions or returns an error.
func (o *BackendsOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, args []string) error {
cmdNamespace, explicit, err := f.DefaultNamespace()
if err != nil {
return err
}
clientConfig, err := f.ClientConfig()
if err != nil {
return err
}
o.OutputVersion, err = kcmdutil.OutputVersion(cmd, clientConfig.GroupVersion)
if err != nil {
return err
}
var resources []string
for _, arg := range args {
if !strings.Contains(arg, "=") {
resources = append(resources, arg)
continue
}
input, err := ParseBackendInput(arg)
if err != nil {
return fmt.Errorf("invalid argument %q: %v", arg, err)
}
o.Transform.Inputs = append(o.Transform.Inputs, *input)
}
o.PrintTable = o.Transform.Empty()
mapper, typer := f.Object(false)
o.Builder = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), kapi.Codecs.UniversalDecoder()).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(explicit, false, o.Filenames...).
SelectorParam(o.Selector).
SelectAllParam(o.All).
ResourceNames("route", resources...).
Flatten()
if len(resources) == 0 {
o.Builder.ResourceTypes("routes")
}
output := kcmdutil.GetFlagString(cmd, "output")
if len(output) != 0 {
o.PrintObject = func(obj runtime.Object) error { return f.PrintObject(cmd, mapper, obj, o.Out) }
}
o.Encoder = f.JSONEncoder()
o.ShortOutput = kcmdutil.GetFlagString(cmd, "output") == "name"
o.Mapper = mapper
return nil
}
// Validate verifies the provided options are valid or returns an error.
func (o *BackendsOptions) Validate() error {
return o.Transform.Validate()
}
// Run executes the BackendOptions or returns an error.
func (o *BackendsOptions) Run() error {
infos := o.Infos
singular := len(o.Infos) <= 1
if o.Builder != nil {
loaded, err := o.Builder.Do().IntoSingular(&singular).Infos()
if err != nil {
return err
}
infos = loaded
}
if o.PrintTable && o.PrintObject == nil {
return o.printBackends(infos)
}
patches := CalculatePatches(infos, o.Encoder, func(info *resource.Info) (bool, error) {
return UpdateBackendsForObject(info.Object, o.Transform.Apply)
})
if singular && len(patches) == 0 {
return fmt.Errorf("%s/%s is not a deployment config or build config", infos[0].Mapping.Resource, infos[0].Name)
}
if o.PrintObject != nil {
object, err := resource.AsVersionedObject(infos, !singular, o.OutputVersion, kapi.Codecs.LegacyCodec(o.OutputVersion))
if err != nil {
return err
}
return o.PrintObject(object)
}
failed := false
for _, patch := range patches {
info := patch.Info
if patch.Err != nil {
failed = true
fmt.Fprintf(o.Err, "error: %s/%s %v\n", info.Mapping.Resource, info.Name, patch.Err)
continue
}
if string(patch.Patch) == "{}" || len(patch.Patch) == 0 {
fmt.Fprintf(o.Err, "info: %s %q was not changed\n", info.Mapping.Resource, info.Name)
continue
}
glog.V(4).Infof("Calculated patch %s", patch.Patch)
obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, kapi.StrategicMergePatchType, patch.Patch)
if err != nil {
handlePodUpdateError(o.Err, err, "altered")
failed = true
continue
}
info.Refresh(obj, true) |
718df344 |
}
if failed {
return cmdutil.ErrExit
}
return nil
}
// printBackends displays a tabular output of the backends for each object.
func (o *BackendsOptions) printBackends(infos []*resource.Info) error {
w := tabwriter.NewWriter(o.Out, 0, 2, 2, ' ', 0)
defer w.Flush()
fmt.Fprintf(w, "NAME\tKIND\tTO\tWEIGHT\n")
for _, info := range infos {
_, err := UpdateBackendsForObject(info.Object, func(backends *Backends) error {
totalWeight := int32(0)
for _, b := range backends.Backends {
if b.Weight != nil {
totalWeight += *b.Weight
}
}
for _, b := range backends.Backends {
switch {
case b.Weight == nil:
fmt.Fprintf(w, "%s/%s\t%s\t%s\t%s\n", info.Mapping.Resource, info.Name, b.Kind, b.Name, "")
case totalWeight == 0, len(backends.Backends) == 1 && totalWeight != 0:
fmt.Fprintf(w, "%s/%s\t%s\t%s\t%d\n", info.Mapping.Resource, info.Name, b.Kind, b.Name, totalWeight)
default:
fmt.Fprintf(w, "%s/%s\t%s\t%s\t%d (%d%%)\n", info.Mapping.Resource, info.Name, b.Kind, b.Name, *b.Weight, *b.Weight*100/totalWeight)
}
}
return nil
})
if err != nil {
fmt.Fprintf(w, "%s/%s\t%s\t%s\t%d\n", info.Mapping.Resource, info.Name, "", "<error>", 0)
}
}
return nil
}
// BackendTransform describes the desired transformation of backends.
type BackendTransform struct {
// Adjust expects a single Input to transform, relative to other backends.
Adjust bool
// Zero sets all backend weights to zero.
Zero bool
// Equal means backends will be set to equal weights.
Equal bool
// Inputs is the desired backends.
Inputs []BackendInput
}
// Empty returns true if no transformations have been specified.
func (t BackendTransform) Empty() bool {
return !(t.Zero || t.Equal || len(t.Inputs) > 0)
}
// Validate returns an error if the transformations are not internally consistent.
func (t BackendTransform) Validate() error {
switch {
case t.Adjust:
if t.Zero {
return fmt.Errorf("--adjust and --zero may not be specified together")
}
if t.Equal {
return fmt.Errorf("--adjust and --equal may not be specified together")
}
if len(t.Inputs) != 1 {
return fmt.Errorf("only one backend may be specified when adjusting")
}
case t.Zero, t.Equal:
if t.Equal && t.Zero {
return fmt.Errorf("--zero and --equal may not be specified together")
}
if len(t.Inputs) > 0 {
return fmt.Errorf("arguments may not be provided when --zero or --equal is specified")
}
default:
percent := false
names := sets.NewString()
for i, input := range t.Inputs {
if names.Has(input.Name) {
return fmt.Errorf("backend name %q may only be specified once", input.Name)
}
names.Insert(input.Name)
if input.Percentage {
if !percent && i != 0 {
return fmt.Errorf("all backends must either be percentages or weights")
}
percent = true
}
if input.Value < 0 {
return fmt.Errorf("negative percentages are not allowed")
}
}
}
return nil
}
// Apply transforms the provided backends or returns an error.
func (t BackendTransform) Apply(b *Backends) error {
switch {
case t.Zero:
zero := int32(0)
for i := range b.Backends {
b.Backends[i].Weight = &zero
}
case t.Equal:
equal := int32(100)
for i := range b.Backends {
b.Backends[i].Weight = &equal
}
case t.Adjust:
input := t.Inputs[0]
switch {
case len(b.Backends) == 0:
return fmt.Errorf("no backends can be adjusted")
case len(b.Backends) == 1:
// treat adjusting primary specially
backend := &b.Backends[0]
if backend.Name != input.Name {
return fmt.Errorf("backend %q is not in the list of backends (%s)", input.Name, strings.Join(b.Names(), ", "))
}
if input.Relative {
return fmt.Errorf("cannot adjust a single backend by relative weight")
}
// ignore distinction between percentage and weight for single backend
backend.Weight = &input.Value
case b.Backends[0].Name == input.Name:
// changing the primary backend, multiple available
if len(b.Backends) == 1 {
input.Apply(&b.Backends[0], nil, b.Backends)
return nil
}
input.Apply(&b.Backends[0], &b.Backends[1], b.Backends)
default:
// changing an alternate backend, multiple available
for i := range b.Backends {
if b.Backends[i].Name != input.Name {
continue
}
input.Apply(&b.Backends[i], &b.Backends[0], b.Backends)
return nil
}
return fmt.Errorf("backend %q is not in the list of backends (%s)", input.Name, strings.Join(b.Names(), ", "))
}
default:
b.Backends = nil
for _, input := range t.Inputs {
weight := input.Value
b.Backends = append(b.Backends, routeapi.RouteTargetReference{
Kind: "Service",
Name: input.Name,
Weight: &weight,
})
}
}
return nil
}
// BackendInput describes a change to a named service.
type BackendInput struct {
// Name is the name of a service.
Name string
// Value is the amount to change.
Value int32
// Percentage means value should be interpreted as a percentage between -100 and 100, inclusive.
Percentage bool
// Relative means value is applied relative to the current values.
Relative bool
}
// Apply alters the weights of two services.
func (input *BackendInput) Apply(ref, to *routeapi.RouteTargetReference, backends []routeapi.RouteTargetReference) {
weight := int32(100)
if ref.Weight != nil {
weight = *ref.Weight
}
switch {
case input.Percentage:
if to == nil {
weight += (weight * input.Value) / 100
ref.Weight = &weight
return
}
otherWeight := int32(0)
if to.Weight != nil {
otherWeight = *to.Weight
}
previousWeight := weight + otherWeight
// rebalance all other backends to be relative in weight to the current
for i, other := range backends {
if previousWeight == 0 || other.Weight == nil || other.Name == ref.Name || other.Name == to.Name {
continue
}
adjusted := *other.Weight * 100 / previousWeight
backends[i].Weight = &adjusted
}
// adjust the weight between ref and to
target := float32(input.Value) / 100
if input.Relative {
if previousWeight != 0 {
percent := float32(weight) / float32(previousWeight)
target = percent + target
}
}
switch {
case target < 0:
target = 0
case target > 1:
target = 1
}
weight = int32(target * 100)
otherWeight = int32((1 - target) * 100)
ref.Weight = &weight
to.Weight = &otherWeight
// rescale the max to 200 in case we are dealing with very small percentages
max := int32(0)
for _, other := range backends {
if other.Weight == nil {
continue
}
if *other.Weight > max {
max = *other.Weight
}
}
if max > 256 {
for i, other := range backends {
if other.Weight == nil || *other.Weight == 0 {
continue
}
adjusted := 200 * *other.Weight / max
if adjusted < 1 {
adjusted = 1
}
backends[i].Weight = &adjusted
}
}
case input.Relative:
weight += input.Value
if weight < 0 {
weight = 0
}
ref.Weight = &weight
default:
ref.Weight = &input.Value
}
}
// ParseBackendInput turns the provided input into a BackendInput or returns an error.
func ParseBackendInput(s string) (*BackendInput, error) {
parts := strings.SplitN(s, "=", 2)
switch {
case len(parts) != 2, len(parts[0]) == 0, len(parts[1]) == 0:
return nil, fmt.Errorf("expected NAME=WEIGHT")
}
if strings.Contains(parts[0], "/") {
return nil, fmt.Errorf("only NAME=WEIGHT may be specified")
}
input := &BackendInput{}
input.Name = parts[0]
if strings.HasSuffix(parts[1], "%") {
input.Percentage = true
parts[1] = strings.TrimSuffix(parts[1], "%")
}
if strings.HasPrefix(parts[1], "+") {
input.Relative = true
parts[1] = strings.TrimPrefix(parts[1], "+")
}
value, err := strconv.Atoi(parts[1])
if err != nil {
return nil, fmt.Errorf("WEIGHT must be a number: %v", err)
}
input.Value = int32(value)
if input.Value < 0 {
input.Relative = true
}
return input, nil
}
// Backends is a struct that represents the backends to be transformed.
type Backends struct {
Backends []routeapi.RouteTargetReference
}
// Names returns the referenced backend service names, in the order they appear.
func (b *Backends) Names() []string {
var names []string
for _, backend := range b.Backends {
names = append(names, backend.Name)
}
return names
}
// UpdateBackendsForObject extracts a backend definition array from the provided object, passes it to fn,
// and then applies the backend on the object. It returns true if the object was mutated and an optional error
// if any part of the flow returns error.
func UpdateBackendsForObject(obj runtime.Object, fn func(*Backends) error) (bool, error) {
// TODO: replace with a swagger schema based approach (identify pod template via schema introspection)
switch t := obj.(type) {
case *routeapi.Route:
b := &Backends{
Backends: []routeapi.RouteTargetReference{t.Spec.To},
}
for _, backend := range t.Spec.AlternateBackends {
b.Backends = append(b.Backends, backend)
}
if err := fn(b); err != nil {
return true, err
}
if len(b.Backends) == 0 {
t.Spec.To = routeapi.RouteTargetReference{}
} else {
t.Spec.To = b.Backends[0]
}
if len(b.Backends) > 1 {
t.Spec.AlternateBackends = b.Backends[1:]
} else {
t.Spec.AlternateBackends = nil
}
return true, nil
default:
return false, fmt.Errorf("the object is not a route")
}
} |