github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/charm/testing/mockstore.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package testing
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"io"
    10  	"net"
    11  	"net/http"
    12  	"os"
    13  	"strconv"
    14  	"strings"
    15  
    16  	"github.com/juju/loggo"
    17  	gc "launchpad.net/gocheck"
    18  
    19  	"launchpad.net/juju-core/charm"
    20  	"launchpad.net/juju-core/testing"
    21  	"launchpad.net/juju-core/utils"
    22  )
    23  
    24  var logger = loggo.GetLogger("juju.charm.testing.mockstore")
    25  
    26  // MockStore provides a mock charm store implementation useful when testing.
    27  type MockStore struct {
    28  	mux                     *http.ServeMux
    29  	listener                net.Listener
    30  	bundleBytes             []byte
    31  	bundleSha256            string
    32  	Downloads               []*charm.URL
    33  	DownloadsNoStats        []*charm.URL
    34  	Authorizations          []string
    35  	Metadata                []string
    36  	InfoRequestCount        int
    37  	InfoRequestCountNoStats int
    38  
    39  	charms map[string]int
    40  }
    41  
    42  // NewMockStore creates a mock charm store containing the specified charms.
    43  func NewMockStore(c *gc.C, charms map[string]int) *MockStore {
    44  	s := &MockStore{charms: charms}
    45  	f, err := os.Open(testing.Charms.BundlePath(c.MkDir(), "dummy"))
    46  	c.Assert(err, gc.IsNil)
    47  	defer f.Close()
    48  	buf := &bytes.Buffer{}
    49  	s.bundleSha256, _, err = utils.ReadSHA256(io.TeeReader(f, buf))
    50  	c.Assert(err, gc.IsNil)
    51  	s.bundleBytes = buf.Bytes()
    52  	c.Assert(err, gc.IsNil)
    53  	s.mux = http.NewServeMux()
    54  	s.mux.HandleFunc("/charm-info", s.serveInfo)
    55  	s.mux.HandleFunc("/charm-event", s.serveEvent)
    56  	s.mux.HandleFunc("/charm/", s.serveCharm)
    57  	lis, err := net.Listen("tcp", "127.0.0.1:0")
    58  	c.Assert(err, gc.IsNil)
    59  	s.listener = lis
    60  	go http.Serve(s.listener, s)
    61  	return s
    62  }
    63  
    64  // Close closes the mock store's socket.
    65  func (s *MockStore) Close() {
    66  	s.listener.Close()
    67  }
    68  
    69  // Address returns the URL used to make requests to the mock store.
    70  func (s *MockStore) Address() string {
    71  	return "http://" + s.listener.Addr().String()
    72  }
    73  
    74  // UpdateStoreRevision sets the revision of the specified charm to rev.
    75  func (s *MockStore) UpdateStoreRevision(ch string, rev int) {
    76  	s.charms[ch] = rev
    77  }
    78  
    79  // ServeHTTP implements http.ServeHTTP
    80  func (s *MockStore) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    81  	s.mux.ServeHTTP(w, r)
    82  }
    83  
    84  func (s *MockStore) serveInfo(w http.ResponseWriter, r *http.Request) {
    85  	if metadata := r.Header.Get("Juju-Metadata"); metadata != "" {
    86  		s.Metadata = append(s.Metadata, metadata)
    87  		logger.Infof("Juju metadata: " + metadata)
    88  	}
    89  
    90  	r.ParseForm()
    91  	if r.Form.Get("stats") == "0" {
    92  		s.InfoRequestCountNoStats += 1
    93  	} else {
    94  		s.InfoRequestCount += 1
    95  	}
    96  
    97  	response := map[string]*charm.InfoResponse{}
    98  	for _, url := range r.Form["charms"] {
    99  		cr := &charm.InfoResponse{}
   100  		response[url] = cr
   101  		charmURL := charm.MustParseURL(url)
   102  		switch charmURL.Name {
   103  		case "borken":
   104  			cr.Errors = append(cr.Errors, "badness")
   105  		case "terracotta":
   106  			cr.Errors = append(cr.Errors, "cannot get revision")
   107  		case "unwise":
   108  			cr.Warnings = append(cr.Warnings, "foolishness")
   109  			fallthrough
   110  		default:
   111  			if rev, ok := s.charms[charmURL.WithRevision(-1).String()]; ok {
   112  				if charmURL.Revision == -1 {
   113  					cr.Revision = rev
   114  				} else {
   115  					cr.Revision = charmURL.Revision
   116  				}
   117  				cr.Sha256 = s.bundleSha256
   118  			} else {
   119  				cr.Errors = append(cr.Errors, "entry not found")
   120  			}
   121  		}
   122  	}
   123  	data, err := json.Marshal(response)
   124  	if err != nil {
   125  		panic(err)
   126  	}
   127  	w.Header().Set("Content-Type", "application/json")
   128  	_, err = w.Write(data)
   129  	if err != nil {
   130  		panic(err)
   131  	}
   132  }
   133  
   134  func (s *MockStore) serveEvent(w http.ResponseWriter, r *http.Request) {
   135  	r.ParseForm()
   136  	response := map[string]*charm.EventResponse{}
   137  	for _, url := range r.Form["charms"] {
   138  		digest := ""
   139  		if i := strings.Index(url, "@"); i >= 0 {
   140  			digest = url[i+1:]
   141  			url = url[:i]
   142  		}
   143  		er := &charm.EventResponse{}
   144  		response[url] = er
   145  		if digest != "" && digest != "the-digest" {
   146  			er.Kind = "not-found"
   147  			er.Errors = []string{"entry not found"}
   148  			continue
   149  		}
   150  		charmURL := charm.MustParseURL(url)
   151  		switch charmURL.Name {
   152  		case "borken":
   153  			er.Kind = "publish-error"
   154  			er.Errors = append(er.Errors, "badness")
   155  		case "unwise":
   156  			er.Warnings = append(er.Warnings, "foolishness")
   157  			fallthrough
   158  		default:
   159  			if rev, ok := s.charms[charmURL.WithRevision(-1).String()]; ok {
   160  				er.Kind = "published"
   161  				er.Revision = rev
   162  				er.Digest = "the-digest"
   163  			} else {
   164  				er.Kind = "not-found"
   165  				er.Errors = []string{"entry not found"}
   166  			}
   167  		}
   168  	}
   169  	data, err := json.Marshal(response)
   170  	if err != nil {
   171  		panic(err)
   172  	}
   173  	w.Header().Set("Content-Type", "application/json")
   174  	_, err = w.Write(data)
   175  	if err != nil {
   176  		panic(err)
   177  	}
   178  }
   179  
   180  func (s *MockStore) serveCharm(w http.ResponseWriter, r *http.Request) {
   181  	charmURL := charm.MustParseURL("cs:" + r.URL.Path[len("/charm/"):])
   182  
   183  	r.ParseForm()
   184  	if r.Form.Get("stats") == "0" {
   185  		s.DownloadsNoStats = append(s.DownloadsNoStats, charmURL)
   186  	} else {
   187  		s.Downloads = append(s.Downloads, charmURL)
   188  	}
   189  
   190  	if auth := r.Header.Get("Authorization"); auth != "" {
   191  		s.Authorizations = append(s.Authorizations, auth)
   192  	}
   193  
   194  	w.Header().Set("Connection", "close")
   195  	w.Header().Set("Content-Type", "application/octet-stream")
   196  	w.Header().Set("Content-Length", strconv.Itoa(len(s.bundleBytes)))
   197  	_, err := w.Write(s.bundleBytes)
   198  	if err != nil {
   199  		panic(err)
   200  	}
   201  }