github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/imageserver/scanner/load.go (about)

     1  package scanner
     2  
     3  import (
     4  	"encoding/gob"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path"
    10  	"strings"
    11  	"syscall"
    12  	"time"
    13  
    14  	"github.com/Cloud-Foundations/Dominator/lib/concurrent"
    15  	"github.com/Cloud-Foundations/Dominator/lib/fsutil"
    16  	"github.com/Cloud-Foundations/Dominator/lib/image"
    17  	"github.com/Cloud-Foundations/Dominator/lib/log"
    18  	"github.com/Cloud-Foundations/Dominator/lib/log/logutil"
    19  	"github.com/Cloud-Foundations/Dominator/lib/log/prefixlogger"
    20  	"github.com/Cloud-Foundations/Dominator/lib/objectserver"
    21  	objectclient "github.com/Cloud-Foundations/Dominator/lib/objectserver/client"
    22  	"github.com/Cloud-Foundations/Dominator/lib/srpc"
    23  	"github.com/Cloud-Foundations/Dominator/lib/stringutil"
    24  )
    25  
    26  func loadImageDataBase(baseDir string, objSrv objectserver.FullObjectServer,
    27  	replicationMaster string, logger log.DebugLogger) (*ImageDataBase, error) {
    28  	fi, err := os.Stat(baseDir)
    29  	if err != nil {
    30  		return nil, errors.New(
    31  			fmt.Sprintf("Cannot stat: %s: %s\n", baseDir, err))
    32  	}
    33  	if !fi.IsDir() {
    34  		return nil, errors.New(fmt.Sprintf("%s is not a directory\n", baseDir))
    35  	}
    36  	imdb := &ImageDataBase{
    37  		baseDir:           baseDir,
    38  		directoryMap:      make(map[string]image.DirectoryMetadata),
    39  		imageMap:          make(map[string]*image.Image),
    40  		addNotifiers:      make(notifiers),
    41  		deleteNotifiers:   make(notifiers),
    42  		mkdirNotifiers:    make(makeDirectoryNotifiers),
    43  		deduper:           stringutil.NewStringDeduplicator(false),
    44  		objectServer:      objSrv,
    45  		replicationMaster: replicationMaster,
    46  		logger:            logger,
    47  	}
    48  	imdb.unreferencedObjects, err = loadUnreferencedObjects(
    49  		path.Join(baseDir, unreferencedObjectsFile))
    50  	if err != nil {
    51  		return nil, errors.New("error loading unreferenced objects list: " +
    52  			err.Error())
    53  	}
    54  	state := concurrent.NewState(0)
    55  	startTime := time.Now()
    56  	var rusageStart, rusageStop syscall.Rusage
    57  	syscall.Getrusage(syscall.RUSAGE_SELF, &rusageStart)
    58  	if err := imdb.scanDirectory(".", state, logger); err != nil {
    59  		return nil, err
    60  	}
    61  	if err := state.Reap(); err != nil {
    62  		return nil, err
    63  	}
    64  	if logger != nil {
    65  		plural := ""
    66  		if imdb.CountImages() != 1 {
    67  			plural = "s"
    68  		}
    69  		syscall.Getrusage(syscall.RUSAGE_SELF, &rusageStop)
    70  		userTime := time.Duration(rusageStop.Utime.Sec)*time.Second +
    71  			time.Duration(rusageStop.Utime.Usec)*time.Microsecond -
    72  			time.Duration(rusageStart.Utime.Sec)*time.Second -
    73  			time.Duration(rusageStart.Utime.Usec)*time.Microsecond
    74  		logger.Printf("Loaded %d image%s in %s (%s user CPUtime)\n",
    75  			imdb.CountImages(), plural, time.Since(startTime), userTime)
    76  		logutil.LogMemory(logger, 0, "after loading")
    77  	}
    78  	imdb.regenerateUnreferencedObjectsList()
    79  	if ads, ok := objSrv.(objectserver.AddCallbackSetter); ok {
    80  		ads.SetAddCallback(imdb.garbageCollectorAddCallback)
    81  	}
    82  	if gcs, ok := objSrv.(objectserver.GarbageCollectorSetter); ok {
    83  		gcs.SetGarbageCollector(imdb.garbageCollector)
    84  	}
    85  	go imdb.periodicGarbageCollector()
    86  	return imdb, nil
    87  }
    88  
    89  func (imdb *ImageDataBase) scanDirectory(dirname string,
    90  	state *concurrent.State, logger log.DebugLogger) error {
    91  	directoryMetadata, err := imdb.readDirectoryMetadata(dirname)
    92  	if err != nil {
    93  		return err
    94  	}
    95  	imdb.directoryMap[dirname] = directoryMetadata
    96  	file, err := os.Open(path.Join(imdb.baseDir, dirname))
    97  	if err != nil {
    98  		return err
    99  	}
   100  	names, err := file.Readdirnames(-1)
   101  	file.Close()
   102  	if err != nil {
   103  		return err
   104  	}
   105  	for _, name := range names {
   106  		if len(name) > 0 && name[0] == '.' {
   107  			continue // Skip hidden paths.
   108  		}
   109  		filename := path.Join(dirname, name)
   110  		var stat syscall.Stat_t
   111  		err := syscall.Lstat(path.Join(imdb.baseDir, filename), &stat)
   112  		if err != nil {
   113  			if err == syscall.ENOENT {
   114  				continue
   115  			}
   116  			return err
   117  		}
   118  		if stat.Mode&syscall.S_IFMT == syscall.S_IFDIR {
   119  			err = imdb.scanDirectory(filename, state, logger)
   120  		} else if stat.Mode&syscall.S_IFMT == syscall.S_IFREG && stat.Size > 0 {
   121  			err = state.GoRun(func() error {
   122  				return imdb.loadFile(filename, logger)
   123  			})
   124  		}
   125  		if err != nil {
   126  			if err == syscall.ENOENT {
   127  				continue
   128  			}
   129  			return err
   130  		}
   131  	}
   132  	return nil
   133  }
   134  
   135  func (imdb *ImageDataBase) readDirectoryMetadata(dirname string) (
   136  	image.DirectoryMetadata, error) {
   137  	file, err := os.Open(path.Join(imdb.baseDir, dirname, metadataFile))
   138  	if err != nil {
   139  		if os.IsNotExist(err) {
   140  			return image.DirectoryMetadata{}, nil
   141  		}
   142  		return image.DirectoryMetadata{}, err
   143  	}
   144  	defer file.Close()
   145  	reader := fsutil.NewChecksumReader(file)
   146  	decoder := gob.NewDecoder(reader)
   147  	metadata := image.DirectoryMetadata{}
   148  	if err := decoder.Decode(&metadata); err != nil {
   149  		return image.DirectoryMetadata{}, fmt.Errorf(
   150  			"unable to read directory metadata for \"%s\": %s", dirname, err)
   151  	}
   152  	return metadata, reader.VerifyChecksum()
   153  }
   154  
   155  func (imdb *ImageDataBase) loadFile(filename string,
   156  	logger log.DebugLogger) error {
   157  	pathname := path.Join(imdb.baseDir, filename)
   158  	file, err := os.Open(pathname)
   159  	if err != nil {
   160  		return err
   161  	}
   162  	defer file.Close()
   163  	reader := fsutil.NewChecksumReader(file)
   164  	decoder := gob.NewDecoder(reader)
   165  	var img image.Image
   166  	if err := decoder.Decode(&img); err != nil {
   167  		return err
   168  	}
   169  	if err := reader.VerifyChecksum(); err != nil {
   170  		if err == fsutil.ErrorChecksumMismatch {
   171  			logger.Printf("Checksum mismatch for image: %s\n", filename)
   172  			return nil
   173  		}
   174  		if err != io.EOF {
   175  			return err
   176  		}
   177  	}
   178  	if imageIsExpired(&img) {
   179  		imdb.logger.Printf("Deleting already expired image: %s\n", filename)
   180  		return os.Remove(pathname)
   181  	}
   182  	if err := img.VerifyObjects(imdb.objectServer); err != nil {
   183  		if imdb.replicationMaster == "" ||
   184  			!strings.Contains(err.Error(), "not available") {
   185  			return fmt.Errorf("error verifying: %s: %s", filename, err)
   186  		}
   187  		err = imdb.fetchMissingObjects(&img,
   188  			prefixlogger.New(filename+": ", logger))
   189  		if err != nil {
   190  			return err
   191  		}
   192  	}
   193  	img.FileSystem.RebuildInodePointers()
   194  	imdb.deduperLock.Lock()
   195  	img.ReplaceStrings(imdb.deduper.DeDuplicate)
   196  	imdb.deduperLock.Unlock()
   197  	if err := img.Verify(); err != nil {
   198  		return err
   199  	}
   200  	imdb.scheduleExpiration(&img, filename)
   201  	imdb.Lock()
   202  	defer imdb.Unlock()
   203  	imdb.imageMap[filename] = &img
   204  	return nil
   205  }
   206  
   207  func (imdb *ImageDataBase) fetchMissingObjects(img *image.Image,
   208  	logger log.DebugLogger) error {
   209  	imdb.objectFetchLock.Lock()
   210  	defer imdb.objectFetchLock.Unlock()
   211  	client, err := srpc.DialHTTP("tcp", imdb.replicationMaster, time.Minute)
   212  	if err != nil {
   213  		return err
   214  	}
   215  	defer client.Close()
   216  	objClient := objectclient.AttachObjectClient(client)
   217  	defer objClient.Close()
   218  	return img.GetMissingObjects(imdb.objectServer, objClient, logger)
   219  }