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 }