github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/libvirttools/gc.go (about)

     1  /*
     2  Copyright 2017 Mirantis
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package libvirttools
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  	"strings"
    24  
    25  	"github.com/Mirantis/virtlet/pkg/blockdev"
    26  	"github.com/Mirantis/virtlet/pkg/metadata"
    27  	"github.com/Mirantis/virtlet/pkg/metadata/types"
    28  )
    29  
    30  const (
    31  	configFilenameTemplate = "config-*.iso"
    32  )
    33  
    34  // GarbageCollect retrieves from metadata store list of container ids,
    35  // passes it to all GC submodules, collecting from them list of
    36  // possible errors, which is returned to outer scope
    37  func (v *VirtualizationTool) GarbageCollect() (allErrors []error) {
    38  	ids, fatal, errors := v.retrieveListOfContainerIDs()
    39  	if errors != nil {
    40  		allErrors = append(allErrors, errors...)
    41  	}
    42  	if fatal {
    43  		return
    44  	}
    45  
    46  	allErrors = append(allErrors, v.removeOrphanDomains(ids)...)
    47  	allErrors = append(allErrors, v.removeOrphanRootVolumes(ids)...)
    48  	allErrors = append(allErrors, v.removeOrphanQcow2Volumes(ids)...)
    49  	allErrors = append(allErrors, v.removeOrphanConfigImages(ids, configIsoDir)...)
    50  	allErrors = append(allErrors, v.removeOrphanVirtualBlockDevices(ids, "", "")...)
    51  
    52  	return
    53  }
    54  
    55  func (v *VirtualizationTool) retrieveListOfContainerIDs() ([]string, bool, []error) {
    56  	var containerIDs []string
    57  
    58  	sandboxes, err := v.metadataStore.ListPodSandboxes(nil)
    59  	if err != nil {
    60  		return nil, true, []error{
    61  			fmt.Errorf("cannot list pod sandboxes: %v", err),
    62  		}
    63  	}
    64  
    65  	var allErrors []error
    66  	for _, sandbox := range sandboxes {
    67  		if err := v.checkSandboxNetNs(sandbox); err != nil {
    68  			allErrors = append(allErrors, err)
    69  			continue
    70  		}
    71  
    72  		containers, err := v.metadataStore.ListPodContainers(sandbox.GetID())
    73  		if err != nil {
    74  			allErrors = append(
    75  				allErrors,
    76  				fmt.Errorf(
    77  					"cannot list containers for pod %s: %v",
    78  					sandbox.GetID(),
    79  					err,
    80  				),
    81  			)
    82  			continue
    83  		}
    84  		for _, container := range containers {
    85  			containerIDs = append(containerIDs, container.GetID())
    86  		}
    87  	}
    88  
    89  	return containerIDs, false, allErrors
    90  }
    91  
    92  func (v *VirtualizationTool) checkSandboxNetNs(sandbox metadata.PodSandboxMetadata) error {
    93  	sinfo, err := sandbox.Retrieve()
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	if !v.fsys.IsPathAnNs(sinfo.ContainerSideNetwork.NsPath) {
    99  		// NS didn't found, need RunSandbox again
   100  		if err := sandbox.Save(func(s *types.PodSandboxInfo) (*types.PodSandboxInfo, error) {
   101  			if s != nil {
   102  				s.State = types.PodSandboxState_SANDBOX_NOTREADY
   103  			}
   104  			return s, nil
   105  		}); err != nil {
   106  			return err
   107  		}
   108  	}
   109  
   110  	return nil
   111  }
   112  
   113  func inList(list []string, filter func(string) bool) bool {
   114  	for _, element := range list {
   115  		if filter(element) {
   116  			return true
   117  		}
   118  	}
   119  	return false
   120  }
   121  
   122  func (v *VirtualizationTool) removeOrphanDomains(ids []string) []error {
   123  	domains, err := v.domainConn.ListDomains()
   124  	if err != nil {
   125  		return []error{fmt.Errorf("cannot list domains: %v", err)}
   126  	}
   127  
   128  	var allErrors []error
   129  	for _, domain := range domains {
   130  		name, err := domain.Name()
   131  		if err != nil {
   132  			allErrors = append(
   133  				allErrors,
   134  				fmt.Errorf("cannot retrieve domain name: %v", err),
   135  			)
   136  		}
   137  
   138  		filter := func(id string) bool {
   139  			return strings.HasPrefix(name, "virtlet-"+id[:13])
   140  		}
   141  
   142  		if !inList(ids, filter) {
   143  			d, err := v.DomainConnection().LookupDomainByName(name)
   144  			if err != nil {
   145  				allErrors = append(
   146  					allErrors,
   147  					fmt.Errorf(
   148  						"cannot lookup domain '%s' by name: %v",
   149  						name,
   150  						err,
   151  					),
   152  				)
   153  				continue
   154  			}
   155  
   156  			// ignore errors from stopping domain - it can be (and probably is) already stopped
   157  			d.Destroy()
   158  			if err := d.Undefine(); err != nil {
   159  				allErrors = append(
   160  					allErrors,
   161  					fmt.Errorf(
   162  						"cannot undefine domain '%s': %v",
   163  						name,
   164  						err,
   165  					),
   166  				)
   167  			}
   168  		}
   169  	}
   170  
   171  	return allErrors
   172  }
   173  
   174  func (v *VirtualizationTool) removeOrphanRootVolumes(ids []string) []error {
   175  	volumePool, err := v.StoragePool()
   176  	if err != nil {
   177  		return []error{fmt.Errorf("cannot get the storage pool: %v", err)}
   178  	}
   179  	volumes, err := volumePool.ListVolumes()
   180  	if err != nil {
   181  		return []error{fmt.Errorf("cannot list libvirt volumes: %v", err)}
   182  	}
   183  
   184  	var allErrors []error
   185  	for _, volume := range volumes {
   186  		path, err := volume.Path()
   187  		if err != nil {
   188  			allErrors = append(
   189  				allErrors,
   190  				fmt.Errorf("cannot retrieve volume path: %v", err),
   191  			)
   192  			continue
   193  		}
   194  
   195  		filename := filepath.Base(path)
   196  		filter := func(id string) bool {
   197  			return "virtlet_root_"+id == filename
   198  		}
   199  
   200  		if strings.HasPrefix(filename, "virtlet_root_") && !inList(ids, filter) {
   201  			if err := volume.Remove(); err != nil {
   202  				allErrors = append(
   203  					allErrors,
   204  					fmt.Errorf(
   205  						"cannot remove volume with path '%s': %v",
   206  						path,
   207  						err,
   208  					),
   209  				)
   210  			}
   211  		}
   212  	}
   213  
   214  	return allErrors
   215  }
   216  
   217  func (v *VirtualizationTool) removeOrphanQcow2Volumes(ids []string) []error {
   218  	volumePool, err := v.StoragePool()
   219  	if err != nil {
   220  		return []error{fmt.Errorf("cannot get the storage pool: %v", err)}
   221  	}
   222  	volumes, err := volumePool.ListVolumes()
   223  	if err != nil {
   224  		return []error{fmt.Errorf("cannot list domains: %v", err)}
   225  	}
   226  
   227  	var allErrors []error
   228  	for _, volume := range volumes {
   229  		path, err := volume.Path()
   230  		if err != nil {
   231  			allErrors = append(
   232  				allErrors,
   233  				fmt.Errorf("cannot retrieve volume path: %v", err),
   234  			)
   235  			continue
   236  		}
   237  
   238  		filename := filepath.Base(path)
   239  		filter := func(id string) bool {
   240  			return strings.HasPrefix(filename, "virtlet-"+id)
   241  		}
   242  
   243  		if strings.HasPrefix(filename, "virtlet-") && !inList(ids, filter) {
   244  			if err := volume.Remove(); err != nil {
   245  				allErrors = append(
   246  					allErrors,
   247  					fmt.Errorf(
   248  						"cannot remove volume with path '%s': %v",
   249  						path,
   250  						err,
   251  					),
   252  				)
   253  			}
   254  		}
   255  	}
   256  
   257  	return allErrors
   258  }
   259  
   260  func (v *VirtualizationTool) removeOrphanConfigImages(ids []string, directory string) []error {
   261  	files, err := filepath.Glob(filepath.Join(directory, configFilenameTemplate))
   262  	if err != nil {
   263  		return []error{
   264  			fmt.Errorf(
   265  				"error while globbing '%s' files in '%s' directory: %v",
   266  				configFilenameTemplate,
   267  				configIsoDir,
   268  				err,
   269  			),
   270  		}
   271  	}
   272  
   273  	var allErrors []error
   274  	for _, path := range files {
   275  		filename := filepath.Base(path)
   276  
   277  		filter := func(id string) bool {
   278  			return filename == "config-"+id+".iso"
   279  		}
   280  
   281  		if strings.HasPrefix(filename, "config-") && strings.HasSuffix(filename, ".iso") && !inList(ids, filter) {
   282  			if err := os.Remove(path); err != nil {
   283  				allErrors = append(
   284  					allErrors,
   285  					fmt.Errorf(
   286  						"cannot remove volume with path '%s': %v",
   287  						path,
   288  						err,
   289  					),
   290  				)
   291  			}
   292  		}
   293  	}
   294  
   295  	return allErrors
   296  }
   297  
   298  func (v *VirtualizationTool) removeOrphanVirtualBlockDevices(ids []string, devPath, sysfsPath string) []error {
   299  	idsInUse := make(map[string]bool)
   300  	for _, id := range ids {
   301  		idsInUse[id] = true
   302  	}
   303  	ldh := blockdev.NewLogicalDeviceHandler(v.Commander(), devPath, sysfsPath)
   304  	dmNames, err := ldh.ListVirtletLogicalDevices()
   305  	if err != nil {
   306  		return []error{err}
   307  	}
   308  
   309  	var allErrors []error
   310  	for _, dmName := range dmNames {
   311  		if !strings.HasPrefix(dmName, blockdev.VirtletLogicalDevicePrefix) {
   312  			panic("bad dmname " + dmName)
   313  		}
   314  		id := dmName[len(blockdev.VirtletLogicalDevicePrefix):]
   315  		if idsInUse[id] {
   316  			continue
   317  		}
   318  		if err := ldh.Unmap(dmName); err != nil {
   319  			allErrors = append(
   320  				allErrors,
   321  				fmt.Errorf("error unmapping %q: %v", dmName, err))
   322  		}
   323  	}
   324  
   325  	return allErrors
   326  }