2017-08-23 18:33:00 +00:00
/ *
Copyright 2016 The Kubernetes Authors All rights reserved .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2019-08-19 23:11:38 +00:00
package kapi
2017-08-23 18:33:00 +00:00
import (
2019-07-09 10:03:09 +00:00
"context"
2017-08-23 18:33:00 +00:00
"fmt"
2020-06-25 16:40:14 +00:00
"path"
2017-08-23 18:33:00 +00:00
"time"
2019-05-14 04:57:59 +00:00
apps "k8s.io/api/apps/v1"
core "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
2017-08-23 18:33:00 +00:00
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
2019-09-12 16:43:34 +00:00
"k8s.io/client-go/rest"
2017-08-23 18:33:00 +00:00
"k8s.io/client-go/tools/clientcmd"
2019-07-09 10:03:09 +00:00
watchtools "k8s.io/client-go/tools/watch"
2020-09-29 22:49:41 +00:00
"k8s.io/klog/v2"
2019-05-15 04:45:35 +00:00
"k8s.io/minikube/pkg/minikube/proxy"
2020-06-25 16:40:14 +00:00
"k8s.io/minikube/pkg/minikube/vmpath"
2021-12-07 02:56:25 +00:00
kconst "k8s.io/minikube/third_party/kubeadm/app/constants"
2019-02-14 05:15:46 +00:00
)
var (
2019-03-01 00:26:42 +00:00
// ReasonableMutateTime is how long to wait for basic object mutations, such as deletions, to show up
2019-06-14 23:03:23 +00:00
ReasonableMutateTime = time . Minute * 2
2019-08-02 21:34:03 +00:00
// ReasonableStartTime is how long to wait for pods to start
2019-08-18 06:18:13 +00:00
ReasonableStartTime = time . Minute * 5
2017-08-23 18:33:00 +00:00
)
2019-09-12 16:43:34 +00:00
// ClientConfig returns the client configuration for a kubectl context
func ClientConfig ( context string ) ( * rest . Config , error ) {
loader := clientcmd . NewDefaultClientConfigLoadingRules ( )
cc := clientcmd . NewNonInteractiveDeferredLoadingClientConfig ( loader , & clientcmd . ConfigOverrides { CurrentContext : context } )
c , err := cc . ClientConfig ( )
2017-08-23 18:33:00 +00:00
if err != nil {
2019-09-12 16:43:34 +00:00
return nil , fmt . Errorf ( "client config: %v" , err )
2017-08-23 18:33:00 +00:00
}
2019-09-12 16:43:34 +00:00
c = proxy . UpdateTransport ( c )
2020-09-29 22:49:41 +00:00
klog . V ( 1 ) . Infof ( "client config for %s: %+v" , context , c )
2019-09-12 16:43:34 +00:00
return c , nil
}
2020-05-07 21:12:10 +00:00
// Client gets the Kubernetes client for a kubectl context name
2019-09-12 16:43:34 +00:00
func Client ( context string ) ( * kubernetes . Clientset , error ) {
c , err := ClientConfig ( context )
2017-08-23 18:33:00 +00:00
if err != nil {
2019-09-12 16:43:34 +00:00
return nil , err
2017-08-23 18:33:00 +00:00
}
2019-09-12 16:43:34 +00:00
return kubernetes . NewForConfig ( c )
2017-08-23 18:33:00 +00:00
}
2023-01-09 02:23:07 +00:00
// WaitForPods waits for all matching pods to become Running or finish successfully and at least one matching pod exists.
2020-06-26 21:22:17 +00:00
func WaitForPods ( c kubernetes . Interface , ns string , selector string , timeOut ... time . Duration ) error {
2019-08-18 06:18:13 +00:00
start := time . Now ( )
2020-09-29 22:49:41 +00:00
klog . Infof ( "Waiting for pod with label %q in ns %q ..." , selector , ns )
2017-09-08 02:49:42 +00:00
lastKnownPodNumber := - 1
2023-04-12 22:03:13 +00:00
f := func ( ctx context . Context ) ( bool , error ) {
2020-06-26 21:22:17 +00:00
listOpts := meta . ListOptions { LabelSelector : selector }
2023-04-12 22:03:13 +00:00
pods , err := c . CoreV1 ( ) . Pods ( ns ) . List ( ctx , listOpts )
2017-09-08 02:49:42 +00:00
if err != nil {
2020-09-29 22:49:41 +00:00
klog . Infof ( "temporary error: getting Pods with label selector %q : [%v]\n" , selector , err )
2017-09-08 02:49:42 +00:00
return false , nil
}
if lastKnownPodNumber != len ( pods . Items ) {
2020-09-29 22:49:41 +00:00
klog . Infof ( "Found %d Pods for label selector %s\n" , len ( pods . Items ) , selector )
2017-09-08 02:49:42 +00:00
lastKnownPodNumber = len ( pods . Items )
}
if len ( pods . Items ) == 0 {
return false , nil
2017-08-23 18:33:00 +00:00
}
2017-09-08 02:49:42 +00:00
for _ , pod := range pods . Items {
2020-06-26 21:22:17 +00:00
if pod . Status . Phase != core . PodRunning && pod . Status . Phase != core . PodSucceeded {
2023-01-09 02:23:07 +00:00
klog . Infof ( "waiting for pod %q, current state: %s: [%v]\n" , selector , pod . Status . Phase , err )
2017-09-08 02:49:42 +00:00
return false , nil
2017-08-23 18:33:00 +00:00
}
}
2017-09-08 02:49:42 +00:00
return true , nil
2019-08-18 06:18:13 +00:00
}
2019-08-18 08:14:02 +00:00
t := ReasonableStartTime
if timeOut != nil {
t = timeOut [ 0 ]
}
2023-04-12 22:03:13 +00:00
err := wait . PollUntilContextTimeout ( context . Background ( ) , kconst . APICallRetryInterval , t , true , f )
2020-09-29 22:49:41 +00:00
klog . Infof ( "duration metric: took %s to wait for %s ..." , time . Since ( start ) , selector )
2019-08-18 06:18:13 +00:00
return err
2017-08-23 18:33:00 +00:00
}
2019-08-29 13:51:38 +00:00
// WaitForDeploymentToStabilize waits till the Deployment has a matching generation/replica count between spec and status. used by integration tests
2018-01-09 18:15:07 +00:00
func WaitForDeploymentToStabilize ( c kubernetes . Interface , ns , name string , timeout time . Duration ) error {
2019-05-14 04:57:59 +00:00
options := meta . ListOptions { FieldSelector : fields . Set {
2018-01-09 18:15:07 +00:00
"metadata.name" : name ,
"metadata.namespace" : ns ,
} . AsSelector ( ) . String ( ) }
2019-07-09 10:03:09 +00:00
ctx , cancel := watchtools . ContextWithOptionalTimeout ( context . Background ( ) , timeout )
defer cancel ( )
2021-03-02 00:45:02 +00:00
w , err := c . AppsV1 ( ) . Deployments ( ns ) . Watch ( ctx , options )
if err != nil {
return err
}
2019-07-09 10:03:09 +00:00
_ , err = watchtools . UntilWithoutRetry ( ctx , w , func ( event watch . Event ) ( bool , error ) {
2019-05-14 01:31:55 +00:00
if event . Type == watch . Deleted {
2019-05-14 04:57:59 +00:00
return false , apierr . NewNotFound ( schema . GroupResource { Resource : "deployments" } , "" )
2018-01-09 18:15:07 +00:00
}
2019-05-14 04:57:59 +00:00
dp , ok := event . Object . ( * apps . Deployment )
2019-05-14 01:31:55 +00:00
if ok {
2018-01-09 18:15:07 +00:00
if dp . Name == name && dp . Namespace == ns &&
dp . Generation <= dp . Status . ObservedGeneration &&
* ( dp . Spec . Replicas ) == dp . Status . Replicas {
return true , nil
}
2020-09-29 22:49:41 +00:00
klog . Infof ( "Waiting for deployment %s to stabilize, generation %v observed generation %v spec.replicas %d status.replicas %d" ,
2018-01-09 18:15:07 +00:00
name , dp . Generation , dp . Status . ObservedGeneration , * ( dp . Spec . Replicas ) , dp . Status . Replicas )
}
return false , nil
} )
return err
2017-08-23 18:33:00 +00:00
}
// WaitForService waits until the service appears (exist == true), or disappears (exist == false)
2017-09-08 02:49:42 +00:00
func WaitForService ( c kubernetes . Interface , namespace , name string , exist bool , interval , timeout time . Duration ) error {
2023-04-12 22:03:13 +00:00
err := wait . PollUntilContextTimeout ( context . Background ( ) , interval , timeout , true , func ( ctx context . Context ) ( bool , error ) {
_ , err := c . CoreV1 ( ) . Services ( namespace ) . Get ( ctx , name , meta . GetOptions { } )
2017-08-23 18:33:00 +00:00
switch {
case err == nil :
2020-09-29 22:49:41 +00:00
klog . Infof ( "Service %s in namespace %s found." , name , namespace )
2017-08-23 18:33:00 +00:00
return exist , nil
2019-05-14 04:57:59 +00:00
case apierr . IsNotFound ( err ) :
2020-09-29 22:49:41 +00:00
klog . Infof ( "Service %s in namespace %s disappeared." , name , namespace )
2017-08-23 18:33:00 +00:00
return ! exist , nil
case ! IsRetryableAPIError ( err ) :
2020-09-29 22:49:41 +00:00
klog . Info ( "Non-retryable failure while getting service." )
2017-08-23 18:33:00 +00:00
return false , err
default :
2020-09-29 22:49:41 +00:00
klog . Infof ( "Get service %s in namespace %s failed: %v" , name , namespace , err )
2017-08-23 18:33:00 +00:00
return false , nil
}
} )
if err != nil {
stateMsg := map [ bool ] string { true : "to appear" , false : "to disappear" }
return fmt . Errorf ( "error waiting for service %s/%s %s: %v" , namespace , name , stateMsg [ exist ] , err )
}
return nil
}
2019-03-16 13:12:18 +00:00
// IsRetryableAPIError returns if this error is retryable or not
2017-08-23 18:33:00 +00:00
func IsRetryableAPIError ( err error ) bool {
2019-05-14 04:57:59 +00:00
return apierr . IsTimeout ( err ) || apierr . IsServerTimeout ( err ) || apierr . IsTooManyRequests ( err ) || apierr . IsInternalError ( err )
2017-08-23 18:33:00 +00:00
}
2020-06-25 16:40:14 +00:00
// KubectlBinaryPath returns the path to kubectl on the node
func KubectlBinaryPath ( version string ) string {
return path . Join ( vmpath . GuestPersistentDir , "binaries" , version , "kubectl" )
}
2021-03-01 03:36:03 +00:00
2021-03-27 23:20:23 +00:00
// ScaleDeployment tries to set the number of deployment replicas in namespace and context.
// It will retry (usually needed due to "the object has been modified; please apply your changes to the latest version and try again" error) up to ReasonableMutateTime to ensure target scale is achieved.
2021-03-02 00:45:02 +00:00
func ScaleDeployment ( kcontext , namespace , deploymentName string , replicas int ) error {
client , err := Client ( kcontext )
2021-03-01 03:36:03 +00:00
if err != nil {
return fmt . Errorf ( "client: %v" , err )
}
2023-04-12 22:03:13 +00:00
err = wait . PollUntilContextTimeout ( context . Background ( ) , kconst . APICallRetryInterval , ReasonableMutateTime , true , func ( ctx context . Context ) ( bool , error ) {
scale , err := client . AppsV1 ( ) . Deployments ( namespace ) . GetScale ( ctx , deploymentName , meta . GetOptions { } )
2021-03-01 03:36:03 +00:00
if err != nil {
2023-01-10 22:43:05 +00:00
if ! IsRetryableAPIError ( err ) {
return false , fmt . Errorf ( "non-retryable failure while getting %q deployment scale: %v" , deploymentName , err )
2022-12-05 01:27:02 +00:00
}
2023-01-10 22:43:05 +00:00
klog . Warningf ( "failed getting %q deployment scale, will retry: %v" , deploymentName , err )
return false , nil
2021-03-27 23:20:23 +00:00
}
if scale . Spec . Replicas != int32 ( replicas ) {
scale . Spec . Replicas = int32 ( replicas )
2023-04-12 22:03:13 +00:00
if _ , err = client . AppsV1 ( ) . Deployments ( namespace ) . UpdateScale ( ctx , deploymentName , scale , meta . UpdateOptions { } ) ; err != nil {
2023-01-10 22:43:05 +00:00
if ! IsRetryableAPIError ( err ) {
return false , fmt . Errorf ( "non-retryable failure while rescaling %s deployment: %v" , deploymentName , err )
2022-12-09 02:18:17 +00:00
}
2023-01-10 22:43:05 +00:00
klog . Warningf ( "failed rescaling %s deployment, will retry: %v" , deploymentName , err )
2021-03-27 23:20:23 +00:00
}
// repeat (if change was successful - once again to check & confirm requested scale)
return false , nil
2021-03-01 03:36:03 +00:00
}
2021-03-27 23:20:23 +00:00
return true , nil
} )
if err != nil {
2023-01-12 21:18:49 +00:00
klog . Warningf ( "failed rescaling %q deployment in %q namespace and %q context to %d replicas: %v" , deploymentName , namespace , kcontext , replicas , err )
2021-03-27 23:20:23 +00:00
return err
2021-03-01 03:36:03 +00:00
}
2022-12-09 02:18:17 +00:00
klog . Infof ( "%q deployment in %q namespace and %q context rescaled to %d replicas" , deploymentName , namespace , kcontext , replicas )
2021-03-01 03:36:03 +00:00
return nil
}