scale.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package kubectl
  14. import (
  15. "fmt"
  16. "strconv"
  17. "time"
  18. autoscalingv1 "k8s.io/api/autoscaling/v1"
  19. "k8s.io/apimachinery/pkg/api/errors"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/runtime/schema"
  22. "k8s.io/apimachinery/pkg/util/wait"
  23. scaleclient "k8s.io/client-go/scale"
  24. )
  25. // Scaler provides an interface for resources that can be scaled.
  26. type Scaler interface {
  27. // Scale scales the named resource after checking preconditions. It optionally
  28. // retries in the event of resource version mismatch (if retry is not nil),
  29. // and optionally waits until the status of the resource matches newSize (if wait is not nil)
  30. // TODO: Make the implementation of this watch-based (#56075) once #31345 is fixed.
  31. Scale(namespace, name string, newSize uint, preconditions *ScalePrecondition, retry, wait *RetryParams, gr schema.GroupResource) error
  32. // ScaleSimple does a simple one-shot attempt at scaling - not useful on its own, but
  33. // a necessary building block for Scale
  34. ScaleSimple(namespace, name string, preconditions *ScalePrecondition, newSize uint, gr schema.GroupResource) (updatedResourceVersion string, err error)
  35. }
  36. // NewScaler get a scaler for a given resource
  37. func NewScaler(scalesGetter scaleclient.ScalesGetter) Scaler {
  38. return &genericScaler{scalesGetter}
  39. }
  40. // ScalePrecondition describes a condition that must be true for the scale to take place
  41. // If CurrentSize == -1, it is ignored.
  42. // If CurrentResourceVersion is the empty string, it is ignored.
  43. // Otherwise they must equal the values in the resource for it to be valid.
  44. type ScalePrecondition struct {
  45. Size int
  46. ResourceVersion string
  47. }
  48. // A PreconditionError is returned when a resource fails to match
  49. // the scale preconditions passed to kubectl.
  50. type PreconditionError struct {
  51. Precondition string
  52. ExpectedValue string
  53. ActualValue string
  54. }
  55. func (pe PreconditionError) Error() string {
  56. return fmt.Sprintf("Expected %s to be %s, was %s", pe.Precondition, pe.ExpectedValue, pe.ActualValue)
  57. }
  58. // RetryParams encapsulates the retry parameters used by kubectl's scaler.
  59. type RetryParams struct {
  60. Interval, Timeout time.Duration
  61. }
  62. func NewRetryParams(interval, timeout time.Duration) *RetryParams {
  63. return &RetryParams{interval, timeout}
  64. }
  65. // ScaleCondition is a closure around Scale that facilitates retries via util.wait
  66. func ScaleCondition(r Scaler, precondition *ScalePrecondition, namespace, name string, count uint, updatedResourceVersion *string, gr schema.GroupResource) wait.ConditionFunc {
  67. return func() (bool, error) {
  68. rv, err := r.ScaleSimple(namespace, name, precondition, count, gr)
  69. if updatedResourceVersion != nil {
  70. *updatedResourceVersion = rv
  71. }
  72. // Retry only on update conflicts.
  73. if errors.IsConflict(err) {
  74. return false, nil
  75. }
  76. if err != nil {
  77. return false, err
  78. }
  79. return true, nil
  80. }
  81. }
  82. // validateGeneric ensures that the preconditions match. Returns nil if they are valid, otherwise an error
  83. func (precondition *ScalePrecondition) validate(scale *autoscalingv1.Scale) error {
  84. if precondition.Size != -1 && int(scale.Spec.Replicas) != precondition.Size {
  85. return PreconditionError{"replicas", strconv.Itoa(precondition.Size), strconv.Itoa(int(scale.Spec.Replicas))}
  86. }
  87. if len(precondition.ResourceVersion) > 0 && scale.ResourceVersion != precondition.ResourceVersion {
  88. return PreconditionError{"resource version", precondition.ResourceVersion, scale.ResourceVersion}
  89. }
  90. return nil
  91. }
  92. // genericScaler can update scales for resources in a particular namespace
  93. type genericScaler struct {
  94. scaleNamespacer scaleclient.ScalesGetter
  95. }
  96. var _ Scaler = &genericScaler{}
  97. // ScaleSimple updates a scale of a given resource. It returns the resourceVersion of the scale if the update was successful.
  98. func (s *genericScaler) ScaleSimple(namespace, name string, preconditions *ScalePrecondition, newSize uint, gr schema.GroupResource) (updatedResourceVersion string, err error) {
  99. scale := &autoscalingv1.Scale{
  100. ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name},
  101. }
  102. if preconditions != nil {
  103. var err error
  104. scale, err = s.scaleNamespacer.Scales(namespace).Get(gr, name)
  105. if err != nil {
  106. return "", err
  107. }
  108. if err := preconditions.validate(scale); err != nil {
  109. return "", err
  110. }
  111. }
  112. scale.Spec.Replicas = int32(newSize)
  113. updatedScale, err := s.scaleNamespacer.Scales(namespace).Update(gr, scale)
  114. if err != nil {
  115. return "", err
  116. }
  117. return updatedScale.ResourceVersion, nil
  118. }
  119. // Scale updates a scale of a given resource to a new size, with optional precondition check (if preconditions is not nil),
  120. // optional retries (if retry is not nil), and then optionally waits for the status to reach desired count.
  121. func (s *genericScaler) Scale(namespace, resourceName string, newSize uint, preconditions *ScalePrecondition, retry, waitForReplicas *RetryParams, gr schema.GroupResource) error {
  122. if retry == nil {
  123. // make it try only once, immediately
  124. retry = &RetryParams{Interval: time.Millisecond, Timeout: time.Millisecond}
  125. }
  126. cond := ScaleCondition(s, preconditions, namespace, resourceName, newSize, nil, gr)
  127. if err := wait.PollImmediate(retry.Interval, retry.Timeout, cond); err != nil {
  128. return err
  129. }
  130. if waitForReplicas != nil {
  131. return WaitForScaleHasDesiredReplicas(s.scaleNamespacer, gr, resourceName, namespace, newSize, waitForReplicas)
  132. }
  133. return nil
  134. }
  135. // scaleHasDesiredReplicas returns a condition that will be true if and only if the desired replica
  136. // count for a scale (Spec) equals its updated replicas count (Status)
  137. func scaleHasDesiredReplicas(sClient scaleclient.ScalesGetter, gr schema.GroupResource, resourceName string, namespace string, desiredReplicas int32) wait.ConditionFunc {
  138. return func() (bool, error) {
  139. actualScale, err := sClient.Scales(namespace).Get(gr, resourceName)
  140. if err != nil {
  141. return false, err
  142. }
  143. // this means the desired scale target has been reset by something else
  144. if actualScale.Spec.Replicas != desiredReplicas {
  145. return true, nil
  146. }
  147. return actualScale.Spec.Replicas == actualScale.Status.Replicas &&
  148. desiredReplicas == actualScale.Status.Replicas, nil
  149. }
  150. }
  151. // WaitForScaleHasDesiredReplicas waits until condition scaleHasDesiredReplicas is satisfied
  152. // or returns error when timeout happens
  153. func WaitForScaleHasDesiredReplicas(sClient scaleclient.ScalesGetter, gr schema.GroupResource, resourceName string, namespace string, newSize uint, waitForReplicas *RetryParams) error {
  154. if waitForReplicas == nil {
  155. return fmt.Errorf("waitForReplicas parameter cannot be nil")
  156. }
  157. err := wait.PollImmediate(
  158. waitForReplicas.Interval,
  159. waitForReplicas.Timeout,
  160. scaleHasDesiredReplicas(sClient, gr, resourceName, namespace, int32(newSize)))
  161. if err == wait.ErrWaitTimeout {
  162. return fmt.Errorf("timed out waiting for %q to be synced", resourceName)
  163. }
  164. return err
  165. }