github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/state/backups/metadata.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package backups 5 6 import ( 7 "bytes" 8 "crypto/sha1" 9 "encoding/base64" 10 "encoding/json" 11 "io" 12 "os" 13 "time" 14 15 "github.com/juju/errors" 16 "github.com/juju/utils/filestorage" 17 18 "github.com/juju/juju/version" 19 ) 20 21 // checksumFormat identifies how to interpret the checksum for a backup 22 // generated with this version of juju. 23 const checksumFormat = "SHA-1, base64 encoded" 24 25 // Origin identifies where a backup archive came from. While it is 26 // more about where and Metadata about what and when, that distinction 27 // does not merit special consideration. Instead, Origin exists 28 // separately from Metadata due to its use as an argument when 29 // requesting the creation of a new backup. 30 type Origin struct { 31 Environment string 32 Machine string 33 Hostname string 34 Version version.Number 35 } 36 37 // UnknownString is a marker value for string fields with unknown values. 38 const UnknownString = "<unknown>" 39 40 // UnknownVersion is a marker value for version fields with unknown values. 41 var UnknownVersion = version.MustParse("9999.9999.9999") 42 43 // UnknownOrigin returns a new backups origin with unknown values. 44 func UnknownOrigin() Origin { 45 return Origin{ 46 Environment: UnknownString, 47 Machine: UnknownString, 48 Hostname: UnknownString, 49 Version: UnknownVersion, 50 } 51 } 52 53 // Metadata contains the metadata for a single state backup archive. 54 type Metadata struct { 55 *filestorage.FileMetadata 56 57 // Started records when the backup was started. 58 Started time.Time 59 // Finished records when the backup was complete. 60 Finished *time.Time 61 // Origin identifies where the backup was created. 62 Origin Origin 63 // Notes is an optional user-supplied annotation. 64 Notes string 65 } 66 67 // NewMetadata returns a new Metadata for a state backup archive. Only 68 // the start time and the version are set. 69 func NewMetadata() *Metadata { 70 return &Metadata{ 71 FileMetadata: filestorage.NewMetadata(), 72 Started: time.Now().UTC(), 73 Origin: Origin{ 74 Version: version.Current.Number, 75 }, 76 } 77 } 78 79 // NewMetadataState composes a new backup metadata with its origin 80 // values set. The environment UUID comes from state. The hostname is 81 // retrieved from the OS. 82 func NewMetadataState(db DB, machine string) (*Metadata, error) { 83 // hostname could be derived from the environment... 84 hostname, err := os.Hostname() 85 if err != nil { 86 // If os.Hostname() is not working, something is woefully wrong. 87 // Run for the hills. 88 return nil, errors.Annotate(err, "could not get hostname (system unstable?)") 89 } 90 91 meta := NewMetadata() 92 meta.Origin.Environment = db.EnvironTag().Id() 93 meta.Origin.Machine = machine 94 meta.Origin.Hostname = hostname 95 return meta, nil 96 } 97 98 // MarkComplete populates the remaining metadata values. The default 99 // checksum format is used. 100 func (m *Metadata) MarkComplete(size int64, checksum string) error { 101 if size == 0 { 102 return errors.New("missing size") 103 } 104 if checksum == "" { 105 return errors.New("missing checksum") 106 } 107 format := checksumFormat 108 finished := time.Now().UTC() 109 110 if err := m.SetFileInfo(size, checksum, format); err != nil { 111 return errors.Annotate(err, "unexpected failure") 112 } 113 m.Finished = &finished 114 115 return nil 116 } 117 118 type flatMetadata struct { 119 ID string 120 121 // file storage 122 123 Checksum string 124 ChecksumFormat string 125 Size int64 126 Stored time.Time 127 128 // backup 129 130 Started time.Time 131 Finished time.Time 132 Notes string 133 Environment string 134 Machine string 135 Hostname string 136 Version version.Number 137 } 138 139 // TODO(ericsnow) Move AsJSONBuffer to filestorage.Metadata. 140 141 // AsJSONBuffer returns a bytes.Buffer containing the JSON-ified metadata. 142 func (m *Metadata) AsJSONBuffer() (io.Reader, error) { 143 flat := flatMetadata{ 144 ID: m.ID(), 145 146 Checksum: m.Checksum(), 147 ChecksumFormat: m.ChecksumFormat(), 148 Size: m.Size(), 149 150 Started: m.Started, 151 Notes: m.Notes, 152 Environment: m.Origin.Environment, 153 Machine: m.Origin.Machine, 154 Hostname: m.Origin.Hostname, 155 Version: m.Origin.Version, 156 } 157 158 stored := m.Stored() 159 if stored != nil { 160 flat.Stored = *stored 161 } 162 163 if m.Finished != nil { 164 flat.Finished = *m.Finished 165 } 166 167 var outfile bytes.Buffer 168 if err := json.NewEncoder(&outfile).Encode(flat); err != nil { 169 return nil, errors.Trace(err) 170 } 171 return &outfile, nil 172 } 173 174 // NewMetadataJSONReader extracts a new metadata from the JSON file. 175 func NewMetadataJSONReader(in io.Reader) (*Metadata, error) { 176 var flat flatMetadata 177 if err := json.NewDecoder(in).Decode(&flat); err != nil { 178 return nil, errors.Trace(err) 179 } 180 181 meta := NewMetadata() 182 meta.SetID(flat.ID) 183 184 err := meta.SetFileInfo(flat.Size, flat.Checksum, flat.ChecksumFormat) 185 if err != nil { 186 return nil, errors.Trace(err) 187 } 188 189 if !flat.Stored.IsZero() { 190 meta.SetStored(&flat.Stored) 191 } 192 193 meta.Started = flat.Started 194 if !flat.Finished.IsZero() { 195 meta.Finished = &flat.Finished 196 } 197 meta.Notes = flat.Notes 198 meta.Origin = Origin{ 199 Environment: flat.Environment, 200 Machine: flat.Machine, 201 Hostname: flat.Hostname, 202 Version: flat.Version, 203 } 204 205 return meta, nil 206 } 207 208 func fileTimestamp(fi os.FileInfo) time.Time { 209 timestamp := creationTime(fi) 210 if !timestamp.IsZero() { 211 return timestamp 212 } 213 // Fall back to modification time. 214 return fi.ModTime() 215 } 216 217 // BuildMetadata generates the metadata for a backup archive file. 218 func BuildMetadata(file *os.File) (*Metadata, error) { 219 220 // Extract the file size. 221 fi, err := file.Stat() 222 if err != nil { 223 return nil, errors.Trace(err) 224 } 225 size := fi.Size() 226 227 // Extract the timestamp. 228 timestamp := fileTimestamp(fi) 229 230 // Get the checksum. 231 hasher := sha1.New() 232 _, err = io.Copy(hasher, file) 233 if err != nil { 234 return nil, errors.Trace(err) 235 } 236 rawsum := hasher.Sum(nil) 237 checksum := base64.StdEncoding.EncodeToString(rawsum) 238 239 // Build the metadata. 240 meta := NewMetadata() 241 meta.Started = time.Time{} 242 meta.Origin = UnknownOrigin() 243 err = meta.MarkComplete(size, checksum) 244 if err != nil { 245 return nil, errors.Trace(err) 246 } 247 meta.Finished = ×tamp 248 return meta, nil 249 }