github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 jujuversion "github.com/juju/juju/version" 17 "github.com/juju/utils/filestorage" 18 "github.com/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 Model 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 Model: 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 60 // Finished records when the backup was complete. 61 Finished *time.Time 62 63 // Origin identifies where the backup was created. 64 Origin Origin 65 66 // Notes is an optional user-supplied annotation. 67 Notes string 68 69 // TODO(wallyworld) - remove these ASAP 70 // These are only used by the restore CLI when re-bootstrapping. 71 // We will use a better solution but the way restore currently 72 // works, we need them and they are no longer available via 73 // bootstrap config. We will need to ifx how re-bootstrap deals 74 // with these keys to address the issue. 75 76 // CACert is the controller CA certificate. 77 CACert string 78 79 // CAPrivateKey is the controller CA private key. 80 CAPrivateKey string 81 } 82 83 // NewMetadata returns a new Metadata for a state backup archive. Only 84 // the start time and the version are set. 85 func NewMetadata() *Metadata { 86 return &Metadata{ 87 FileMetadata: filestorage.NewMetadata(), 88 // TODO(fwereade): 2016-03-17 lp:1558657 89 Started: time.Now().UTC(), 90 Origin: Origin{ 91 Version: jujuversion.Current, 92 }, 93 } 94 } 95 96 // NewMetadataState composes a new backup metadata with its origin 97 // values set. The model UUID comes from state. The hostname is 98 // retrieved from the OS. 99 func NewMetadataState(db DB, machine string) (*Metadata, error) { 100 // hostname could be derived from the model... 101 hostname, err := os.Hostname() 102 if err != nil { 103 // If os.Hostname() is not working, something is woefully wrong. 104 // Run for the hills. 105 return nil, errors.Annotate(err, "could not get hostname (system unstable?)") 106 } 107 108 meta := NewMetadata() 109 meta.Origin.Model = db.ModelTag().Id() 110 meta.Origin.Machine = machine 111 meta.Origin.Hostname = hostname 112 113 si, err := db.StateServingInfo() 114 if err != nil { 115 return nil, errors.Annotate(err, "could not get server secrets") 116 } 117 cfg, err := db.ModelConfig() 118 if err != nil { 119 return nil, errors.Annotate(err, "could not get model config") 120 } 121 meta.CACert, _ = cfg.CACert() 122 meta.CAPrivateKey = si.CAPrivateKey 123 return meta, nil 124 } 125 126 // MarkComplete populates the remaining metadata values. The default 127 // checksum format is used. 128 func (m *Metadata) MarkComplete(size int64, checksum string) error { 129 if size == 0 { 130 return errors.New("missing size") 131 } 132 if checksum == "" { 133 return errors.New("missing checksum") 134 } 135 format := checksumFormat 136 // TODO(fwereade): 2016-03-17 lp:1558657 137 finished := time.Now().UTC() 138 139 if err := m.SetFileInfo(size, checksum, format); err != nil { 140 return errors.Annotate(err, "unexpected failure") 141 } 142 m.Finished = &finished 143 144 return nil 145 } 146 147 type flatMetadata struct { 148 ID string 149 150 // file storage 151 152 Checksum string 153 ChecksumFormat string 154 Size int64 155 Stored time.Time 156 157 // backup 158 159 Started time.Time 160 Finished time.Time 161 Notes string 162 Environment string 163 Machine string 164 Hostname string 165 Version version.Number 166 167 CACert string 168 CAPrivateKey string 169 } 170 171 // TODO(ericsnow) Move AsJSONBuffer to filestorage.Metadata. 172 173 // AsJSONBuffer returns a bytes.Buffer containing the JSON-ified metadata. 174 func (m *Metadata) AsJSONBuffer() (io.Reader, error) { 175 flat := flatMetadata{ 176 ID: m.ID(), 177 178 Checksum: m.Checksum(), 179 ChecksumFormat: m.ChecksumFormat(), 180 Size: m.Size(), 181 182 Started: m.Started, 183 Notes: m.Notes, 184 Environment: m.Origin.Model, 185 Machine: m.Origin.Machine, 186 Hostname: m.Origin.Hostname, 187 Version: m.Origin.Version, 188 CACert: m.CACert, 189 CAPrivateKey: m.CAPrivateKey, 190 } 191 192 stored := m.Stored() 193 if stored != nil { 194 flat.Stored = *stored 195 } 196 197 if m.Finished != nil { 198 flat.Finished = *m.Finished 199 } 200 201 var outfile bytes.Buffer 202 if err := json.NewEncoder(&outfile).Encode(flat); err != nil { 203 return nil, errors.Trace(err) 204 } 205 return &outfile, nil 206 } 207 208 // NewMetadataJSONReader extracts a new metadata from the JSON file. 209 func NewMetadataJSONReader(in io.Reader) (*Metadata, error) { 210 var flat flatMetadata 211 if err := json.NewDecoder(in).Decode(&flat); err != nil { 212 return nil, errors.Trace(err) 213 } 214 215 meta := NewMetadata() 216 meta.SetID(flat.ID) 217 218 err := meta.SetFileInfo(flat.Size, flat.Checksum, flat.ChecksumFormat) 219 if err != nil { 220 return nil, errors.Trace(err) 221 } 222 223 if !flat.Stored.IsZero() { 224 meta.SetStored(&flat.Stored) 225 } 226 227 meta.Started = flat.Started 228 if !flat.Finished.IsZero() { 229 meta.Finished = &flat.Finished 230 } 231 meta.Notes = flat.Notes 232 meta.Origin = Origin{ 233 Model: flat.Environment, 234 Machine: flat.Machine, 235 Hostname: flat.Hostname, 236 Version: flat.Version, 237 } 238 239 // TODO(wallyworld) - put these in a separate file. 240 meta.CACert = flat.CACert 241 meta.CAPrivateKey = flat.CAPrivateKey 242 243 return meta, nil 244 } 245 246 func fileTimestamp(fi os.FileInfo) time.Time { 247 timestamp := creationTime(fi) 248 if !timestamp.IsZero() { 249 return timestamp 250 } 251 // Fall back to modification time. 252 return fi.ModTime() 253 } 254 255 // BuildMetadata generates the metadata for a backup archive file. 256 func BuildMetadata(file *os.File) (*Metadata, error) { 257 258 // Extract the file size. 259 fi, err := file.Stat() 260 if err != nil { 261 return nil, errors.Trace(err) 262 } 263 size := fi.Size() 264 265 // Extract the timestamp. 266 timestamp := fileTimestamp(fi) 267 268 // Get the checksum. 269 hasher := sha1.New() 270 _, err = io.Copy(hasher, file) 271 if err != nil { 272 return nil, errors.Trace(err) 273 } 274 rawsum := hasher.Sum(nil) 275 checksum := base64.StdEncoding.EncodeToString(rawsum) 276 277 // Build the metadata. 278 meta := NewMetadata() 279 meta.Started = time.Time{} 280 meta.Origin = UnknownOrigin() 281 err = meta.MarkComplete(size, checksum) 282 if err != nil { 283 return nil, errors.Trace(err) 284 } 285 meta.Finished = ×tamp 286 return meta, nil 287 }