~themue/juju-core/053-env-more-script-friendly

« back to all changes in this revision

Viewing changes to environs/maas/storage.go

Merge trunk and resolve conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package maas
 
2
 
 
3
import (
 
4
        "bytes"
 
5
        "encoding/base64"
 
6
        "fmt"
 
7
        "io"
 
8
        "io/ioutil"
 
9
        "launchpad.net/gomaasapi"
 
10
        "launchpad.net/juju-core/environs"
 
11
        "net/url"
 
12
        "sort"
 
13
        "sync"
 
14
)
 
15
 
 
16
type maasStorage struct {
 
17
        // Mutex protects the "*Unlocked" fields.
 
18
        sync.Mutex
 
19
 
 
20
        // The Environ that this Storage is for.
 
21
        environUnlocked *maasEnviron
 
22
 
 
23
        // Reference to the URL on the API where files are stored.
 
24
        maasClientUnlocked gomaasapi.MAASObject
 
25
}
 
26
 
 
27
var _ environs.Storage = (*maasStorage)(nil)
 
28
 
 
29
func NewStorage(env *maasEnviron) environs.Storage {
 
30
        storage := new(maasStorage)
 
31
        storage.environUnlocked = env
 
32
        storage.maasClientUnlocked = env.getMAASClient().GetSubObject("files")
 
33
        return storage
 
34
}
 
35
 
 
36
// getSnapshot returns a consistent copy of a maasStorage.  Use this if you
 
37
// need a consistent view of the object's entire state, without having to
 
38
// lock the object the whole time.
 
39
//
 
40
// An easy mistake to make with "defer" is to keep holding a lock without
 
41
// realizing it, while you go on to block on http requests or other slow
 
42
// things that don't actually require the lock.  In most cases you can just
 
43
// create a snapshot first (releasing the lock immediately) and then do the
 
44
// rest of the work with the snapshot.
 
45
func (stor *maasStorage) getSnapshot() *maasStorage {
 
46
        stor.Lock()
 
47
        defer stor.Unlock()
 
48
 
 
49
        return &maasStorage{
 
50
                environUnlocked:    stor.environUnlocked,
 
51
                maasClientUnlocked: stor.maasClientUnlocked,
 
52
        }
 
53
}
 
54
 
 
55
// addressFileObject creates a MAASObject pointing to a given file.
 
56
// Takes out a lock on the storage object to get a consistent view.
 
57
func (stor *maasStorage) addressFileObject(name string) gomaasapi.MAASObject {
 
58
        stor.Lock()
 
59
        defer stor.Unlock()
 
60
        return stor.maasClientUnlocked.GetSubObject(name)
 
61
}
 
62
 
 
63
// retrieveFileObject retrieves the information of the named file, including
 
64
// its download URL and its contents, as a MAASObject.
 
65
//
 
66
// This may return many different errors, but specifically, it returns
 
67
// (a pointer to) environs.NotFoundError if the file did not exist.
 
68
//
 
69
// The function takes out a lock on the storage object.
 
70
func (stor *maasStorage) retrieveFileObject(name string) (gomaasapi.MAASObject, error) {
 
71
        obj, err := stor.addressFileObject(name).Get()
 
72
        if err != nil {
 
73
                noObj := gomaasapi.MAASObject{}
 
74
                serverErr, ok := err.(gomaasapi.ServerError)
 
75
                if ok && serverErr.StatusCode == 404 {
 
76
                        msg := fmt.Errorf("file '%s' not found", name)
 
77
                        return noObj, &environs.NotFoundError{msg}
 
78
                }
 
79
                msg := fmt.Errorf("could not access file '%s': %v", name, err)
 
80
                return noObj, msg
 
81
        }
 
82
        return obj, nil
 
83
}
 
84
 
 
85
// Get is specified in the Storage interface.
 
86
func (stor *maasStorage) Get(name string) (io.ReadCloser, error) {
 
87
        fileObj, err := stor.retrieveFileObject(name)
 
88
        if err != nil {
 
89
                return nil, err
 
90
        }
 
91
        data, err := fileObj.GetField("content")
 
92
        if err != nil {
 
93
                return nil, fmt.Errorf("could not extract file content for %s: %v", name, err)
 
94
        }
 
95
        buf, err := base64.StdEncoding.DecodeString(data)
 
96
        if err != nil {
 
97
                return nil, fmt.Errorf("bad data in file '%s': %v", name, err)
 
98
        }
 
99
        return ioutil.NopCloser(bytes.NewReader(buf)), nil
 
100
}
 
101
 
 
102
// extractFilenames returns the filenames from a "list" operation on the
 
103
// MAAS API, sorted by name.
 
104
func (stor *maasStorage) extractFilenames(listResult gomaasapi.JSONObject) ([]string, error) {
 
105
        list, err := listResult.GetArray()
 
106
        if err != nil {
 
107
                return nil, err
 
108
        }
 
109
        result := make([]string, len(list))
 
110
        for index, entry := range list {
 
111
                file, err := entry.GetMap()
 
112
                if err != nil {
 
113
                        return nil, err
 
114
                }
 
115
                filename, err := file["filename"].GetString()
 
116
                if err != nil {
 
117
                        return nil, err
 
118
                }
 
119
                result[index] = filename
 
120
        }
 
121
        sort.Strings(result)
 
122
        return result, nil
 
123
}
 
124
 
 
125
// List is specified in the Storage interface.
 
126
func (stor *maasStorage) List(prefix string) ([]string, error) {
 
127
        params := make(url.Values)
 
128
        params.Add("prefix", prefix)
 
129
        snapshot := stor.getSnapshot()
 
130
        obj, err := snapshot.maasClientUnlocked.CallGet("list", params)
 
131
        if err != nil {
 
132
                return nil, err
 
133
        }
 
134
        return snapshot.extractFilenames(obj)
 
135
}
 
136
 
 
137
// URL is specified in the Storage interface.
 
138
func (stor *maasStorage) URL(name string) (string, error) {
 
139
        fileObj, err := stor.retrieveFileObject(name)
 
140
        if err != nil {
 
141
                return "", err
 
142
        }
 
143
        uri, err := fileObj.GetField("anon_resource_uri")
 
144
        if err != nil {
 
145
                msg := fmt.Errorf("could not get file's download URL (may be an outdated MAAS): %s", err)
 
146
                return "", msg
 
147
        }
 
148
 
 
149
        partialURL, err := url.Parse(uri)
 
150
        if err != nil {
 
151
                return "", err
 
152
        }
 
153
        fullURL := fileObj.URL().ResolveReference(partialURL)
 
154
        return fullURL.String(), nil
 
155
}
 
156
 
 
157
// Put is specified in the Storage interface.
 
158
func (stor *maasStorage) Put(name string, r io.Reader, length int64) error {
 
159
        data, err := ioutil.ReadAll(io.LimitReader(r, length))
 
160
        if err != nil {
 
161
                return err
 
162
        }
 
163
        params := url.Values{"filename": {name}}
 
164
        files := map[string][]byte{"file": data}
 
165
        snapshot := stor.getSnapshot()
 
166
        _, err = snapshot.maasClientUnlocked.CallPostFiles("add", params, files)
 
167
        return err
 
168
}
 
169
 
 
170
// Remove is specified in the Storage interface.
 
171
func (stor *maasStorage) Remove(name string) error {
 
172
        // The only thing that can go wrong here, really, is that the file
 
173
        // does not exist.  But deletion is idempotent: deleting a file that
 
174
        // is no longer there anyway is success, not failure.
 
175
        stor.getSnapshot().maasClientUnlocked.GetSubObject(name).Delete()
 
176
        return nil
 
177
}
 
178
 
 
179
func (stor *maasStorage) deleteAll() error {
 
180
        names, err := stor.List("")
 
181
        if err != nil {
 
182
                return err
 
183
        }
 
184
        // Remove all the objects in parallel so that we incur less round-trips.
 
185
        // If we're in danger of having hundreds of objects,
 
186
        // we'll want to change this to limit the number
 
187
        // of concurrent operations.
 
188
        var wg sync.WaitGroup
 
189
        wg.Add(len(names))
 
190
        errc := make(chan error, len(names))
 
191
        for _, name := range names {
 
192
                name := name
 
193
                go func() {
 
194
                        defer wg.Done()
 
195
                        if err := stor.Remove(name); err != nil {
 
196
                                errc <- err
 
197
                        }
 
198
                }()
 
199
        }
 
200
        wg.Wait()
 
201
        select {
 
202
        case err := <-errc:
 
203
                return fmt.Errorf("cannot delete all provider state: %v", err)
 
204
        default:
 
205
        }
 
206
        return nil
 
207
}