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