github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/maas/storage.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package maas
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/base64"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"net/url"
    13  	"sort"
    14  	"strings"
    15  	"sync"
    16  
    17  	"github.com/juju/errors"
    18  	"github.com/juju/gomaasapi"
    19  	"github.com/juju/utils"
    20  
    21  	"github.com/juju/juju/environs/storage"
    22  )
    23  
    24  type maas1Storage struct {
    25  	// The Environ that this Storage is for.
    26  	environ *maasEnviron
    27  
    28  	// Reference to the URL on the API where files are stored.
    29  	maasClient gomaasapi.MAASObject
    30  }
    31  
    32  var _ storage.Storage = (*maas1Storage)(nil)
    33  
    34  func NewStorage(env *maasEnviron) storage.Storage {
    35  	if env.usingMAAS2() {
    36  		return &maas2Storage{
    37  			environ:        env,
    38  			maasController: env.maasController,
    39  		}
    40  	} else {
    41  		return &maas1Storage{
    42  			environ:    env,
    43  			maasClient: env.getMAASClient().GetSubObject("files"),
    44  		}
    45  	}
    46  }
    47  
    48  // addressFileObject creates a MAASObject pointing to a given file.
    49  // Takes out a lock on the storage object to get a consistent view.
    50  func (stor *maas1Storage) addressFileObject(name string) gomaasapi.MAASObject {
    51  	return stor.maasClient.GetSubObject(name)
    52  }
    53  
    54  // All filenames need to be namespaced so they are private to this environment.
    55  // This prevents different environments from interfering with each other.
    56  // We're using the agent name UUID here.
    57  func prefixWithPrivateNamespace(env *maasEnviron, name string) string {
    58  	prefix := env.ecfg().maasAgentName()
    59  	if prefix != "" {
    60  		return prefix + "-" + name
    61  	}
    62  	return name
    63  }
    64  
    65  func (stor *maas1Storage) prefixWithPrivateNamespace(name string) string {
    66  	return prefixWithPrivateNamespace(stor.environ, name)
    67  }
    68  
    69  // retrieveFileObject retrieves the information of the named file,
    70  // including its download URL and its contents, as a MAASObject.
    71  //
    72  // This may return many different errors, but specifically, it returns
    73  // an error that satisfies errors.IsNotFound if the file did not
    74  // exist.
    75  //
    76  // The function takes out a lock on the storage object.
    77  func (stor *maas1Storage) retrieveFileObject(name string) (gomaasapi.MAASObject, error) {
    78  	obj, err := stor.addressFileObject(name).Get()
    79  	if err != nil {
    80  		noObj := gomaasapi.MAASObject{}
    81  		serverErr, ok := errors.Cause(err).(gomaasapi.ServerError)
    82  		if ok && serverErr.StatusCode == 404 {
    83  			return noObj, errors.NotFoundf("file '%s' not found", name)
    84  		}
    85  		msg := fmt.Errorf("could not access file '%s': %v", name, err)
    86  		return noObj, msg
    87  	}
    88  	return obj, nil
    89  }
    90  
    91  // Get is specified in the StorageReader interface.
    92  func (stor *maas1Storage) Get(name string) (io.ReadCloser, error) {
    93  	name = stor.prefixWithPrivateNamespace(name)
    94  	fileObj, err := stor.retrieveFileObject(name)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  	data, err := fileObj.GetField("content")
    99  	if err != nil {
   100  		return nil, fmt.Errorf("could not extract file content for %s: %v", name, err)
   101  	}
   102  	buf, err := base64.StdEncoding.DecodeString(data)
   103  	if err != nil {
   104  		return nil, fmt.Errorf("bad data in file '%s': %v", name, err)
   105  	}
   106  	return ioutil.NopCloser(bytes.NewReader(buf)), nil
   107  }
   108  
   109  // extractFilenames returns the filenames from a "list" operation on the
   110  // MAAS API, sorted by name.
   111  func (stor *maas1Storage) extractFilenames(listResult gomaasapi.JSONObject) ([]string, error) {
   112  	privatePrefix := stor.prefixWithPrivateNamespace("")
   113  	list, err := listResult.GetArray()
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	result := make([]string, len(list))
   118  	for index, entry := range list {
   119  		file, err := entry.GetMap()
   120  		if err != nil {
   121  			return nil, err
   122  		}
   123  		filename, err := file["filename"].GetString()
   124  		if err != nil {
   125  			return nil, err
   126  		}
   127  		// When listing files we need to return them without our special prefix.
   128  		result[index] = strings.TrimPrefix(filename, privatePrefix)
   129  	}
   130  	sort.Strings(result)
   131  	return result, nil
   132  }
   133  
   134  // List is specified in the StorageReader interface.
   135  func (stor *maas1Storage) List(prefix string) ([]string, error) {
   136  	prefix = stor.prefixWithPrivateNamespace(prefix)
   137  	params := make(url.Values)
   138  	params.Add("prefix", prefix)
   139  	obj, err := stor.maasClient.CallGet("list", params)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	return stor.extractFilenames(obj)
   144  }
   145  
   146  // URL is specified in the StorageReader interface.
   147  func (stor *maas1Storage) URL(name string) (string, error) {
   148  	name = stor.prefixWithPrivateNamespace(name)
   149  	fileObj, err := stor.retrieveFileObject(name)
   150  	if err != nil {
   151  		return "", err
   152  	}
   153  	uri, err := fileObj.GetField("anon_resource_uri")
   154  	if err != nil {
   155  		msg := fmt.Errorf("could not get file's download URL (may be an outdated MAAS): %s", err)
   156  		return "", msg
   157  	}
   158  
   159  	partialURL, err := url.Parse(uri)
   160  	if err != nil {
   161  		return "", err
   162  	}
   163  	fullURL := fileObj.URL().ResolveReference(partialURL)
   164  	return fullURL.String(), nil
   165  }
   166  
   167  // DefaultConsistencyStrategy is specified in the StorageReader interface.
   168  func (stor *maas1Storage) DefaultConsistencyStrategy() utils.AttemptStrategy {
   169  	// This storage backend has immediate consistency, so there's no
   170  	// need to wait.  One attempt should do.
   171  	return utils.AttemptStrategy{}
   172  }
   173  
   174  // ShouldRetry is specified in the StorageReader interface.
   175  func (stor *maas1Storage) ShouldRetry(err error) bool {
   176  	return false
   177  }
   178  
   179  // Put is specified in the StorageWriter interface.
   180  func (stor *maas1Storage) Put(name string, r io.Reader, length int64) error {
   181  	name = stor.prefixWithPrivateNamespace(name)
   182  	data, err := ioutil.ReadAll(io.LimitReader(r, length))
   183  	if err != nil {
   184  		return err
   185  	}
   186  	params := url.Values{"filename": {name}}
   187  	files := map[string][]byte{"file": data}
   188  	_, err = stor.maasClient.CallPostFiles("add", params, files)
   189  	return err
   190  }
   191  
   192  // Remove is specified in the StorageWriter interface.
   193  func (stor *maas1Storage) Remove(name string) error {
   194  	name = stor.prefixWithPrivateNamespace(name)
   195  	// The only thing that can go wrong here, really, is that the file
   196  	// does not exist.  But deletion is idempotent: deleting a file that
   197  	// is no longer there anyway is success, not failure.
   198  	stor.maasClient.GetSubObject(name).Delete()
   199  	return nil
   200  }
   201  
   202  // RemoveAll is specified in the StorageWriter interface.
   203  func (stor *maas1Storage) RemoveAll() error {
   204  	return removeAll(stor)
   205  }
   206  
   207  func removeAll(stor storage.Storage) error {
   208  	names, err := storage.List(stor, "")
   209  	if err != nil {
   210  		return err
   211  	}
   212  	// Remove all the objects in parallel so that we incur fewer round-trips.
   213  	// If we're in danger of having hundreds of objects,
   214  	// we'll want to change this to limit the number
   215  	// of concurrent operations.
   216  	var wg sync.WaitGroup
   217  	wg.Add(len(names))
   218  	errc := make(chan error, len(names))
   219  	for _, name := range names {
   220  		name := name
   221  		go func() {
   222  			defer wg.Done()
   223  			if err := stor.Remove(name); err != nil {
   224  				errc <- err
   225  			}
   226  		}()
   227  	}
   228  	wg.Wait()
   229  	select {
   230  	case err := <-errc:
   231  		return fmt.Errorf("cannot delete all provider state: %v", err)
   232  	default:
   233  	}
   234  	return nil
   235  }