~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/state/backups/metadata.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 = &timestamp
 
291
        return meta, nil
 
292
}