2019-12-18 19:31:29 +00:00
/ *
Copyright 2019 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 .
* /
package addons
import (
2019-12-19 05:35:46 +00:00
"fmt"
2020-01-30 23:54:04 +00:00
"path"
2020-06-25 22:35:58 +00:00
"runtime"
2020-02-03 18:49:40 +00:00
"sort"
2019-12-18 19:31:29 +00:00
"strconv"
2020-01-31 01:46:25 +00:00
"strings"
2020-04-08 19:29:23 +00:00
"sync"
2020-01-31 01:46:25 +00:00
"time"
2019-12-18 19:31:29 +00:00
"github.com/pkg/errors"
2020-06-24 23:31:46 +00:00
"github.com/spf13/viper"
2020-04-15 14:30:10 +00:00
2020-09-29 22:49:41 +00:00
"k8s.io/klog/v2"
2020-04-17 00:49:26 +00:00
"k8s.io/minikube/pkg/drivers/kic/oci"
2020-06-24 23:31:46 +00:00
"k8s.io/minikube/pkg/kapi"
2019-12-18 19:31:29 +00:00
"k8s.io/minikube/pkg/minikube/assets"
"k8s.io/minikube/pkg/minikube/command"
"k8s.io/minikube/pkg/minikube/config"
2020-04-17 00:49:26 +00:00
"k8s.io/minikube/pkg/minikube/constants"
2020-02-28 23:58:05 +00:00
"k8s.io/minikube/pkg/minikube/driver"
2019-12-18 19:31:29 +00:00
"k8s.io/minikube/pkg/minikube/exit"
"k8s.io/minikube/pkg/minikube/machine"
"k8s.io/minikube/pkg/minikube/out"
2020-07-07 18:25:27 +00:00
"k8s.io/minikube/pkg/minikube/out/register"
2020-08-31 00:25:11 +00:00
"k8s.io/minikube/pkg/minikube/reason"
"k8s.io/minikube/pkg/minikube/style"
2021-02-24 20:48:39 +00:00
"k8s.io/minikube/pkg/minikube/sysinit"
2020-03-25 20:44:28 +00:00
"k8s.io/minikube/pkg/util/retry"
2019-12-18 19:31:29 +00:00
)
2021-03-09 21:32:09 +00:00
// Force is used to override checks for addons
var Force bool = false
2019-12-18 19:31:29 +00:00
2020-04-09 04:23:35 +00:00
// RunCallbacks runs all actions associated to an addon, but does not set it (thread-safe)
func RunCallbacks ( cc * config . ClusterConfig , name string , value string ) error {
2020-09-29 22:49:41 +00:00
klog . Infof ( "Setting %s=%s in profile %q" , name , value , cc . Name )
2019-12-19 06:21:53 +00:00
a , valid := isAddonValid ( name )
2019-12-19 05:35:46 +00:00
if ! valid {
return errors . Errorf ( "%s is not a valid addon" , name )
}
2020-03-11 00:49:47 +00:00
// Run any additional validations for this property
if err := run ( cc , name , value , a . validations ) ; err != nil {
return errors . Wrap ( err , "running validations" )
}
2019-12-19 05:35:46 +00:00
// Run any callbacks for this property
2020-03-11 00:49:47 +00:00
if err := run ( cc , name , value , a . callbacks ) ; err != nil {
2020-01-06 21:42:09 +00:00
return errors . Wrap ( err , "running callbacks" )
2019-12-19 05:35:46 +00:00
}
2020-04-09 04:23:35 +00:00
return nil
}
// Set sets a value in the config (not threadsafe)
func Set ( cc * config . ClusterConfig , name string , value string ) error {
a , valid := isAddonValid ( name )
if ! valid {
return errors . Errorf ( "%s is not a valid addon" , name )
}
return a . set ( cc , name , value )
}
// SetAndSave sets a value and saves the config
func SetAndSave ( profile string , name string , value string ) error {
cc , err := config . Load ( profile )
if err != nil {
return errors . Wrap ( err , "loading profile" )
}
if err := RunCallbacks ( cc , name , value ) ; err != nil {
return errors . Wrap ( err , "run callbacks" )
}
if err := Set ( cc , name , value ) ; err != nil {
return errors . Wrap ( err , "set" )
}
2019-12-19 05:35:46 +00:00
2020-09-29 22:49:41 +00:00
klog . Infof ( "Writing out %q config to set %s=%v..." , profile , name , value )
2020-03-11 00:49:47 +00:00
return config . Write ( profile , cc )
2019-12-19 05:35:46 +00:00
}
// Runs all the validation or callback functions and collects errors
2020-03-11 00:49:47 +00:00
func run ( cc * config . ClusterConfig , name string , value string , fns [ ] setFn ) error {
2019-12-19 05:35:46 +00:00
var errors [ ] error
for _ , fn := range fns {
2020-03-11 00:49:47 +00:00
err := fn ( cc , name , value )
2019-12-19 05:35:46 +00:00
if err != nil {
errors = append ( errors , err )
}
}
if len ( errors ) > 0 {
return fmt . Errorf ( "%v" , errors )
}
return nil
}
2020-04-09 04:23:35 +00:00
// SetBool sets a bool value in the config (not threadsafe)
2020-03-11 00:49:47 +00:00
func SetBool ( cc * config . ClusterConfig , name string , val string ) error {
2019-12-18 19:31:29 +00:00
b , err := strconv . ParseBool ( val )
if err != nil {
return err
}
2020-03-11 00:49:47 +00:00
if cc . Addons == nil {
cc . Addons = map [ string ] bool { }
2019-12-19 05:35:46 +00:00
}
2020-03-11 00:49:47 +00:00
cc . Addons [ name ] = b
2019-12-18 19:31:29 +00:00
return nil
}
2021-02-07 14:43:05 +00:00
// EnableOrDisableAddon updates addon status executing any commands necessary
func EnableOrDisableAddon ( cc * config . ClusterConfig , name string , val string ) error {
2020-09-29 22:49:41 +00:00
klog . Infof ( "Setting addon %s=%s in %q" , name , val , cc . Name )
2019-12-18 19:31:29 +00:00
enable , err := strconv . ParseBool ( val )
if err != nil {
return errors . Wrapf ( err , "parsing bool: %s" , name )
}
addon := assets . Addons [ name ]
// check addon status before enabling/disabling it
2020-04-09 04:23:35 +00:00
if isAddonAlreadySet ( cc , addon , enable ) {
2020-09-29 22:49:41 +00:00
klog . Warningf ( "addon %s should already be in state %v" , name , val )
2020-02-27 10:21:08 +00:00
if ! enable {
return nil
}
2019-12-18 19:31:29 +00:00
}
2020-07-28 20:44:30 +00:00
// to match both ingress and ingress-dns addons
if strings . HasPrefix ( name , "ingress" ) && enable {
2020-11-21 10:31:06 +00:00
if driver . IsKIC ( cc . Driver ) {
if runtime . GOOS == "windows" {
2021-02-25 19:18:24 +00:00
out . Styled ( style . Tip , ` After the addon is enabled, please run "minikube tunnel" and your ingress resources would be available at "127.0.0.1" ` )
2020-11-21 10:31:06 +00:00
} else if runtime . GOOS != "linux" {
exit . Message ( reason . Usage , ` Due to networking limitations of driver { { . driver_name } } on { { . os_name } } , { { . addon_name } } addon is not supported .
2020-06-25 22:36:34 +00:00
Alternatively to use this addon you can use a vm - based driver :
2020-04-03 00:19:45 +00:00
2020-06-25 22:36:34 +00:00
' minikube start -- vm = true '
2020-04-03 00:16:00 +00:00
2020-06-25 22:36:34 +00:00
To track the update on this work in progress feature please check :
https : //github.com/kubernetes/minikube/issues/7332`, out.V{"driver_name": cc.Driver, "os_name": runtime.GOOS, "addon_name": name})
2021-03-11 09:14:06 +00:00
} else if driver . BareMetal ( cc . Driver ) {
out . WarningT ( ` Due to networking limitations of driver {{ .driver_name }} , {{ .addon_name }} addon is not fully supported. Try using a different driver. ` ,
out . V { "driver_name" : cc . Driver , "addon_name" : name } )
2020-11-21 10:31:06 +00:00
}
2021-02-10 20:16:06 +00:00
}
2020-06-25 22:35:58 +00:00
}
2020-06-24 23:31:46 +00:00
2020-03-11 00:49:47 +00:00
if strings . HasPrefix ( name , "istio" ) && enable {
2019-12-30 15:10:05 +00:00
minMem := 8192
2020-03-11 00:49:47 +00:00
minCPUs := 4
if cc . Memory < minMem {
out . WarningT ( "Istio needs {{.minMem}}MB of memory -- your configuration only allocates {{.memory}}MB" , out . V { "minMem" : minMem , "memory" : cc . Memory } )
}
if cc . CPUs < minCPUs {
out . WarningT ( "Istio needs {{.minCPUs}} CPUs -- your configuration only allocates {{.cpus}} CPUs" , out . V { "minCPUs" : minCPUs , "cpus" : cc . CPUs } )
2019-12-30 15:10:05 +00:00
}
}
2019-12-18 19:31:29 +00:00
api , err := machine . NewAPIClient ( )
if err != nil {
return errors . Wrap ( err , "machine client" )
}
defer api . Close ( )
2020-03-11 00:49:47 +00:00
cp , err := config . PrimaryControlPlane ( cc )
if err != nil {
2020-08-31 00:25:11 +00:00
exit . Error ( reason . GuestCpConfig , "Error getting primary control plane" , err )
2019-12-18 19:31:29 +00:00
}
2021-01-08 19:26:10 +00:00
mName := config . MachineName ( * cc , cp )
2020-03-13 22:51:03 +00:00
host , err := machine . LoadHost ( api , mName )
if err != nil || ! machine . IsRunning ( api , mName ) {
2020-09-29 22:49:41 +00:00
klog . Warningf ( "%q is not running, setting %s=%v and skipping enablement (err=%v)" , mName , addon . Name ( ) , enable , err )
2020-01-31 20:19:25 +00:00
return nil
2019-12-18 19:31:29 +00:00
}
2020-04-10 19:45:43 +00:00
if name == "registry" {
2020-04-17 00:49:26 +00:00
if driver . NeedsPortForward ( cc . Driver ) {
port , err := oci . ForwardedPort ( cc . Driver , cc . Name , constants . RegistryAddonPort )
2020-04-11 05:56:34 +00:00
if err != nil {
2020-04-15 14:30:10 +00:00
return errors . Wrap ( err , "registry port" )
2020-04-11 05:56:34 +00:00
}
2021-03-12 10:24:25 +00:00
if enable {
out . Styled ( style . Tip , ` Registry addon on with {{ .driver }} uses {{ .port }} please use that instead of default 5000 ` , out . V { "driver" : cc . Driver , "port" : port } )
}
2021-02-25 19:18:24 +00:00
out . Styled ( style . Documentation , ` For more information see: https://minikube.sigs.k8s.io/docs/drivers/ {{ .driver }} ` , out . V { "driver" : cc . Driver } )
2020-04-10 19:45:43 +00:00
}
}
2021-02-22 06:41:38 +00:00
runner , err := machine . CommandRunner ( host )
2019-12-18 19:31:29 +00:00
if err != nil {
return errors . Wrap ( err , "command runner" )
}
2021-02-22 08:54:41 +00:00
if name == "auto-pause" && ! enable { // needs to be disabled before deleting the service file in the internal disable
2021-02-24 20:48:39 +00:00
if err := sysinit . New ( runner ) . DisableNow ( "auto-pause" ) ; err != nil {
2021-02-22 06:41:38 +00:00
klog . ErrorS ( err , "failed to disable" , "service" , "auto-pause" )
}
}
2021-03-17 18:52:55 +00:00
var networkInfo assets . NetworkInfo
if len ( cc . Nodes ) >= 1 {
networkInfo . CPNodeIP = cc . Nodes [ 0 ] . IP
} else {
out . WarningT ( "At least needs control plane nodes to enable addon" )
}
data := assets . GenerateTemplateData ( addon , cc . KubernetesConfig , networkInfo )
2021-02-22 06:41:38 +00:00
return enableOrDisableAddonInternal ( cc , addon , runner , data , enable )
2019-12-18 19:31:29 +00:00
}
2020-04-09 04:23:35 +00:00
func isAddonAlreadySet ( cc * config . ClusterConfig , addon * assets . Addon , enable bool ) bool {
enabled := addon . IsEnabled ( cc )
if enabled && enable {
return true
2019-12-18 19:31:29 +00:00
}
2020-04-09 04:23:35 +00:00
if ! enabled && ! enable {
return true
2019-12-18 19:31:29 +00:00
}
2020-04-09 04:23:35 +00:00
return false
2019-12-18 19:31:29 +00:00
}
2021-02-22 06:41:38 +00:00
func enableOrDisableAddonInternal ( cc * config . ClusterConfig , addon * assets . Addon , runner command . Runner , data interface { } , enable bool ) error {
2020-02-05 22:41:05 +00:00
deployFiles := [ ] string { }
2020-01-06 21:42:09 +00:00
for _ , addon := range addon . Assets {
2020-01-30 23:54:04 +00:00
var f assets . CopyableFile
2020-01-22 22:18:20 +00:00
var err error
2020-01-06 21:42:09 +00:00
if addon . IsTemplate ( ) {
2020-01-30 23:54:04 +00:00
f , err = addon . Evaluate ( data )
2020-01-06 21:42:09 +00:00
if err != nil {
2020-04-09 20:24:14 +00:00
return errors . Wrapf ( err , "evaluate bundled addon %s asset" , addon . GetSourcePath ( ) )
2019-12-18 19:31:29 +00:00
}
2020-01-06 21:42:09 +00:00
} else {
2020-01-30 23:54:04 +00:00
f = addon
2019-12-18 19:31:29 +00:00
}
2020-01-30 23:54:04 +00:00
fPath := path . Join ( f . GetTargetDir ( ) , f . GetTargetName ( ) )
2020-01-22 22:18:20 +00:00
if enable {
2020-09-29 22:49:41 +00:00
klog . Infof ( "installing %s" , fPath )
2021-02-22 06:41:38 +00:00
if err := runner . Copy ( f ) ; err != nil {
2020-01-22 22:18:20 +00:00
return err
}
} else {
2020-09-29 22:49:41 +00:00
klog . Infof ( "Removing %+v" , fPath )
2020-01-22 23:11:33 +00:00
defer func ( ) {
2021-02-22 06:41:38 +00:00
if err := runner . Remove ( f ) ; err != nil {
2020-09-29 22:49:41 +00:00
klog . Warningf ( "error removing %s; addon should still be disabled as expected" , fPath )
2020-01-22 23:11:33 +00:00
}
} ( )
2019-12-18 19:31:29 +00:00
}
2020-02-05 22:41:05 +00:00
if strings . HasSuffix ( fPath , ".yaml" ) {
deployFiles = append ( deployFiles , fPath )
}
2019-12-18 19:31:29 +00:00
}
2020-02-05 22:41:05 +00:00
2020-03-25 20:44:28 +00:00
// Retry, because sometimes we race against an apiserver restart
apply := func ( ) error {
2021-02-22 06:41:38 +00:00
_ , err := runner . RunCmd ( kubectlCommand ( cc , deployFiles , enable ) )
2020-03-25 20:44:28 +00:00
if err != nil {
2020-09-29 22:49:41 +00:00
klog . Warningf ( "apply failed, will retry: %v" , err )
2020-03-25 20:44:28 +00:00
}
return err
2020-01-22 22:18:20 +00:00
}
2020-03-25 20:44:28 +00:00
2020-05-04 19:44:30 +00:00
return retry . Expo ( apply , 250 * time . Millisecond , 2 * time . Minute )
2019-12-18 19:31:29 +00:00
}
2020-06-26 22:08:38 +00:00
func verifyAddonStatus ( cc * config . ClusterConfig , name string , val string ) error {
2020-07-21 21:25:58 +00:00
return verifyAddonStatusInternal ( cc , name , val , "kube-system" )
}
func verifyAddonStatusInternal ( cc * config . ClusterConfig , name string , val string , ns string ) error {
2020-09-29 22:49:41 +00:00
klog . Infof ( "Verifying addon %s=%s in %q" , name , val , cc . Name )
2020-06-25 17:40:32 +00:00
enable , err := strconv . ParseBool ( val )
if err != nil {
return errors . Wrapf ( err , "parsing bool: %s" , name )
}
2020-06-26 21:22:17 +00:00
2020-06-26 22:08:38 +00:00
label , ok := addonPodLabels [ name ]
if ok && enable {
2021-02-25 23:32:03 +00:00
out . Step ( style . HealthCheck , "Verifying {{.addon_name}} addon..." , out . V { "addon_name" : name } )
2020-06-26 22:08:38 +00:00
client , err := kapi . Client ( viper . GetString ( config . ProfileName ) )
2020-06-25 22:43:44 +00:00
if err != nil {
2020-06-26 22:08:38 +00:00
return errors . Wrapf ( err , "get kube-client to validate %s addon: %v" , name , err )
2020-06-25 17:40:32 +00:00
}
2020-06-26 21:22:17 +00:00
2020-10-21 16:44:39 +00:00
// This timeout includes image pull time, which can take a few minutes. 3 is not enough.
2020-10-21 16:54:01 +00:00
err = kapi . WaitForPods ( client , ns , label , time . Minute * 6 )
2020-06-26 21:22:17 +00:00
if err != nil {
2020-10-21 16:44:39 +00:00
return errors . Wrapf ( err , "waiting for %s pods" , label )
2020-06-26 21:22:17 +00:00
}
2020-06-25 17:40:32 +00:00
}
return nil
}
2020-01-31 01:46:25 +00:00
// Start enables the default addons for a profile, plus any additional
2020-04-09 04:23:35 +00:00
func Start ( wg * sync . WaitGroup , cc * config . ClusterConfig , toEnable map [ string ] bool , additional [ ] string ) {
2020-04-08 22:17:14 +00:00
defer wg . Done ( )
2020-04-08 19:29:23 +00:00
2020-01-31 01:46:25 +00:00
start := time . Now ( )
2020-09-29 22:49:41 +00:00
klog . Infof ( "enableAddons start: toEnable=%v, additional=%s" , toEnable , additional )
2020-01-31 01:46:25 +00:00
defer func ( ) {
2020-09-29 22:49:41 +00:00
klog . Infof ( "enableAddons completed in %s" , time . Since ( start ) )
2020-01-31 01:46:25 +00:00
} ( )
2020-02-03 18:49:40 +00:00
// Get the default values of any addons not saved to our config
2020-01-31 01:46:25 +00:00
for name , a := range assets . Addons {
2020-04-09 04:23:35 +00:00
defaultVal := a . IsEnabled ( cc )
2020-02-03 18:49:40 +00:00
_ , exists := toEnable [ name ]
if ! exists {
toEnable [ name ] = defaultVal
2020-01-31 01:46:25 +00:00
}
}
2020-02-03 18:49:40 +00:00
// Apply new addons
for _ , name := range additional {
2020-06-12 16:07:16 +00:00
// replace heapster as metrics-server because heapster is deprecated
if name == "heapster" {
name = "metrics-server"
}
2020-06-11 14:17:17 +00:00
// if the specified addon doesn't exist, skip enabling
_ , e := isAddonValid ( name )
if e {
toEnable [ name ] = true
}
2020-02-03 18:49:40 +00:00
}
toEnableList := [ ] string { }
for k , v := range toEnable {
if v {
toEnableList = append ( toEnableList , k )
}
2020-01-31 01:46:25 +00:00
}
2020-02-03 18:49:40 +00:00
sort . Strings ( toEnableList )
2020-01-31 01:46:25 +00:00
2020-04-09 04:23:35 +00:00
var awg sync . WaitGroup
2020-09-29 20:29:14 +00:00
enabledAddons := [ ] string { }
defer func ( ) { // making it show after verifications (see #7613)
2020-07-07 18:25:27 +00:00
register . Reg . SetStep ( register . EnablingAddons )
2020-11-12 12:45:33 +00:00
out . Step ( style . AddonEnable , "Enabled addons: {{.addons}}" , out . V { "addons" : strings . Join ( enabledAddons , ", " ) } )
2020-04-16 20:38:17 +00:00
} ( )
2020-02-03 18:49:40 +00:00
for _ , a := range toEnableList {
2020-04-09 04:23:35 +00:00
awg . Add ( 1 )
2020-04-08 22:20:30 +00:00
go func ( name string ) {
2020-04-09 04:23:35 +00:00
err := RunCallbacks ( cc , name , "true" )
2020-04-08 19:29:23 +00:00
if err != nil {
2020-04-08 22:17:14 +00:00
out . WarningT ( "Enabling '{{.name}}' returned an error: {{.error}}" , out . V { "name" : name , "error" : err } )
2020-09-29 20:29:14 +00:00
} else {
enabledAddons = append ( enabledAddons , name )
2020-04-08 19:29:23 +00:00
}
2020-04-09 04:23:35 +00:00
awg . Done ( )
2020-04-08 22:20:30 +00:00
} ( a )
2020-01-31 01:46:25 +00:00
}
2020-04-09 04:23:35 +00:00
// Wait until all of the addons are enabled before updating the config (not thread safe)
awg . Wait ( )
2020-09-29 20:29:14 +00:00
for _ , a := range enabledAddons {
2020-04-09 04:23:35 +00:00
if err := Set ( cc , a , "true" ) ; err != nil {
2020-09-29 22:49:41 +00:00
klog . Errorf ( "store failed: %v" , err )
2020-04-09 04:23:35 +00:00
}
}
2020-01-31 01:46:25 +00:00
}