github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/tools/lxdclient/client_image_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // +build go1.3
     5  
     6  package lxdclient
     7  
     8  import (
     9  	"fmt"
    10  	"time"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/testing"
    14  	jc "github.com/juju/testing/checkers"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	coretesting "github.com/juju/juju/testing"
    18  )
    19  
    20  type imageSuite struct {
    21  	testing.IsolationSuite
    22  	Stub              *testing.Stub
    23  	remoteWithTrusty  *stubRemoteClient
    24  	remoteWithNothing *stubRemoteClient
    25  }
    26  
    27  var _ = gc.Suite(&imageSuite{})
    28  
    29  func (s *imageSuite) SetUpTest(c *gc.C) {
    30  	s.IsolationSuite.SetUpTest(c)
    31  	s.Stub = &testing.Stub{}
    32  	s.remoteWithTrusty = &stubRemoteClient{
    33  		stub: s.Stub,
    34  		url:  "https://match",
    35  		aliases: map[string]string{
    36  			"trusty": "deadbeef",
    37  		},
    38  	}
    39  	s.remoteWithNothing = &stubRemoteClient{
    40  		stub:    s.Stub,
    41  		url:     "https://missing",
    42  		aliases: nil,
    43  	}
    44  }
    45  
    46  type stubRemoteClient struct {
    47  	stub    *testing.Stub
    48  	url     string
    49  	aliases map[string]string
    50  }
    51  
    52  var _ remoteClient = (*stubRemoteClient)(nil)
    53  
    54  func (s *stubRemoteClient) URL() string {
    55  	// Note we don't log calls to URL because they are not interesting, and
    56  	// are generally just used for logging, etc.
    57  	return s.url
    58  }
    59  
    60  func (s *stubRemoteClient) GetAlias(alias string) string {
    61  	s.stub.AddCall("GetAlias", alias)
    62  	if err := s.stub.NextErr(); err != nil {
    63  		// GetAlias can't return an Err, but if we get an error, we'll
    64  		// just treat that as a miss on the Alias lookup.
    65  		return ""
    66  	}
    67  	return s.aliases[alias]
    68  }
    69  
    70  func (s *stubRemoteClient) CopyImage(imageTarget string, dest rawImageClient, aliases []string, callback func(string)) error {
    71  	// We don't include the destination or the callback because they aren't
    72  	// objects we can easily assert against.
    73  	s.stub.AddCall("CopyImage", imageTarget, aliases)
    74  	if err := s.stub.NextErr(); err != nil {
    75  		return err
    76  	}
    77  	// This is to make this CopyImage act a bit like a real CopyImage. it
    78  	// gives some Progress callbacks, and then sets the alias in the
    79  	// target.
    80  	if callback != nil {
    81  		// The real one gives progress every 1%
    82  		for i := 10; i <= 100; i += 10 {
    83  			callback(fmt.Sprintf("%d%%", i))
    84  			time.Sleep(1 * time.Microsecond)
    85  		}
    86  	}
    87  	if stubDest, ok := dest.(*stubClient); ok {
    88  		if stubDest.Aliases == nil {
    89  			stubDest.Aliases = make(map[string]string)
    90  		}
    91  		for _, alias := range aliases {
    92  			stubDest.Aliases[alias] = imageTarget
    93  		}
    94  	}
    95  	return nil
    96  }
    97  
    98  func (s *stubRemoteClient) AsRemote() Remote {
    99  	return Remote{
   100  		Host:     s.url,
   101  		Protocol: SimplestreamsProtocol,
   102  	}
   103  }
   104  
   105  type stubConnector struct {
   106  	stub          *testing.Stub
   107  	remoteClients map[string]remoteClient
   108  }
   109  
   110  func MakeConnector(stub *testing.Stub, remotes ...remoteClient) *stubConnector {
   111  	remoteMap := make(map[string]remoteClient)
   112  	for _, remote := range remotes {
   113  		remoteMap[remote.URL()] = remote
   114  	}
   115  	return &stubConnector{
   116  		stub:          stub,
   117  		remoteClients: remoteMap,
   118  	}
   119  }
   120  
   121  func (s *stubConnector) connectToSource(remote Remote) (remoteClient, error) {
   122  	s.stub.AddCall("connectToSource", remote.Host)
   123  	if err := s.stub.NextErr(); err != nil {
   124  		return nil, err
   125  	}
   126  	return s.remoteClients[remote.Host], nil
   127  }
   128  
   129  func (s *imageSuite) TestEnsureImageExistsAlreadyPresent(c *gc.C) {
   130  	raw := &stubClient{
   131  		stub: s.Stub,
   132  		Aliases: map[string]string{
   133  			"ubuntu-trusty": "dead-beef",
   134  		},
   135  	}
   136  	client := &imageClient{
   137  		raw: raw,
   138  	}
   139  	err := client.EnsureImageExists("trusty", nil, nil)
   140  	c.Assert(err, jc.ErrorIsNil)
   141  	s.Stub.CheckCall(c, 0, "GetAlias", "ubuntu-trusty")
   142  }
   143  
   144  func (s *imageSuite) TestEnsureImageExistsFirstRemote(c *gc.C) {
   145  	connector := MakeConnector(s.Stub, s.remoteWithTrusty)
   146  	raw := &stubClient{
   147  		stub: s.Stub,
   148  		// We don't have the image locally
   149  		Aliases: nil,
   150  	}
   151  	client := &imageClient{
   152  		raw:             raw,
   153  		connectToSource: connector.connectToSource,
   154  	}
   155  	remotes := []Remote{s.remoteWithTrusty.AsRemote()}
   156  	s.Stub.ResetCalls()
   157  	err := client.EnsureImageExists("trusty", remotes, nil)
   158  	c.Assert(err, jc.ErrorIsNil)
   159  	// We didn't find it locally
   160  	s.Stub.CheckCalls(c, []testing.StubCall{
   161  		{ // Check if we already have 'ubuntu-trusty' locally
   162  			FuncName: "GetAlias",
   163  			Args:     []interface{}{"ubuntu-trusty"},
   164  		},
   165  		{ // We didn't so connect to the first remote
   166  			FuncName: "connectToSource",
   167  			Args:     []interface{}{"https://match"},
   168  		},
   169  		{ // And check if it has trusty (which it should)
   170  			FuncName: "GetAlias",
   171  			Args:     []interface{}{"trusty"},
   172  		},
   173  		{ // So Copy the Image
   174  			FuncName: "CopyImage",
   175  			Args:     []interface{}{"deadbeef", []string{"ubuntu-trusty"}},
   176  		},
   177  	})
   178  	// We've updated the aliases
   179  	c.Assert(raw.Aliases, gc.DeepEquals, map[string]string{
   180  		"ubuntu-trusty": "deadbeef",
   181  	})
   182  }
   183  
   184  func (s *imageSuite) TestEnsureImageExistsUnableToConnect(c *gc.C) {
   185  	connector := MakeConnector(s.Stub, s.remoteWithTrusty)
   186  	raw := &stubClient{
   187  		stub: s.Stub,
   188  		// We don't have the image locally
   189  		Aliases: nil,
   190  	}
   191  	client := &imageClient{
   192  		raw:             raw,
   193  		connectToSource: connector.connectToSource,
   194  	}
   195  	badRemote := Remote{
   196  		Host:     "https://nosuch-remote.invalid",
   197  		Protocol: SimplestreamsProtocol,
   198  	}
   199  	s.Stub.ResetCalls()
   200  	s.Stub.SetErrors(nil, errors.Errorf("unable-to-connect"))
   201  	remotes := []Remote{badRemote, s.remoteWithTrusty.AsRemote()}
   202  	err := client.EnsureImageExists("trusty", remotes, nil)
   203  	c.Assert(err, jc.ErrorIsNil)
   204  	// We didn't find it locally
   205  	s.Stub.CheckCalls(c, []testing.StubCall{
   206  		{ // Check if we already have 'ubuntu-trusty' locally
   207  			FuncName: "GetAlias",
   208  			Args:     []interface{}{"ubuntu-trusty"},
   209  		},
   210  		{ // We didn't so connect to the first remote
   211  			FuncName: "connectToSource",
   212  			Args:     []interface{}{"https://nosuch-remote.invalid"},
   213  		},
   214  		{ // Connect failed to first, so connect to second and copy
   215  			FuncName: "connectToSource",
   216  			Args:     []interface{}{"https://match"},
   217  		},
   218  		{ // And check if it has trusty (which it should)
   219  			FuncName: "GetAlias",
   220  			Args:     []interface{}{"trusty"},
   221  		},
   222  		{ // So Copy the Image
   223  			FuncName: "CopyImage",
   224  			Args:     []interface{}{"deadbeef", []string{"ubuntu-trusty"}},
   225  		},
   226  	})
   227  	// We've updated the aliases
   228  	c.Assert(raw.Aliases, gc.DeepEquals, map[string]string{
   229  		"ubuntu-trusty": "deadbeef",
   230  	})
   231  }
   232  
   233  func (s *imageSuite) TestEnsureImageExistsNotPresentInFirstRemote(c *gc.C) {
   234  	connector := MakeConnector(s.Stub, s.remoteWithNothing, s.remoteWithTrusty)
   235  	raw := &stubClient{
   236  		stub: s.Stub,
   237  		// We don't have the image locally
   238  		Aliases: nil,
   239  	}
   240  	client := &imageClient{
   241  		raw:             raw,
   242  		connectToSource: connector.connectToSource,
   243  	}
   244  	s.Stub.ResetCalls()
   245  	remotes := []Remote{s.remoteWithNothing.AsRemote(), s.remoteWithTrusty.AsRemote()}
   246  	err := client.EnsureImageExists("trusty", remotes, nil)
   247  	c.Assert(err, jc.ErrorIsNil)
   248  	// We didn't find it locally
   249  	s.Stub.CheckCalls(c, []testing.StubCall{
   250  		{ // Check if we already have 'ubuntu-trusty' locally
   251  			FuncName: "GetAlias",
   252  			Args:     []interface{}{"ubuntu-trusty"},
   253  		},
   254  		{ // We didn't so connect to the first remote
   255  			FuncName: "connectToSource",
   256  			Args:     []interface{}{s.remoteWithNothing.URL()},
   257  		},
   258  		{ // Lookup the Alias
   259  			FuncName: "GetAlias",
   260  			Args:     []interface{}{"trusty"},
   261  		},
   262  		{ // It wasn't found, so connect to second and look there
   263  			FuncName: "connectToSource",
   264  			Args:     []interface{}{s.remoteWithTrusty.URL()},
   265  		},
   266  		{ // And check if it has trusty (which it should)
   267  			FuncName: "GetAlias",
   268  			Args:     []interface{}{"trusty"},
   269  		},
   270  		{ // So Copy the Image
   271  			FuncName: "CopyImage",
   272  			Args:     []interface{}{"deadbeef", []string{"ubuntu-trusty"}},
   273  		},
   274  	})
   275  	// We've updated the aliases
   276  	c.Assert(raw.Aliases, gc.DeepEquals, map[string]string{
   277  		"ubuntu-trusty": "deadbeef",
   278  	})
   279  }
   280  
   281  func (s *imageSuite) TestEnsureImageExistsCallbackIncludesSourceURL(c *gc.C) {
   282  	calls := make(chan string, 1)
   283  	callback := func(message string) {
   284  		select {
   285  		case calls <- message:
   286  		default:
   287  		}
   288  	}
   289  	connector := MakeConnector(s.Stub, s.remoteWithTrusty)
   290  	raw := &stubClient{
   291  		stub: s.Stub,
   292  		// We don't have the image locally
   293  		Aliases: nil,
   294  	}
   295  	client := &imageClient{
   296  		raw:             raw,
   297  		connectToSource: connector.connectToSource,
   298  	}
   299  	remotes := []Remote{s.remoteWithTrusty.AsRemote()}
   300  	err := client.EnsureImageExists("trusty", remotes, callback)
   301  	c.Assert(err, jc.ErrorIsNil)
   302  	select {
   303  	case message := <-calls:
   304  		c.Check(message, gc.Matches, "copying image for ubuntu-trusty from https://match: \\d+%")
   305  	case <-time.After(coretesting.LongWait):
   306  		// The callbacks are made asynchronously, and so may not
   307  		// have happened by the time EnsureImageExists exits.
   308  		c.Fatalf("no messages received")
   309  	}
   310  }