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