github.com/fafucoder/cilium@v1.6.11/cilium/cmd/cleanup.go (about)

     1  // Copyright 2017 Authors of Cilium
     2  //
     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  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cmd
    16  
    17  import (
    18  	"fmt"
    19  	"io/ioutil"
    20  	"os"
    21  	"path/filepath"
    22  	"strings"
    23  	"syscall"
    24  
    25  	"github.com/cilium/cilium/common"
    26  	"github.com/cilium/cilium/pkg/bpf"
    27  	"github.com/cilium/cilium/pkg/defaults"
    28  	"github.com/cilium/cilium/pkg/maps/tunnel"
    29  	"github.com/cilium/cilium/pkg/netns"
    30  	"github.com/cilium/cilium/pkg/option"
    31  
    32  	"github.com/spf13/cobra"
    33  	"github.com/spf13/viper"
    34  	"github.com/vishvananda/netlink"
    35  )
    36  
    37  // configCmd represents the config command
    38  var cleanupCmd = &cobra.Command{
    39  	Use:   "cleanup",
    40  	Short: "Reset the agent state",
    41  	Run: func(cmd *cobra.Command, args []string) {
    42  		common.RequireRootPrivilege("cleanup")
    43  		runCleanup()
    44  	},
    45  }
    46  
    47  var (
    48  	cleanAll bool
    49  	cleanBPF bool
    50  	force    bool
    51  )
    52  
    53  const (
    54  	allFlagName   = "all-state"
    55  	bpfFlagName   = "bpf-state"
    56  	forceFlagName = "force"
    57  
    58  	cleanCiliumEnvVar = "CLEAN_CILIUM_STATE"
    59  	cleanBpfEnvVar    = "CLEAN_CILIUM_BPF_STATE"
    60  )
    61  
    62  const (
    63  	ciliumLinkPrefix  = "cilium_"
    64  	ciliumNetNSPrefix = "cilium-"
    65  	hostLinkPrefix    = "lxc"
    66  	hostLinkLen       = len(hostLinkPrefix + "XXXXX")
    67  	cniConfigV1       = "/etc/cni/net.d/10-cilium-cni.conf"
    68  	cniConfigV2       = "/etc/cni/net.d/00-cilium-cni.conf"
    69  	cniConfigV3       = "/etc/cni/net.d/05-cilium-cni.conf"
    70  )
    71  
    72  func init() {
    73  	rootCmd.AddCommand(cleanupCmd)
    74  
    75  	cleanupCmd.Flags().BoolVarP(&cleanAll, allFlagName, "", false, "Remove all cilium state")
    76  	cleanupCmd.Flags().BoolVarP(&cleanBPF, bpfFlagName, "", false, "Remove BPF state")
    77  	cleanupCmd.Flags().BoolVarP(&force, forceFlagName, "f", false, "Skip confirmation")
    78  
    79  	option.BindEnv(allFlagName)
    80  	option.BindEnv(bpfFlagName)
    81  
    82  	bindEnv(cleanCiliumEnvVar, cleanCiliumEnvVar)
    83  	bindEnv(cleanBpfEnvVar, cleanBpfEnvVar)
    84  
    85  	if err := viper.BindPFlags(cleanupCmd.Flags()); err != nil {
    86  		Fatalf("viper failed to bind to flags: %v\n", err)
    87  	}
    88  }
    89  
    90  func bindEnv(flagName string, envName string) {
    91  	if err := viper.BindEnv(flagName, envName); err != nil {
    92  		Fatalf("Unable to bind flag %s to env variable %s: %s", flagName, envName, err)
    93  	}
    94  }
    95  
    96  // cleanupFunc represents a function to cleanup specific state items.
    97  type cleanupFunc func() error
    98  
    99  // cleanup represents a set of cleanup actions.
   100  type cleanup interface {
   101  	// whatWillBeRemoved contains descriptions of
   102  	// the removed state.
   103  	whatWillBeRemoved() []string
   104  	// cleanupFuncs contains a set of functions that
   105  	// carry out cleanup actions.
   106  	cleanupFuncs() []cleanupFunc
   107  }
   108  
   109  // bpfCleanup represents cleanup actions for BPF state.
   110  type bpfCleanup struct{}
   111  
   112  func (c bpfCleanup) whatWillBeRemoved() []string {
   113  	return []string{
   114  		fmt.Sprintf("all BPF maps in %s containing '%s' and '%s'",
   115  			bpf.MapPrefixPath(), ciliumLinkPrefix, tunnel.MapName),
   116  		fmt.Sprintf("mounted bpffs at %s", bpf.GetMapRoot()),
   117  	}
   118  }
   119  
   120  func (c bpfCleanup) cleanupFuncs() []cleanupFunc {
   121  	return []cleanupFunc{
   122  		removeAllMaps,
   123  	}
   124  }
   125  
   126  // ciliumCleanup represents cleanup actions for non-BPF state.
   127  type ciliumCleanup struct {
   128  	routes map[int]netlink.Route
   129  	links  map[int]netlink.Link
   130  	netNSs []string
   131  }
   132  
   133  func newCiliumCleanup() ciliumCleanup {
   134  	routes, links, err := findRoutesAndLinks()
   135  	if err != nil {
   136  		fmt.Fprintf(os.Stderr, "Error: %s\n", err)
   137  	}
   138  
   139  	netNSs, err := netns.ListNamedNetNSWithPrefix(ciliumNetNSPrefix)
   140  	if err != nil {
   141  		fmt.Fprintf(os.Stderr, "Error: %s\n", err)
   142  	}
   143  
   144  	return ciliumCleanup{routes, links, netNSs}
   145  }
   146  
   147  func (c ciliumCleanup) whatWillBeRemoved() []string {
   148  	toBeRemoved := []string{
   149  		fmt.Sprintf("mounted cgroupv2 at %s", defaults.DefaultCgroupRoot),
   150  		fmt.Sprintf("library code in %s", defaults.LibraryPath),
   151  		fmt.Sprintf("endpoint state in %s", defaults.RuntimePath),
   152  		fmt.Sprintf("CNI configuration at %s, %s, %s",
   153  			cniConfigV1, cniConfigV2, cniConfigV3),
   154  	}
   155  
   156  	if len(c.routes) > 0 {
   157  		section := "routes\n"
   158  		for _, v := range c.routes {
   159  			section += fmt.Sprintf("%v\n", v)
   160  		}
   161  		toBeRemoved = append(toBeRemoved, section)
   162  	}
   163  
   164  	if len(c.links) > 0 {
   165  		section := "links\n"
   166  		for _, v := range c.links {
   167  			section += fmt.Sprintf("%v\n", v)
   168  		}
   169  		toBeRemoved = append(toBeRemoved, section)
   170  	}
   171  
   172  	if len(c.netNSs) > 0 {
   173  		section := "network namespaces\n"
   174  		for _, n := range c.netNSs {
   175  			section += fmt.Sprintf("%s\n", n)
   176  		}
   177  		toBeRemoved = append(toBeRemoved, section)
   178  	}
   179  
   180  	return toBeRemoved
   181  }
   182  
   183  func (c ciliumCleanup) cleanupFuncs() []cleanupFunc {
   184  	cleanupRoutesAndLinks := func() error {
   185  		return removeRoutesAndLinks(c.routes, c.links)
   186  	}
   187  
   188  	cleanupNamedNetNSs := func() error {
   189  		return removeNamedNetNSs(c.netNSs)
   190  	}
   191  
   192  	return []cleanupFunc{
   193  		unmountCgroup,
   194  		removeDirs,
   195  		removeCNI,
   196  		cleanupRoutesAndLinks,
   197  		cleanupNamedNetNSs,
   198  	}
   199  }
   200  
   201  func runCleanup() {
   202  	// Abort if the agent is running, err == nil is handled correctly by Stat.
   203  	if _, err := os.Stat(defaults.PidFilePath); !os.IsNotExist(err) {
   204  		fmt.Fprintf(os.Stderr, "Agent should not be running when cleaning up\n"+
   205  			"Found pidfile %s\n", defaults.PidFilePath)
   206  		os.Exit(1)
   207  	}
   208  
   209  	cleanAll = viper.GetBool(allFlagName) || viper.GetBool(cleanCiliumEnvVar)
   210  	cleanBPF = viper.GetBool(bpfFlagName) || viper.GetBool(cleanBpfEnvVar)
   211  
   212  	// if no flags are specifed then clean all
   213  	if (cleanAll || cleanBPF) == false {
   214  		cleanAll = true
   215  	}
   216  
   217  	var cleanups []cleanup
   218  
   219  	if cleanAll || cleanBPF {
   220  		cleanups = append(cleanups, bpfCleanup{})
   221  	}
   222  
   223  	if cleanAll {
   224  		cleanups = append(cleanups, newCiliumCleanup())
   225  	}
   226  
   227  	if len(cleanups) == 0 {
   228  		return
   229  	}
   230  
   231  	showWhatWillBeRemoved(cleanups)
   232  	if !force && !confirmCleanup() {
   233  		return
   234  	}
   235  
   236  	var cleanupFuncs []cleanupFunc
   237  	for _, cleanup := range cleanups {
   238  		cleanupFuncs = append(cleanupFuncs, cleanup.cleanupFuncs()...)
   239  	}
   240  
   241  	// ENOENT and similar errors are ignored. Should print all other
   242  	// errors seen, but continue.  So that one remove function does not
   243  	// prevent the remaining from running.
   244  	for _, clean := range cleanupFuncs {
   245  		if err := clean(); err != nil {
   246  			fmt.Fprintf(os.Stderr, "Error: %s\n", err)
   247  		}
   248  	}
   249  }
   250  
   251  func showWhatWillBeRemoved(cleanups []cleanup) {
   252  	var toBeRemoved []string
   253  
   254  	for _, cleanup := range cleanups {
   255  		toBeRemoved = append(toBeRemoved, cleanup.whatWillBeRemoved()...)
   256  	}
   257  
   258  	warning := "Warning: Destructive operation. You are about to remove:\n"
   259  	for _, warn := range toBeRemoved {
   260  		warning += fmt.Sprintf("- %s\n", warn)
   261  	}
   262  
   263  	fmt.Printf(warning)
   264  }
   265  
   266  func confirmCleanup() bool {
   267  	fmt.Printf("The command is non-revertible, do you want to continue [y/N]?\n")
   268  	var res string
   269  	fmt.Scanln(&res)
   270  	return res == "y"
   271  }
   272  
   273  func removeCNI() error {
   274  	os.Remove(cniConfigV1)
   275  	os.Remove(cniConfigV2)
   276  
   277  	err := os.Remove(cniConfigV3)
   278  	if os.IsNotExist(err) {
   279  		return nil
   280  	}
   281  	return err
   282  }
   283  
   284  func unmountCgroup() error {
   285  	cgroupRoot := defaults.DefaultCgroupRoot
   286  	cgroupRootStat, err := os.Stat(cgroupRoot)
   287  	if err != nil {
   288  		return nil
   289  	} else if !cgroupRootStat.IsDir() {
   290  		return fmt.Errorf("%s is a file which is not a directory", cgroupRoot)
   291  	}
   292  
   293  	log.Info("Trying to unmount ", cgroupRoot)
   294  	if err := syscall.Unmount(cgroupRoot, syscall.MNT_FORCE); err != nil {
   295  		return fmt.Errorf("Failed to unmount %s: %s", cgroupRoot, err)
   296  	}
   297  	return nil
   298  }
   299  
   300  func removeDirs() error {
   301  	dirs := []string{defaults.RuntimePath, defaults.LibraryPath}
   302  	for _, dir := range dirs {
   303  		// In the unlikely case one of the constants is the root directory, abort.
   304  		if dir == "/" {
   305  			return fmt.Errorf("will not remove root directory %s", dir)
   306  		}
   307  		if err := os.RemoveAll(dir); err != nil {
   308  			if os.IsNotExist(err) {
   309  				continue
   310  			}
   311  			return err
   312  		}
   313  	}
   314  	return nil
   315  }
   316  
   317  func removeAllMaps() error {
   318  	mapDir := bpf.MapPrefixPath()
   319  	maps, err := ioutil.ReadDir(mapDir)
   320  	if err != nil {
   321  		if os.IsNotExist(err) {
   322  			return nil
   323  		}
   324  		return err
   325  	}
   326  
   327  	for _, m := range maps {
   328  		name := m.Name()
   329  		// Skip non Cilium looking maps
   330  		if !strings.HasPrefix(name, ciliumLinkPrefix) && name != tunnel.MapName {
   331  			continue
   332  		}
   333  		if err = os.Remove(filepath.Join(mapDir, name)); err != nil {
   334  			return err
   335  		}
   336  		fmt.Printf("removed map %s\n", m.Name())
   337  	}
   338  	return err
   339  }
   340  
   341  func linkMatch(linkName string) bool {
   342  	return strings.HasPrefix(linkName, ciliumLinkPrefix) ||
   343  		strings.HasPrefix(linkName, hostLinkPrefix) && len(linkName) == hostLinkLen
   344  }
   345  
   346  func findRoutesAndLinks() (map[int]netlink.Route, map[int]netlink.Link, error) {
   347  	routesToRemove := map[int]netlink.Route{}
   348  	linksToRemove := map[int]netlink.Link{}
   349  
   350  	if routes, err := netlink.RouteList(nil, netlink.FAMILY_V4); err == nil {
   351  		for _, r := range routes {
   352  			link, err := netlink.LinkByIndex(r.LinkIndex)
   353  			if err != nil {
   354  				if strings.Contains(err.Error(), "Link not found") {
   355  					continue
   356  				}
   357  				return routesToRemove, linksToRemove, err
   358  			}
   359  
   360  			linkName := link.Attrs().Name
   361  			if !linkMatch(linkName) {
   362  				continue
   363  			}
   364  			routesToRemove[r.LinkIndex] = r
   365  			linksToRemove[link.Attrs().Index] = link
   366  		}
   367  	}
   368  
   369  	if links, err := netlink.LinkList(); err == nil {
   370  		for _, link := range links {
   371  			linkName := link.Attrs().Name
   372  			if !linkMatch(linkName) {
   373  				continue
   374  			}
   375  			linksToRemove[link.Attrs().Index] = link
   376  		}
   377  	}
   378  	return routesToRemove, linksToRemove, nil
   379  }
   380  
   381  func removeRoutesAndLinks(routes map[int]netlink.Route, links map[int]netlink.Link) error {
   382  	for _, route := range routes {
   383  		if err := netlink.RouteDel(&route); err != nil {
   384  			return err
   385  		}
   386  		fmt.Printf("removed route %v\n", route)
   387  	}
   388  
   389  	for _, link := range links {
   390  		if err := netlink.LinkDel(link); err != nil {
   391  			if strings.Contains(err.Error(), "Link not found") ||
   392  				strings.Contains(err.Error(), "no such device") {
   393  				continue
   394  			}
   395  			return err
   396  		}
   397  		fmt.Printf("removed link %s\n", link.Attrs().Name)
   398  	}
   399  	return nil
   400  }
   401  
   402  func removeNamedNetNSs(netNSs []string) error {
   403  	for _, n := range netNSs {
   404  		if err := netns.RemoveNetNSWithName(n); err != nil {
   405  			if strings.Contains(err.Error(), "No such file") {
   406  				continue
   407  			}
   408  			return err
   409  		}
   410  		fmt.Printf("removed network namespace %s\n", n)
   411  	}
   412  	return nil
   413  }