9
"launchpad.net/gomaasapi"
10
"launchpad.net/juju-core/environs"
16
type maasStorage struct {
17
// Mutex protects the "*Unlocked" fields.
20
// The Environ that this Storage is for.
21
environUnlocked *maasEnviron
23
// Reference to the URL on the API where files are stored.
24
maasClientUnlocked gomaasapi.MAASObject
27
var _ environs.Storage = (*maasStorage)(nil)
29
func NewStorage(env *maasEnviron) environs.Storage {
30
storage := new(maasStorage)
31
storage.environUnlocked = env
32
storage.maasClientUnlocked = env.getMAASClient().GetSubObject("files")
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.
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 {
50
environUnlocked: stor.environUnlocked,
51
maasClientUnlocked: stor.maasClientUnlocked,
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 {
60
return stor.maasClientUnlocked.GetSubObject(name)
63
// retrieveFileObject retrieves the information of the named file, including
64
// its download URL and its contents, as a MAASObject.
66
// This may return many different errors, but specifically, it returns
67
// (a pointer to) environs.NotFoundError if the file did not exist.
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()
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}
79
msg := fmt.Errorf("could not access file '%s': %v", name, err)
85
// Get is specified in the Storage interface.
86
func (stor *maasStorage) Get(name string) (io.ReadCloser, error) {
87
fileObj, err := stor.retrieveFileObject(name)
91
data, err := fileObj.GetField("content")
93
return nil, fmt.Errorf("could not extract file content for %s: %v", name, err)
95
buf, err := base64.StdEncoding.DecodeString(data)
97
return nil, fmt.Errorf("bad data in file '%s': %v", name, err)
99
return ioutil.NopCloser(bytes.NewReader(buf)), nil
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()
109
result := make([]string, len(list))
110
for index, entry := range list {
111
file, err := entry.GetMap()
115
filename, err := file["filename"].GetString()
119
result[index] = filename
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)
134
return snapshot.extractFilenames(obj)
137
// URL is specified in the Storage interface.
138
func (stor *maasStorage) URL(name string) (string, error) {
139
fileObj, err := stor.retrieveFileObject(name)
143
uri, err := fileObj.GetField("anon_resource_uri")
145
msg := fmt.Errorf("could not get file's download URL (may be an outdated MAAS): %s", err)
149
partialURL, err := url.Parse(uri)
153
fullURL := fileObj.URL().ResolveReference(partialURL)
154
return fullURL.String(), nil
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))
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)
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()
179
func (stor *maasStorage) deleteAll() error {
180
names, err := stor.List("")
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
190
errc := make(chan error, len(names))
191
for _, name := range names {
195
if err := stor.Remove(name); err != nil {
203
return fmt.Errorf("cannot delete all provider state: %v", err)