github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/lxd/server_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lxd_test 5 6 import ( 7 "net" 8 "net/url" 9 "os" 10 "syscall" 11 12 "github.com/golang/mock/gomock" 13 "github.com/juju/errors" 14 "github.com/juju/juju/cloud" 15 "github.com/juju/juju/environs" 16 client "github.com/lxc/lxd/client" 17 "github.com/lxc/lxd/shared/api" 18 gc "gopkg.in/check.v1" 19 20 containerLXD "github.com/juju/juju/container/lxd" 21 "github.com/juju/juju/provider/lxd" 22 ) 23 24 var ( 25 _ = gc.Suite(&serverSuite{}) 26 ) 27 28 type serverSuite struct{} 29 30 func (s *serverSuite) TestLocalServer(c *gc.C) { 31 ctrl := gomock.NewController(c) 32 defer ctrl.Finish() 33 34 profile := &api.Profile{} 35 etag := "etag" 36 bridgeName := "lxdbr0" 37 hostAddress := "192.168.0.1" 38 connectionInfo := &client.ConnectionInfo{ 39 Addresses: []string{ 40 "https://192.168.0.1:8443", 41 }, 42 } 43 serverInfo := &api.Server{ 44 ServerUntrusted: api.ServerUntrusted{ 45 APIVersion: "1.1", 46 }, 47 } 48 49 factory, server, interfaceAddr := s.newLocalServerFactory(ctrl) 50 51 gomock.InOrder( 52 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 53 server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil), 54 server.EXPECT().EnableHTTPSListener().Return(nil), 55 server.EXPECT().LocalBridgeName().Return(bridgeName), 56 interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil), 57 server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil), 58 server.EXPECT().StorageSupported().Return(true), 59 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 60 server.EXPECT().EnsureDefaultStorage(profile, etag).Return(nil), 61 server.EXPECT().GetServer().Return(serverInfo, etag, nil), 62 ) 63 64 svr, err := factory.LocalServer() 65 c.Assert(svr, gc.Not(gc.IsNil)) 66 c.Assert(svr, gc.Equals, server) 67 c.Assert(err, gc.IsNil) 68 } 69 70 func (s *serverSuite) TestLocalServerRetrySemantics(c *gc.C) { 71 ctrl := gomock.NewController(c) 72 defer ctrl.Finish() 73 74 profile := &api.Profile{} 75 etag := "etag" 76 bridgeName := "lxdbr0" 77 hostAddress := "192.168.0.1" 78 emptyConnectionInfo := &client.ConnectionInfo{ 79 Addresses: []string{}, 80 } 81 connectionInfo := &client.ConnectionInfo{ 82 Addresses: []string{ 83 "https://192.168.0.1:8443", 84 }, 85 } 86 serverInfo := &api.Server{ 87 ServerUntrusted: api.ServerUntrusted{ 88 APIVersion: "1.1", 89 }, 90 } 91 92 factory, server, interfaceAddr := s.newLocalServerFactory(ctrl) 93 94 gomock.InOrder( 95 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 96 server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil), 97 server.EXPECT().EnableHTTPSListener().Return(nil), 98 server.EXPECT().LocalBridgeName().Return(bridgeName), 99 interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil), 100 server.EXPECT().GetConnectionInfo().Return(emptyConnectionInfo, nil), 101 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 102 server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil), 103 server.EXPECT().EnableHTTPSListener().Return(nil), 104 server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil), 105 server.EXPECT().StorageSupported().Return(true), 106 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 107 server.EXPECT().EnsureDefaultStorage(profile, etag).Return(nil), 108 server.EXPECT().GetServer().Return(serverInfo, etag, nil), 109 ) 110 111 svr, err := factory.LocalServer() 112 c.Assert(svr, gc.Not(gc.IsNil)) 113 c.Assert(svr, gc.Equals, server) 114 c.Assert(err, gc.IsNil) 115 } 116 117 func (s *serverSuite) TestLocalServerRetrySemanticsFailure(c *gc.C) { 118 ctrl := gomock.NewController(c) 119 defer ctrl.Finish() 120 121 profile := &api.Profile{} 122 etag := "etag" 123 bridgeName := "lxdbr0" 124 hostAddress := "192.168.0.1" 125 emptyConnectionInfo := &client.ConnectionInfo{ 126 Addresses: []string{}, 127 } 128 129 factory, server, interfaceAddr := s.newLocalServerFactory(ctrl) 130 131 server.EXPECT().GetProfile("default").Return(profile, etag, nil).Times(31) 132 server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil).Times(31) 133 server.EXPECT().EnableHTTPSListener().Return(nil).Times(31) 134 server.EXPECT().LocalBridgeName().Return(bridgeName) 135 interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil) 136 server.EXPECT().GetConnectionInfo().Return(emptyConnectionInfo, nil).Times(30) 137 138 svr, err := factory.LocalServer() 139 c.Assert(svr, gc.IsNil) 140 c.Assert(err.Error(), gc.Equals, "LXD is not listening on address https://192.168.0.1 (reported addresses: [])") 141 } 142 143 func (s *serverSuite) TestLocalServerWithInvalidAPIVersion(c *gc.C) { 144 ctrl := gomock.NewController(c) 145 defer ctrl.Finish() 146 147 profile := &api.Profile{} 148 etag := "etag" 149 bridgeName := "lxdbr0" 150 hostAddress := "192.168.0.1" 151 connectionInfo := &client.ConnectionInfo{ 152 Addresses: []string{ 153 "https://192.168.0.1:8443", 154 }, 155 } 156 serverInfo := &api.Server{ 157 ServerUntrusted: api.ServerUntrusted{ 158 APIVersion: "a.b", 159 }, 160 } 161 162 factory, server, interfaceAddr := s.newLocalServerFactory(ctrl) 163 164 gomock.InOrder( 165 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 166 server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil), 167 server.EXPECT().EnableHTTPSListener().Return(nil), 168 server.EXPECT().LocalBridgeName().Return(bridgeName), 169 interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil), 170 server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil), 171 server.EXPECT().StorageSupported().Return(true), 172 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 173 server.EXPECT().EnsureDefaultStorage(profile, etag).Return(nil), 174 server.EXPECT().GetServer().Return(serverInfo, etag, nil), 175 ) 176 177 svr, err := factory.LocalServer() 178 c.Assert(svr, gc.Not(gc.IsNil)) 179 c.Assert(svr, gc.Equals, server) 180 c.Assert(err, gc.IsNil) 181 } 182 183 func (s *serverSuite) TestLocalServerErrorMessageShowsInstallMessage(c *gc.C) { 184 ctrl := gomock.NewController(c) 185 defer ctrl.Finish() 186 187 factory := lxd.NewServerFactoryWithMocks( 188 func() (lxd.Server, error) { 189 return nil, errors.New("bad") 190 }, 191 defaultRemoteServerFunc(ctrl), 192 nil, 193 &lxd.MockClock{}, 194 ) 195 196 _, err := factory.LocalServer() 197 c.Assert(errors.Cause(err).Error(), gc.Equals, `bad 198 199 Please install LXD by running: 200 $ sudo snap install lxd 201 and then configure it with: 202 $ newgrp lxd 203 $ lxd init 204 `) 205 } 206 207 func (s *serverSuite) TestLocalServerErrorMessageShowsConfigureMessage(c *gc.C) { 208 ctrl := gomock.NewController(c) 209 defer ctrl.Finish() 210 211 factory := lxd.NewServerFactoryWithMocks( 212 func() (lxd.Server, error) { 213 return nil, errors.Annotatef(&url.Error{ 214 Err: &net.OpError{ 215 Op: "dial", 216 Net: "unix", 217 Err: &os.SyscallError{ 218 Err: syscall.ECONNREFUSED, 219 }, 220 }, 221 }, "bad") 222 }, 223 defaultRemoteServerFunc(ctrl), 224 nil, 225 &lxd.MockClock{}, 226 ) 227 228 _, err := factory.LocalServer() 229 c.Assert(errors.Cause(err).Error(), gc.Equals, `LXD refused connections; is LXD running? 230 231 Please configure LXD by running: 232 $ newgrp lxd 233 $ lxd init 234 `) 235 } 236 237 func (s *serverSuite) TestLocalServerErrorMessageShowsConfigureMessageWhenEACCES(c *gc.C) { 238 ctrl := gomock.NewController(c) 239 defer ctrl.Finish() 240 241 factory := lxd.NewServerFactoryWithMocks( 242 func() (lxd.Server, error) { 243 return nil, errors.Annotatef(&url.Error{ 244 Err: &net.OpError{ 245 Op: "dial", 246 Net: "unix", 247 Err: &os.SyscallError{ 248 Err: syscall.EACCES, 249 }, 250 }, 251 }, "bad") 252 }, 253 defaultRemoteServerFunc(ctrl), 254 nil, 255 &lxd.MockClock{}, 256 ) 257 258 _, err := factory.LocalServer() 259 c.Assert(errors.Cause(err).Error(), gc.Equals, `Permission denied, are you in the lxd group? 260 261 Please configure LXD by running: 262 $ newgrp lxd 263 $ lxd init 264 `) 265 } 266 267 func (s *serverSuite) TestLocalServerErrorMessageShowsInstallMessageWhenENOENT(c *gc.C) { 268 ctrl := gomock.NewController(c) 269 defer ctrl.Finish() 270 271 factory := lxd.NewServerFactoryWithMocks( 272 func() (lxd.Server, error) { 273 return nil, errors.Annotatef(&url.Error{ 274 Err: &net.OpError{ 275 Op: "dial", 276 Net: "unix", 277 Err: &os.SyscallError{ 278 Err: syscall.ENOENT, 279 }, 280 }, 281 }, "bad") 282 }, 283 defaultRemoteServerFunc(ctrl), 284 nil, 285 &lxd.MockClock{}, 286 ) 287 288 _, err := factory.LocalServer() 289 c.Assert(errors.Cause(err).Error(), gc.Equals, `LXD socket not found; is LXD installed & running? 290 291 Please install LXD by running: 292 $ sudo snap install lxd 293 and then configure it with: 294 $ newgrp lxd 295 $ lxd init 296 `) 297 } 298 299 func (s *serverSuite) TestLocalServerWithStorageNotSupported(c *gc.C) { 300 ctrl := gomock.NewController(c) 301 defer ctrl.Finish() 302 303 profile := &api.Profile{} 304 etag := "etag" 305 bridgeName := "lxdbr0" 306 hostAddress := "192.168.0.1" 307 connectionInfo := &client.ConnectionInfo{ 308 Addresses: []string{ 309 "https://192.168.0.1:8443", 310 }, 311 } 312 serverInfo := &api.Server{ 313 ServerUntrusted: api.ServerUntrusted{ 314 APIVersion: "2.2", 315 }, 316 } 317 318 factory, server, interfaceAddr := s.newLocalServerFactory(ctrl) 319 320 gomock.InOrder( 321 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 322 server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil), 323 server.EXPECT().EnableHTTPSListener().Return(nil), 324 server.EXPECT().LocalBridgeName().Return(bridgeName), 325 interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil), 326 server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil), 327 server.EXPECT().StorageSupported().Return(false), 328 server.EXPECT().GetServer().Return(serverInfo, etag, nil), 329 ) 330 331 svr, err := factory.RemoteServer(environs.CloudSpec{ 332 Endpoint: "", 333 }) 334 c.Assert(svr, gc.Not(gc.IsNil)) 335 c.Assert(err, gc.IsNil) 336 } 337 338 func (s *serverSuite) TestRemoteServerWithEmptyEndpointYieldsLocalServer(c *gc.C) { 339 ctrl := gomock.NewController(c) 340 defer ctrl.Finish() 341 342 profile := &api.Profile{} 343 etag := "etag" 344 bridgeName := "lxdbr0" 345 hostAddress := "192.168.0.1" 346 connectionInfo := &client.ConnectionInfo{ 347 Addresses: []string{ 348 "https://192.168.0.1:8443", 349 }, 350 } 351 serverInfo := &api.Server{ 352 ServerUntrusted: api.ServerUntrusted{ 353 APIVersion: "1.1", 354 }, 355 } 356 357 factory, server, interfaceAddr := s.newLocalServerFactory(ctrl) 358 359 gomock.InOrder( 360 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 361 server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil), 362 server.EXPECT().EnableHTTPSListener().Return(nil), 363 server.EXPECT().LocalBridgeName().Return(bridgeName), 364 interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil), 365 server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil), 366 server.EXPECT().StorageSupported().Return(true), 367 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 368 server.EXPECT().EnsureDefaultStorage(profile, etag).Return(nil), 369 server.EXPECT().GetServer().Return(serverInfo, etag, nil), 370 ) 371 372 svr, err := factory.RemoteServer(environs.CloudSpec{ 373 Endpoint: "", 374 }) 375 c.Assert(svr, gc.Not(gc.IsNil)) 376 c.Assert(err, gc.IsNil) 377 } 378 379 func (s *serverSuite) TestRemoteServer(c *gc.C) { 380 ctrl := gomock.NewController(c) 381 defer ctrl.Finish() 382 383 profile := &api.Profile{} 384 etag := "etag" 385 serverInfo := &api.Server{ 386 ServerUntrusted: api.ServerUntrusted{ 387 APIVersion: "1.1", 388 }, 389 } 390 391 factory, server := s.newRemoteServerFactory(ctrl) 392 393 gomock.InOrder( 394 server.EXPECT().StorageSupported().Return(true), 395 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 396 server.EXPECT().EnsureDefaultStorage(profile, etag).Return(nil), 397 server.EXPECT().GetServer().Return(serverInfo, etag, nil), 398 ) 399 400 creds := cloud.NewCredential("any", map[string]string{ 401 "client-cert": "client-cert", 402 "client-key": "client-key", 403 "server-cert": "server-cert", 404 }) 405 svr, err := factory.RemoteServer(environs.CloudSpec{ 406 Endpoint: "https://10.0.0.9:8443", 407 Credential: &creds, 408 }) 409 c.Assert(svr, gc.Not(gc.IsNil)) 410 c.Assert(svr, gc.Equals, server) 411 c.Assert(err, gc.IsNil) 412 } 413 414 func (s *serverSuite) TestRemoteServerWithNoStorage(c *gc.C) { 415 ctrl := gomock.NewController(c) 416 defer ctrl.Finish() 417 418 etag := "etag" 419 serverInfo := &api.Server{ 420 ServerUntrusted: api.ServerUntrusted{ 421 APIVersion: "1.1", 422 }, 423 } 424 425 factory, server := s.newRemoteServerFactory(ctrl) 426 427 gomock.InOrder( 428 server.EXPECT().StorageSupported().Return(false), 429 server.EXPECT().GetServer().Return(serverInfo, etag, nil), 430 ) 431 432 creds := cloud.NewCredential("any", map[string]string{ 433 "client-cert": "client-cert", 434 "client-key": "client-key", 435 "server-cert": "server-cert", 436 }) 437 svr, err := factory.RemoteServer(environs.CloudSpec{ 438 Endpoint: "https://10.0.0.9:8443", 439 Credential: &creds, 440 }) 441 c.Assert(svr, gc.Not(gc.IsNil)) 442 c.Assert(svr, gc.Equals, server) 443 c.Assert(err, gc.IsNil) 444 } 445 446 func (s *serverSuite) TestRemoteServerMissingCertificates(c *gc.C) { 447 ctrl := gomock.NewController(c) 448 defer ctrl.Finish() 449 450 factory, _ := s.newRemoteServerFactory(ctrl) 451 452 creds := cloud.NewCredential("any", map[string]string{}) 453 svr, err := factory.RemoteServer(environs.CloudSpec{ 454 Endpoint: "https://10.0.0.9:8443", 455 Credential: &creds, 456 }) 457 c.Assert(svr, gc.IsNil) 458 c.Assert(errors.Cause(err).Error(), gc.Equals, "credentials not valid") 459 } 460 461 func (s *serverSuite) TestRemoteServerWithGetServerError(c *gc.C) { 462 ctrl := gomock.NewController(c) 463 defer ctrl.Finish() 464 465 factory, server := s.newRemoteServerFactory(ctrl) 466 467 gomock.InOrder( 468 server.EXPECT().StorageSupported().Return(false), 469 server.EXPECT().GetServer().Return(nil, "", errors.New("bad")), 470 ) 471 472 creds := cloud.NewCredential("any", map[string]string{ 473 "client-cert": "client-cert", 474 "client-key": "client-key", 475 "server-cert": "server-cert", 476 }) 477 _, err := factory.RemoteServer(environs.CloudSpec{ 478 Endpoint: "https://10.0.0.9:8443", 479 Credential: &creds, 480 }) 481 c.Assert(errors.Cause(err).Error(), gc.Equals, "bad") 482 } 483 484 func (s *serverSuite) newLocalServerFactory(ctrl *gomock.Controller) (lxd.ServerFactory, *lxd.MockServer, *lxd.MockInterfaceAddress) { 485 server := lxd.NewMockServer(ctrl) 486 interfaceAddr := lxd.NewMockInterfaceAddress(ctrl) 487 488 factory := lxd.NewServerFactoryWithMocks( 489 func() (lxd.Server, error) { 490 return server, nil 491 }, 492 defaultRemoteServerFunc(ctrl), 493 interfaceAddr, 494 &lxd.MockClock{}, 495 ) 496 497 return factory, server, interfaceAddr 498 } 499 500 func (s *serverSuite) newRemoteServerFactory(ctrl *gomock.Controller) (lxd.ServerFactory, *lxd.MockServer) { 501 server := lxd.NewMockServer(ctrl) 502 interfaceAddr := lxd.NewMockInterfaceAddress(ctrl) 503 504 return lxd.NewServerFactoryWithMocks( 505 defaultLocalServerFunc(ctrl), 506 func(spec containerLXD.ServerSpec) (lxd.Server, error) { 507 return server, nil 508 }, 509 interfaceAddr, 510 &lxd.MockClock{}, 511 ), server 512 } 513 514 func defaultLocalServerFunc(ctrl *gomock.Controller) func() (lxd.Server, error) { 515 return func() (lxd.Server, error) { 516 return lxd.NewMockServer(ctrl), nil 517 } 518 } 519 520 func defaultRemoteServerFunc(ctrl *gomock.Controller) func(containerLXD.ServerSpec) (lxd.Server, error) { 521 return func(containerLXD.ServerSpec) (lxd.Server, error) { 522 return lxd.NewMockServer(ctrl), nil 523 } 524 } 525 526 func (s *serverSuite) TestIsSupportedAPIVersion(c *gc.C) { 527 for _, t := range []struct { 528 input string 529 expected bool 530 output string 531 }{ 532 { 533 input: "foo", 534 expected: false, 535 output: `LXD API version "foo": expected format <major>\.<minor>`, 536 }, 537 { 538 input: "a.b", 539 expected: false, 540 output: `LXD API version "a.b": unexpected major number: strconv.(ParseInt|Atoi): parsing "a": invalid syntax`, 541 }, 542 { 543 input: "0.9", 544 expected: false, 545 output: `LXD API version "0.9": expected major version 1 or later`, 546 }, 547 { 548 input: "1.0", 549 expected: true, 550 output: "", 551 }, 552 { 553 input: "2.0", 554 expected: true, 555 output: "", 556 }, 557 { 558 input: "2.1", 559 expected: true, 560 output: "", 561 }, 562 } { 563 msg, ok := lxd.IsSupportedAPIVersion(t.input) 564 c.Assert(ok, gc.Equals, t.expected) 565 c.Assert(msg, gc.Matches, t.output) 566 } 567 }