github.com/Cloud-Foundations/Dominator@v0.3.4/imageserver/scanner/load.go (about)

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