github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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": "trusty-alias",
    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  	connector := MakeConnector(s.Stub, s.remoteWithTrusty)
   131  	raw := &stubClient{
   132  		stub: s.Stub,
   133  		Aliases: map[string]string{
   134  			"ubuntu-trusty": "dead-beef",
   135  		},
   136  	}
   137  	client := &imageClient{
   138  		raw:             raw,
   139  		connectToSource: connector.connectToSource,
   140  	}
   141  	err := client.EnsureImageExists("trusty", []Remote{s.remoteWithTrusty.AsRemote()}, nil)
   142  	c.Assert(err, jc.ErrorIsNil)
   143  }
   144  
   145  func (s *imageSuite) TestEnsureImageExistsFirstRemote(c *gc.C) {
   146  	connector := MakeConnector(s.Stub, s.remoteWithTrusty)
   147  	raw := &stubClient{
   148  		stub: s.Stub,
   149  		// We don't have the image locally
   150  		Aliases: nil,
   151  	}
   152  	client := &imageClient{
   153  		raw:             raw,
   154  		connectToSource: connector.connectToSource,
   155  	}
   156  	remotes := []Remote{s.remoteWithTrusty.AsRemote()}
   157  	s.Stub.ResetCalls()
   158  	err := client.EnsureImageExists("trusty", remotes, nil)
   159  	c.Assert(err, jc.ErrorIsNil)
   160  	// We didn't find it locally
   161  	s.Stub.CheckCalls(c, []testing.StubCall{
   162  		{ // We didn't so connect to the first remote
   163  			FuncName: "connectToSource",
   164  			Args:     []interface{}{"https://match"},
   165  		},
   166  		{ // And check if it has trusty (which it should)
   167  			FuncName: "GetAlias",
   168  			Args:     []interface{}{"trusty"},
   169  		},
   170  		{ // So Copy the Image
   171  			FuncName: "CopyImage",
   172  			Args:     []interface{}{"trusty", []string{"ubuntu-trusty"}},
   173  		},
   174  	})
   175  	// We've updated the aliases
   176  	c.Assert(raw.Aliases, gc.DeepEquals, map[string]string{
   177  		"ubuntu-trusty": "trusty",
   178  	})
   179  }
   180  
   181  func (s *imageSuite) TestEnsureImageExistsUnableToConnect(c *gc.C) {
   182  	connector := MakeConnector(s.Stub, s.remoteWithTrusty)
   183  	raw := &stubClient{
   184  		stub: s.Stub,
   185  		// We don't have the image locally
   186  		Aliases: nil,
   187  	}
   188  	client := &imageClient{
   189  		raw:             raw,
   190  		connectToSource: connector.connectToSource,
   191  	}
   192  	badRemote := Remote{
   193  		Host:     "https://nosuch-remote.invalid",
   194  		Protocol: SimplestreamsProtocol,
   195  	}
   196  	s.Stub.ResetCalls()
   197  	s.Stub.SetErrors(errors.Errorf("unable-to-connect"))
   198  	remotes := []Remote{badRemote, s.remoteWithTrusty.AsRemote()}
   199  	err := client.EnsureImageExists("trusty", remotes, nil)
   200  	c.Assert(err, jc.ErrorIsNil)
   201  	// We didn't find it locally
   202  	s.Stub.CheckCalls(c, []testing.StubCall{
   203  		{ // We didn't so connect to the first remote
   204  			FuncName: "connectToSource",
   205  			Args:     []interface{}{"https://nosuch-remote.invalid"},
   206  		},
   207  		{ // Connect failed to first, so connect to second and copy
   208  			FuncName: "connectToSource",
   209  			Args:     []interface{}{"https://match"},
   210  		},
   211  		{ // And check if it has trusty (which it should)
   212  			FuncName: "GetAlias",
   213  			Args:     []interface{}{"trusty"},
   214  		},
   215  		{ // So Copy the Image
   216  			FuncName: "CopyImage",
   217  			Args:     []interface{}{"trusty", []string{"ubuntu-trusty"}},
   218  		},
   219  	})
   220  	// We've updated the aliases
   221  	c.Assert(raw.Aliases, gc.DeepEquals, map[string]string{
   222  		"ubuntu-trusty": "trusty",
   223  	})
   224  }
   225  
   226  func (s *imageSuite) TestEnsureImageExistsNotPresentInFirstRemote(c *gc.C) {
   227  	connector := MakeConnector(s.Stub, s.remoteWithNothing, s.remoteWithTrusty)
   228  	raw := &stubClient{
   229  		stub: s.Stub,
   230  		// We don't have the image locally
   231  		Aliases: nil,
   232  	}
   233  	client := &imageClient{
   234  		raw:             raw,
   235  		connectToSource: connector.connectToSource,
   236  	}
   237  	s.Stub.ResetCalls()
   238  	remotes := []Remote{s.remoteWithNothing.AsRemote(), s.remoteWithTrusty.AsRemote()}
   239  	err := client.EnsureImageExists("trusty", remotes, nil)
   240  	c.Assert(err, jc.ErrorIsNil)
   241  	// We didn't find it locally
   242  	s.Stub.CheckCalls(c, []testing.StubCall{
   243  		{ // We didn't so connect to the first remote
   244  			FuncName: "connectToSource",
   245  			Args:     []interface{}{s.remoteWithNothing.URL()},
   246  		},
   247  		{ // Lookup the Alias
   248  			FuncName: "GetAlias",
   249  			Args:     []interface{}{"trusty"},
   250  		},
   251  		{ // It wasn't found, so connect to second and look there
   252  			FuncName: "connectToSource",
   253  			Args:     []interface{}{s.remoteWithTrusty.URL()},
   254  		},
   255  		{ // And check if it has trusty (which it should)
   256  			FuncName: "GetAlias",
   257  			Args:     []interface{}{"trusty"},
   258  		},
   259  		{ // So Copy the Image
   260  			FuncName: "CopyImage",
   261  			Args:     []interface{}{"trusty", []string{"ubuntu-trusty"}},
   262  		},
   263  	})
   264  	// We've updated the aliases
   265  	c.Assert(raw.Aliases, gc.DeepEquals, map[string]string{
   266  		"ubuntu-trusty": "trusty",
   267  	})
   268  }
   269  
   270  func (s *imageSuite) TestEnsureImageExistsCallbackIncludesSourceURL(c *gc.C) {
   271  	calls := make(chan string, 1)
   272  	callback := func(message string) {
   273  		select {
   274  		case calls <- message:
   275  		default:
   276  		}
   277  	}
   278  	connector := MakeConnector(s.Stub, s.remoteWithTrusty)
   279  	raw := &stubClient{
   280  		stub: s.Stub,
   281  		// We don't have the image locally
   282  		Aliases: nil,
   283  	}
   284  	client := &imageClient{
   285  		raw:             raw,
   286  		connectToSource: connector.connectToSource,
   287  	}
   288  	remotes := []Remote{s.remoteWithTrusty.AsRemote()}
   289  	err := client.EnsureImageExists("trusty", remotes, callback)
   290  	c.Assert(err, jc.ErrorIsNil)
   291  	select {
   292  	case message := <-calls:
   293  		c.Check(message, gc.Matches, "copying image for ubuntu-trusty from https://match: \\d+%")
   294  	case <-time.After(coretesting.LongWait):
   295  		// The callbacks are made asynchronously, and so may not
   296  		// have happened by the time EnsureImageExists exits.
   297  		c.Fatalf("no messages received")
   298  	}
   299  }