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 }