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