1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
12
"github.com/juju/errors"
13
"github.com/juju/testing"
14
jc "github.com/juju/testing/checkers"
15
gc "gopkg.in/check.v1"
17
coretesting "github.com/juju/juju/testing"
20
type imageSuite struct {
21
testing.IsolationSuite
23
remoteWithTrusty *stubRemoteClient
24
remoteWithNothing *stubRemoteClient
27
var _ = gc.Suite(&imageSuite{})
29
func (s *imageSuite) SetUpTest(c *gc.C) {
30
s.IsolationSuite.SetUpTest(c)
31
s.Stub = &testing.Stub{}
32
s.remoteWithTrusty = &stubRemoteClient{
35
aliases: map[string]string{
39
s.remoteWithNothing = &stubRemoteClient{
41
url: "https://missing",
46
type stubRemoteClient struct {
49
aliases map[string]string
52
var _ remoteClient = (*stubRemoteClient)(nil)
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.
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.
67
return s.aliases[alias]
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 {
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
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)
87
if stubDest, ok := dest.(*stubClient); ok {
88
if stubDest.Aliases == nil {
89
stubDest.Aliases = make(map[string]string)
91
for _, alias := range aliases {
92
stubDest.Aliases[alias] = imageTarget
98
func (s *stubRemoteClient) AsRemote() Remote {
101
Protocol: SimplestreamsProtocol,
105
type stubConnector struct {
107
remoteClients map[string]remoteClient
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
115
return &stubConnector{
117
remoteClients: remoteMap,
121
func (s *stubConnector) connectToSource(remote Remote) (remoteClient, error) {
122
s.stub.AddCall("connectToSource", remote.Host)
123
if err := s.stub.NextErr(); err != nil {
126
return s.remoteClients[remote.Host], nil
129
func (s *imageSuite) TestEnsureImageExistsAlreadyPresent(c *gc.C) {
132
Aliases: map[string]string{
133
"ubuntu-trusty": "dead-beef",
136
client := &imageClient{
139
err := client.EnsureImageExists("trusty", nil, nil)
140
c.Assert(err, jc.ErrorIsNil)
141
s.Stub.CheckCall(c, 0, "GetAlias", "ubuntu-trusty")
144
func (s *imageSuite) TestEnsureImageExistsFirstRemote(c *gc.C) {
145
connector := MakeConnector(s.Stub, s.remoteWithTrusty)
148
// We don't have the image locally
151
client := &imageClient{
153
connectToSource: connector.connectToSource,
155
remotes := []Remote{s.remoteWithTrusty.AsRemote()}
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"},
165
{ // We didn't so connect to the first remote
166
FuncName: "connectToSource",
167
Args: []interface{}{"https://match"},
169
{ // And check if it has trusty (which it should)
170
FuncName: "GetAlias",
171
Args: []interface{}{"trusty"},
173
{ // So Copy the Image
174
FuncName: "CopyImage",
175
Args: []interface{}{"deadbeef", []string{"ubuntu-trusty"}},
178
// We've updated the aliases
179
c.Assert(raw.Aliases, gc.DeepEquals, map[string]string{
180
"ubuntu-trusty": "deadbeef",
184
func (s *imageSuite) TestEnsureImageExistsUnableToConnect(c *gc.C) {
185
connector := MakeConnector(s.Stub, s.remoteWithTrusty)
188
// We don't have the image locally
191
client := &imageClient{
193
connectToSource: connector.connectToSource,
196
Host: "https://nosuch-remote.invalid",
197
Protocol: SimplestreamsProtocol,
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"},
210
{ // We didn't so connect to the first remote
211
FuncName: "connectToSource",
212
Args: []interface{}{"https://nosuch-remote.invalid"},
214
{ // Connect failed to first, so connect to second and copy
215
FuncName: "connectToSource",
216
Args: []interface{}{"https://match"},
218
{ // And check if it has trusty (which it should)
219
FuncName: "GetAlias",
220
Args: []interface{}{"trusty"},
222
{ // So Copy the Image
223
FuncName: "CopyImage",
224
Args: []interface{}{"deadbeef", []string{"ubuntu-trusty"}},
227
// We've updated the aliases
228
c.Assert(raw.Aliases, gc.DeepEquals, map[string]string{
229
"ubuntu-trusty": "deadbeef",
233
func (s *imageSuite) TestEnsureImageExistsNotPresentInFirstRemote(c *gc.C) {
234
connector := MakeConnector(s.Stub, s.remoteWithNothing, s.remoteWithTrusty)
237
// We don't have the image locally
240
client := &imageClient{
242
connectToSource: connector.connectToSource,
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"},
254
{ // We didn't so connect to the first remote
255
FuncName: "connectToSource",
256
Args: []interface{}{s.remoteWithNothing.URL()},
258
{ // Lookup the Alias
259
FuncName: "GetAlias",
260
Args: []interface{}{"trusty"},
262
{ // It wasn't found, so connect to second and look there
263
FuncName: "connectToSource",
264
Args: []interface{}{s.remoteWithTrusty.URL()},
266
{ // And check if it has trusty (which it should)
267
FuncName: "GetAlias",
268
Args: []interface{}{"trusty"},
270
{ // So Copy the Image
271
FuncName: "CopyImage",
272
Args: []interface{}{"deadbeef", []string{"ubuntu-trusty"}},
275
// We've updated the aliases
276
c.Assert(raw.Aliases, gc.DeepEquals, map[string]string{
277
"ubuntu-trusty": "deadbeef",
281
func (s *imageSuite) TestEnsureImageExistsCallbackIncludesSourceURL(c *gc.C) {
282
calls := make(chan string, 1)
283
callback := func(message string) {
285
case calls <- message:
289
connector := MakeConnector(s.Stub, s.remoteWithTrusty)
292
// We don't have the image locally
295
client := &imageClient{
297
connectToSource: connector.connectToSource,
299
remotes := []Remote{s.remoteWithTrusty.AsRemote()}
300
err := client.EnsureImageExists("trusty", remotes, callback)
301
c.Assert(err, jc.ErrorIsNil)
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")