Skip to content

Commit

Permalink
finished charm package
Browse files Browse the repository at this point in the history
  • Loading branch information
fwereade committed Aug 17, 2012
1 parent ff09f68 commit 6df36f4
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 96 deletions.
71 changes: 12 additions & 59 deletions downloader/downloader_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package downloader_test

import (
"fmt"
"io/ioutil"
. "launchpad.net/gocheck"
"launchpad.net/juju-core/downloader"
"launchpad.net/juju-core/testing"
"net"
"net/http"
"os"
"path/filepath"
stdtesting "testing"
Expand All @@ -33,11 +30,11 @@ func (s *suite) TearDownTest(c *C) {
}

func (s *suite) TestDownload(c *C) {
l := newServer()
defer l.close()
l := testing.NewServer()
defer l.Close()

content := l.addContent("/archive.tgz", "archive")
d := downloader.New(content.url)
url := l.AddContent("/archive.tgz", []byte("archive"))
d := downloader.New(url)
status := <-d.Done()
c.Assert(status.Err, IsNil)
c.Assert(status.File, NotNil)
Expand All @@ -50,23 +47,23 @@ func (s *suite) TestDownload(c *C) {
}

func (s *suite) TestDownloadError(c *C) {
l := newServer()
defer l.close()
l := testing.NewServer()
defer l.Close()
// Add some content, then delete it - we should
// get a 404 response.
url := l.addContent("/archive.tgz", "archive").url
delete(l.contents, "/archive.tgz")
url := l.AddContent("/archive.tgz", nil)
l.RemoveContent("/archive.tgz")
d := downloader.New(url)
status := <-d.Done()
c.Assert(status.File, IsNil)
c.Assert(status.Err, ErrorMatches, `cannot download ".*": bad http response: 404 Not Found`)
}

func (s *suite) TestStopDownload(c *C) {
l := newServer()
defer l.close()
content := l.addContent("/x.tgz", "content")
d := downloader.New(content.url)
l := testing.NewServer()
defer l.Close()
url := l.AddContent("/x.tgz", []byte("content"))
d := downloader.New(url)
d.Stop()
select {
case status := <-d.Done():
Expand All @@ -87,47 +84,3 @@ func assertFileContents(c *C, f *os.File, expect string) {
c.Logf("info %#v", info)
}
}

type content struct {
url string
data []byte
}

type server struct {
l net.Listener
contents map[string]*content
}

func newServer() *server {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
panic(fmt.Errorf("cannot start server: %v", err))
}
srv := &server{l, make(map[string]*content)}
go http.Serve(l, srv)
return srv
}

func (srv *server) close() {
srv.l.Close()
}

// addContent makes the given data available from the server
// at the given URL path.
func (srv *server) addContent(path string, data string) *content {
c := &content{
data: []byte(data),
}
c.url = fmt.Sprintf("http://%v%s", srv.l.Addr(), path)
srv.contents[path] = c
return c
}

func (srv *server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := srv.contents[req.URL.Path]
if c == nil {
http.NotFound(w, req)
return
}
w.Write(c.data)
}
57 changes: 57 additions & 0 deletions testing/httpserver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package testing

import (
"fmt"
"net"
"net/http"
"time"
)

// Server is an HTTP server that is convenient to use in tests.
type Server struct {
l net.Listener
delays map[string]time.Duration
contents map[string][]byte
}

func NewServer() *Server {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
panic(fmt.Errorf("cannot start server: %v", err))
}
srv := &Server{l, make(map[string]time.Duration), make(map[string][]byte)}
go http.Serve(l, srv)
return srv
}

func (srv *Server) Close() {
srv.l.Close()
}

// AddContent makes the given data available from the server
// at the given path. It returns a URL that will access that path.
func (srv *Server) AddContent(path string, data []byte) string {
srv.contents[path] = data
return fmt.Sprintf("http://%v%s", srv.l.Addr(), path)
}

// RemoveContent makes the given URL path return a 404.
func (srv *Server) RemoveContent(path string) {
delete(srv.contents, path)
}

// AddDelay causes the server to pause for the supplied duration before
// writing its response to requests for the given path.
func (srv *Server) AddDelay(path string, delay time.Duration) {
srv.delays[path] = delay
}

func (srv *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
<-time.After(srv.delays[req.URL.Path])
data, found := srv.contents[req.URL.Path]
if !found {
http.NotFound(w, req)
return
}
w.Write(data)
}
68 changes: 37 additions & 31 deletions worker/uniter/charm/charm.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const (
// valid returns false if the status is not recognized.
func (s Status) valid() bool {
switch s {
case Missing, Installing, Installed, Upgrading, UpgradingForced:
case Installing, Installed, Upgrading, UpgradingForced:
return true
}
return false
Expand All @@ -42,11 +42,18 @@ type State struct {
}

// StateFile gives access to persistent charm state.
type StateFile string
type StateFile struct {
path string
}

// NewStateFile returns a new state file at path.
func NewStateFile(path string) *StateFile {
return &StateFile{path}
}

// Read reads charm state stored at f.
func (f StateFile) Read() (st State, err error) {
data, err := ioutil.ReadFile(string(f))
func (f *StateFile) Read() (st State, err error) {
data, err := ioutil.ReadFile(f.path)
if os.IsNotExist(err) {
return st, nil
} else if err != nil {
Expand All @@ -56,56 +63,56 @@ func (f StateFile) Read() (st State, err error) {
return
}
if !st.Status.valid() {
return State{}, fmt.Errorf("invalid charm state at %s", f)
return State{}, fmt.Errorf("invalid charm state at %s", f.path)
}
return
}

// Write writes charm state to f.
func (f StateFile) Write(s Status) error {
func (f *StateFile) Write(s Status) error {
if !s.valid() {
panic(fmt.Errorf("unknown charm status %q", s))
} else if s == Missing {
panic("insane operation")
panic(fmt.Errorf("invalid charm status %q", s))
}
return trivial.AtomicWrite(f, &State{s})
return trivial.AtomicWrite(f.path, &State{s})
}

// Bundles is responsible for storing and retrieving charm bundles
// BundlesDir is responsible for storing and retrieving charm bundles
// identified by state charms.
type Bundles struct {
type BundlesDir struct {
path string
}

func NewBundles(path string) *Bundles {
return *Bundles{path}
// NewBundlesDir returns a new BundlesDir which uses path for storage.
func NewBundlesDir(path string) *BundlesDir {
return &BundlesDir{path}
}

// Get returns a charm bundle from the directory. If no bundle exists yet,
// one will be downloaded and validated and copied into the bundles directory
// before being returned. Downloads will be aborted if the supplied tomb dies.
func (b Bundles) Get(sch *state.Charm, t *tomb.Tomb) (*charm.Bundle, error) {
path := b.path(sch)
// Read returns a charm bundle from the directory. If no bundle exists yet,
// one will be downloaded and validated and copied into the directory before
// being returned. Downloads will be aborted if the supplied tomb dies.
func (d *BundlesDir) Read(sch *state.Charm, t *tomb.Tomb) (*charm.Bundle, error) {
path := d.bundlePath(sch)
if _, err := os.Stat(path); err != nil {
if !os.IsNotExist(err) {
return nil, err
} else if err = b.download(sch, t); err != nil {
} else if err = d.download(sch, t); err != nil {
return nil, err
}
}
return charm.ReadBundle(path)
}

// download gets the supplied charm and checks that it has the correct sha256
// hash, then copies it into the bundles directory. If the supplied tomb dies,
// the download will abort.
func (b Bundles) download(sch *state.Charm, t *tomb.Tomb) (err error) {
// hash, then copies it into the directory. If the supplied tomb dies, the
// download will abort.
func (d *BundlesDir) download(sch *state.Charm, t *tomb.Tomb) (err error) {
defer trivial.ErrorContextf(&err, "failed to download charm %q from %q", sch.URL(), sch.BundleURL())
dl := downloader.New(sch.BundleURL().String())
defer dl.Stop()
for {
select {
case <-t.Dying():
return tomb.ErrDying
return fmt.Errorf("aborted")
case st := <-dl.Done():
if st.Err != nil {
return st.Err
Expand All @@ -118,21 +125,20 @@ func (b Bundles) download(sch *state.Charm, t *tomb.Tomb) (err error) {
actualSha256 := hex.EncodeToString(hash.Sum(nil))
if actualSha256 != sch.BundleSha256() {
return fmt.Errorf(
"sha256 mismatch for %q from %q: expected %q, got %q",
sch.URL(), sch.BundleURL(), sch.BundleSha256(), actualSha256,
"expected sha256 %q, got %q", sch.BundleSha256(), actualSha256,
)
}
if err := trivial.EnsureDir(b); err != nil {
if err := trivial.EnsureDir(d.path); err != nil {
return err
}
return os.Rename(st.File.Name(), b.path(sch))
return os.Rename(st.File.Name(), d.bundlePath(sch))
}
}
panic("unreachable")
}

// path returns the path to the location where the verified charm
// bundlePath returns the path to the location where the verified charm
// bundle identified by sch will be, or has been, saved.
func (b Bundles) bundlePath(sch *state.Charm) string {
return filepath.Join(b, charm.Quote(sch.URL().String()))
func (d *BundlesDir) bundlePath(sch *state.Charm) string {
return filepath.Join(d.path, charm.Quote(sch.URL().String()))
}
Loading

0 comments on commit 6df36f4

Please sign in to comment.