github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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": "deadbeef", 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 s.Stub.CheckCall(c, 0, "GetAlias", "ubuntu-trusty") 142 } 143 144 func (s *imageSuite) TestEnsureImageExistsFirstRemote(c *gc.C) { 145 connector := MakeConnector(s.Stub, s.remoteWithTrusty) 146 raw := &stubClient{ 147 stub: s.Stub, 148 // We don't have the image locally 149 Aliases: nil, 150 } 151 client := &imageClient{ 152 raw: raw, 153 connectToSource: connector.connectToSource, 154 } 155 remotes := []Remote{s.remoteWithTrusty.AsRemote()} 156 s.Stub.ResetCalls() 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"}, 164 }, 165 { // We didn't so connect to the first remote 166 FuncName: "connectToSource", 167 Args: []interface{}{"https://match"}, 168 }, 169 { // And check if it has trusty (which it should) 170 FuncName: "GetAlias", 171 Args: []interface{}{"trusty"}, 172 }, 173 { // So Copy the Image 174 FuncName: "CopyImage", 175 Args: []interface{}{"deadbeef", []string{"ubuntu-trusty"}}, 176 }, 177 }) 178 // We've updated the aliases 179 c.Assert(raw.Aliases, gc.DeepEquals, map[string]string{ 180 "ubuntu-trusty": "deadbeef", 181 }) 182 } 183 184 func (s *imageSuite) TestEnsureImageExistsUnableToConnect(c *gc.C) { 185 connector := MakeConnector(s.Stub, s.remoteWithTrusty) 186 raw := &stubClient{ 187 stub: s.Stub, 188 // We don't have the image locally 189 Aliases: nil, 190 } 191 client := &imageClient{ 192 raw: raw, 193 connectToSource: connector.connectToSource, 194 } 195 badRemote := Remote{ 196 Host: "https://nosuch-remote.invalid", 197 Protocol: SimplestreamsProtocol, 198 } 199 s.Stub.ResetCalls() 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"}, 209 }, 210 { // We didn't so connect to the first remote 211 FuncName: "connectToSource", 212 Args: []interface{}{"https://nosuch-remote.invalid"}, 213 }, 214 { // Connect failed to first, so connect to second and copy 215 FuncName: "connectToSource", 216 Args: []interface{}{"https://match"}, 217 }, 218 { // And check if it has trusty (which it should) 219 FuncName: "GetAlias", 220 Args: []interface{}{"trusty"}, 221 }, 222 { // So Copy the Image 223 FuncName: "CopyImage", 224 Args: []interface{}{"deadbeef", []string{"ubuntu-trusty"}}, 225 }, 226 }) 227 // We've updated the aliases 228 c.Assert(raw.Aliases, gc.DeepEquals, map[string]string{ 229 "ubuntu-trusty": "deadbeef", 230 }) 231 } 232 233 func (s *imageSuite) TestEnsureImageExistsNotPresentInFirstRemote(c *gc.C) { 234 connector := MakeConnector(s.Stub, s.remoteWithNothing, s.remoteWithTrusty) 235 raw := &stubClient{ 236 stub: s.Stub, 237 // We don't have the image locally 238 Aliases: nil, 239 } 240 client := &imageClient{ 241 raw: raw, 242 connectToSource: connector.connectToSource, 243 } 244 s.Stub.ResetCalls() 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"}, 253 }, 254 { // We didn't so connect to the first remote 255 FuncName: "connectToSource", 256 Args: []interface{}{s.remoteWithNothing.URL()}, 257 }, 258 { // Lookup the Alias 259 FuncName: "GetAlias", 260 Args: []interface{}{"trusty"}, 261 }, 262 { // It wasn't found, so connect to second and look there 263 FuncName: "connectToSource", 264 Args: []interface{}{s.remoteWithTrusty.URL()}, 265 }, 266 { // And check if it has trusty (which it should) 267 FuncName: "GetAlias", 268 Args: []interface{}{"trusty"}, 269 }, 270 { // So Copy the Image 271 FuncName: "CopyImage", 272 Args: []interface{}{"deadbeef", []string{"ubuntu-trusty"}}, 273 }, 274 }) 275 // We've updated the aliases 276 c.Assert(raw.Aliases, gc.DeepEquals, map[string]string{ 277 "ubuntu-trusty": "deadbeef", 278 }) 279 } 280 281 func (s *imageSuite) TestEnsureImageExistsCallbackIncludesSourceURL(c *gc.C) { 282 calls := make(chan string, 1) 283 callback := func(message string) { 284 select { 285 case calls <- message: 286 default: 287 } 288 } 289 connector := MakeConnector(s.Stub, s.remoteWithTrusty) 290 raw := &stubClient{ 291 stub: s.Stub, 292 // We don't have the image locally 293 Aliases: nil, 294 } 295 client := &imageClient{ 296 raw: raw, 297 connectToSource: connector.connectToSource, 298 } 299 remotes := []Remote{s.remoteWithTrusty.AsRemote()} 300 err := client.EnsureImageExists("trusty", remotes, callback) 301 c.Assert(err, jc.ErrorIsNil) 302 select { 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") 309 } 310 }