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 }