github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/provider/azure/instance_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package azure 5 6 import ( 7 "encoding/base64" 8 "fmt" 9 "net/http" 10 11 gc "launchpad.net/gocheck" 12 "launchpad.net/gwacl" 13 14 "launchpad.net/juju-core/instance" 15 jc "launchpad.net/juju-core/testing/checkers" 16 ) 17 18 type instanceSuite struct{} 19 20 var _ = gc.Suite(&instanceSuite{}) 21 22 // makeHostedServiceDescriptor creates a HostedServiceDescriptor with the 23 // given service name. 24 func makeHostedServiceDescriptor(name string) *gwacl.HostedServiceDescriptor { 25 labelBase64 := base64.StdEncoding.EncodeToString([]byte("label")) 26 return &gwacl.HostedServiceDescriptor{ServiceName: name, Label: labelBase64} 27 } 28 29 func (*instanceSuite) TestId(c *gc.C) { 30 serviceName := "test-name" 31 testService := makeHostedServiceDescriptor(serviceName) 32 azInstance := azureInstance{*testService, nil} 33 c.Check(azInstance.Id(), gc.Equals, instance.Id(serviceName)) 34 } 35 36 func (*instanceSuite) TestStatus(c *gc.C) { 37 serviceName := "test-name" 38 testService := makeHostedServiceDescriptor(serviceName) 39 testService.Status = "something" 40 azInstance := azureInstance{*testService, nil} 41 c.Check(azInstance.Status(), gc.Equals, testService.Status) 42 } 43 44 func (*instanceSuite) TestDNSName(c *gc.C) { 45 // An instance's DNS name is computed from its hosted-service name. 46 host := "hostname" 47 testService := makeHostedServiceDescriptor(host) 48 azInstance := azureInstance{*testService, nil} 49 dnsName, err := azInstance.DNSName() 50 c.Assert(err, gc.IsNil) 51 c.Check(dnsName, gc.Equals, host+"."+AZURE_DOMAIN_NAME) 52 } 53 54 func (*instanceSuite) TestWaitDNSName(c *gc.C) { 55 // An Azure instance gets its DNS name immediately, so there's no 56 // waiting involved. 57 host := "hostname" 58 testService := makeHostedServiceDescriptor(host) 59 azInstance := azureInstance{*testService, nil} 60 dnsName, err := azInstance.WaitDNSName() 61 c.Assert(err, gc.IsNil) 62 c.Check(dnsName, gc.Equals, host+"."+AZURE_DOMAIN_NAME) 63 } 64 65 func makeRole(name string, endpoints ...gwacl.InputEndpoint) gwacl.Role { 66 return gwacl.Role{ 67 RoleName: name, 68 ConfigurationSets: []gwacl.ConfigurationSet{ 69 { 70 ConfigurationSetType: gwacl.CONFIG_SET_NETWORK, 71 InputEndpoints: &endpoints, 72 }, 73 }, 74 } 75 } 76 77 func makeDeployment(name string, roles ...gwacl.Role) gwacl.Deployment { 78 return gwacl.Deployment{ 79 Name: name, 80 RoleList: roles, 81 } 82 } 83 84 func makeInputEndpoint(port int, protocol string) gwacl.InputEndpoint { 85 return gwacl.InputEndpoint{ 86 LocalPort: port, 87 Name: fmt.Sprintf("%s%d", protocol, port), 88 Port: port, 89 Protocol: protocol, 90 } 91 } 92 93 func serialize(c *gc.C, object gwacl.AzureObject) []byte { 94 xml, err := object.Serialize() 95 c.Assert(err, gc.IsNil) 96 return []byte(xml) 97 } 98 99 func prepareDeploymentInfoResponse( 100 c *gc.C, dep gwacl.Deployment) []gwacl.DispatcherResponse { 101 return []gwacl.DispatcherResponse{ 102 gwacl.NewDispatcherResponse( 103 serialize(c, &dep), http.StatusOK, nil), 104 } 105 } 106 107 func preparePortChangeConversation( 108 c *gc.C, service *gwacl.HostedServiceDescriptor, 109 deployments ...gwacl.Deployment) []gwacl.DispatcherResponse { 110 // Construct the series of responses to expected requests. 111 responses := []gwacl.DispatcherResponse{ 112 // First, GetHostedServiceProperties 113 gwacl.NewDispatcherResponse( 114 serialize(c, &gwacl.HostedService{ 115 Deployments: deployments, 116 HostedServiceDescriptor: *service, 117 XMLNS: gwacl.XMLNS, 118 }), 119 http.StatusOK, nil), 120 } 121 for _, deployment := range deployments { 122 for _, role := range deployment.RoleList { 123 // GetRole returns a PersistentVMRole. 124 persistentRole := &gwacl.PersistentVMRole{ 125 XMLNS: gwacl.XMLNS, 126 RoleName: role.RoleName, 127 ConfigurationSets: role.ConfigurationSets, 128 } 129 responses = append(responses, gwacl.NewDispatcherResponse( 130 serialize(c, persistentRole), http.StatusOK, nil)) 131 // UpdateRole expects a 200 response, that's all. 132 responses = append(responses, 133 gwacl.NewDispatcherResponse(nil, http.StatusOK, nil)) 134 } 135 } 136 return responses 137 } 138 139 // point is 1-indexed; it represents which request should fail. 140 func failPortChangeConversationAt(point int, responses []gwacl.DispatcherResponse) { 141 responses[point-1] = gwacl.NewDispatcherResponse( 142 nil, http.StatusInternalServerError, nil) 143 } 144 145 type expectedRequest struct { 146 method string 147 urlpattern string 148 } 149 150 func assertPortChangeConversation(c *gc.C, record []*gwacl.X509Request, expected []expectedRequest) { 151 c.Assert(record, gc.HasLen, len(expected)) 152 for index, request := range record { 153 c.Check(request.Method, gc.Equals, expected[index].method) 154 c.Check(request.URL, gc.Matches, expected[index].urlpattern) 155 } 156 } 157 158 func (*instanceSuite) TestAddresses(c *gc.C) { 159 name := "service-name" 160 vnn := "Virt Net Name" 161 service := makeHostedServiceDescriptor(name) 162 responses := prepareDeploymentInfoResponse(c, 163 gwacl.Deployment{ 164 RoleInstanceList: []gwacl.RoleInstance{ 165 gwacl.RoleInstance{IPAddress: "1.2.3.4"}, 166 }, 167 VirtualNetworkName: vnn, 168 }) 169 170 gwacl.PatchManagementAPIResponses(responses) 171 inst := azureInstance{*service, makeEnviron(c)} 172 173 expected := []instance.Address{ 174 instance.Address{ 175 "1.2.3.4", 176 instance.Ipv4Address, 177 vnn, 178 instance.NetworkCloudLocal, 179 }, 180 instance.Address{ 181 name + "." + AZURE_DOMAIN_NAME, 182 instance.HostName, 183 "", 184 instance.NetworkPublic, 185 }, 186 } 187 188 addrs, err := inst.Addresses() 189 c.Check(err, gc.IsNil) 190 191 c.Check(addrs, jc.SameContents, expected) 192 } 193 194 func (*instanceSuite) TestOpenPorts(c *gc.C) { 195 service := makeHostedServiceDescriptor("service-name") 196 responses := preparePortChangeConversation(c, service, 197 makeDeployment("deployment-one", 198 makeRole("role-one"), makeRole("role-two")), 199 makeDeployment("deployment-two", 200 makeRole("role-three"))) 201 record := gwacl.PatchManagementAPIResponses(responses) 202 azInstance := azureInstance{*service, makeEnviron(c)} 203 204 err := azInstance.OpenPorts("machine-id", []instance.Port{ 205 {"tcp", 79}, {"tcp", 587}, {"udp", 9}, 206 }) 207 208 c.Assert(err, gc.IsNil) 209 assertPortChangeConversation(c, *record, []expectedRequest{ 210 {"GET", ".*/services/hostedservices/service-name[?].*"}, // GetHostedServiceProperties 211 {"GET", ".*/deployments/deployment-one/roles/role-one"}, // GetRole 212 {"PUT", ".*/deployments/deployment-one/roles/role-one"}, // UpdateRole 213 {"GET", ".*/deployments/deployment-one/roles/role-two"}, // GetRole 214 {"PUT", ".*/deployments/deployment-one/roles/role-two"}, // UpdateRole 215 {"GET", ".*/deployments/deployment-two/roles/role-three"}, // GetRole 216 {"PUT", ".*/deployments/deployment-two/roles/role-three"}, // UpdateRole 217 }) 218 219 // A representative UpdateRole payload includes configuration for the 220 // ports requested. 221 role := &gwacl.PersistentVMRole{} 222 err = role.Deserialize((*record)[2].Payload) 223 c.Assert(err, gc.IsNil) 224 c.Check( 225 *(role.ConfigurationSets[0].InputEndpoints), 226 gc.DeepEquals, []gwacl.InputEndpoint{ 227 makeInputEndpoint(79, "tcp"), 228 makeInputEndpoint(587, "tcp"), 229 makeInputEndpoint(9, "udp"), 230 }) 231 } 232 233 func (*instanceSuite) TestOpenPortsFailsWhenUnableToGetServiceProperties(c *gc.C) { 234 service := makeHostedServiceDescriptor("service-name") 235 responses := []gwacl.DispatcherResponse{ 236 // GetHostedServiceProperties breaks. 237 gwacl.NewDispatcherResponse(nil, http.StatusInternalServerError, nil), 238 } 239 record := gwacl.PatchManagementAPIResponses(responses) 240 azInstance := azureInstance{*service, makeEnviron(c)} 241 242 err := azInstance.OpenPorts("machine-id", []instance.Port{ 243 {"tcp", 79}, {"tcp", 587}, {"udp", 9}, 244 }) 245 246 c.Check(err, gc.ErrorMatches, "GET request failed [(]500: Internal Server Error[)]") 247 c.Check(*record, gc.HasLen, 1) 248 } 249 250 func (*instanceSuite) TestOpenPortsFailsWhenUnableToGetRole(c *gc.C) { 251 service := makeHostedServiceDescriptor("service-name") 252 responses := preparePortChangeConversation(c, service, 253 makeDeployment("deployment-one", makeRole("role-one"))) 254 failPortChangeConversationAt(2, responses) // 2nd request, GetRole 255 record := gwacl.PatchManagementAPIResponses(responses) 256 azInstance := azureInstance{*service, makeEnviron(c)} 257 258 err := azInstance.OpenPorts("machine-id", []instance.Port{ 259 {"tcp", 79}, {"tcp", 587}, {"udp", 9}, 260 }) 261 262 c.Check(err, gc.ErrorMatches, "GET request failed [(]500: Internal Server Error[)]") 263 c.Check(*record, gc.HasLen, 2) 264 } 265 266 func (*instanceSuite) TestOpenPortsFailsWhenUnableToUpdateRole(c *gc.C) { 267 service := makeHostedServiceDescriptor("service-name") 268 responses := preparePortChangeConversation(c, service, 269 makeDeployment("deployment-one", makeRole("role-one"))) 270 failPortChangeConversationAt(3, responses) // 3rd request, UpdateRole 271 record := gwacl.PatchManagementAPIResponses(responses) 272 azInstance := azureInstance{*service, makeEnviron(c)} 273 274 err := azInstance.OpenPorts("machine-id", []instance.Port{ 275 {"tcp", 79}, {"tcp", 587}, {"udp", 9}, 276 }) 277 278 c.Check(err, gc.ErrorMatches, "PUT request failed [(]500: Internal Server Error[)]") 279 c.Check(*record, gc.HasLen, 3) 280 } 281 282 func (*instanceSuite) TestClosePorts(c *gc.C) { 283 service := makeHostedServiceDescriptor("service-name") 284 responses := preparePortChangeConversation(c, service, 285 makeDeployment("deployment-one", 286 makeRole("role-one", 287 makeInputEndpoint(587, "tcp"), 288 ), 289 makeRole("role-two", 290 makeInputEndpoint(79, "tcp"), 291 makeInputEndpoint(9, "udp"), 292 )), 293 makeDeployment("deployment-two", 294 makeRole("role-three", 295 makeInputEndpoint(9, "tcp"), 296 makeInputEndpoint(9, "udp"), 297 ))) 298 record := gwacl.PatchManagementAPIResponses(responses) 299 azInstance := azureInstance{*service, makeEnviron(c)} 300 301 err := azInstance.ClosePorts("machine-id", 302 []instance.Port{{"tcp", 587}, {"udp", 9}}) 303 304 c.Assert(err, gc.IsNil) 305 assertPortChangeConversation(c, *record, []expectedRequest{ 306 {"GET", ".*/services/hostedservices/service-name[?].*"}, // GetHostedServiceProperties 307 {"GET", ".*/deployments/deployment-one/roles/role-one"}, // GetRole 308 {"PUT", ".*/deployments/deployment-one/roles/role-one"}, // UpdateRole 309 {"GET", ".*/deployments/deployment-one/roles/role-two"}, // GetRole 310 {"PUT", ".*/deployments/deployment-one/roles/role-two"}, // UpdateRole 311 {"GET", ".*/deployments/deployment-two/roles/role-three"}, // GetRole 312 {"PUT", ".*/deployments/deployment-two/roles/role-three"}, // UpdateRole 313 }) 314 315 // The first UpdateRole removes all endpoints from the role's 316 // configuration. 317 roleOne := &gwacl.PersistentVMRole{} 318 err = roleOne.Deserialize((*record)[2].Payload) 319 c.Assert(err, gc.IsNil) 320 c.Check(roleOne.ConfigurationSets[0].InputEndpoints, gc.IsNil) 321 322 // The second UpdateRole removes all but 79/TCP. 323 roleTwo := &gwacl.PersistentVMRole{} 324 err = roleTwo.Deserialize((*record)[4].Payload) 325 c.Assert(err, gc.IsNil) 326 c.Check( 327 roleTwo.ConfigurationSets[0].InputEndpoints, 328 gc.DeepEquals, 329 &[]gwacl.InputEndpoint{makeInputEndpoint(79, "tcp")}) 330 331 // The third UpdateRole removes all but 9/TCP. 332 roleThree := &gwacl.PersistentVMRole{} 333 err = roleThree.Deserialize((*record)[6].Payload) 334 c.Assert(err, gc.IsNil) 335 c.Check( 336 roleThree.ConfigurationSets[0].InputEndpoints, 337 gc.DeepEquals, 338 &[]gwacl.InputEndpoint{makeInputEndpoint(9, "tcp")}) 339 } 340 341 func (*instanceSuite) TestClosePortsFailsWhenUnableToGetServiceProperties(c *gc.C) { 342 service := makeHostedServiceDescriptor("service-name") 343 responses := []gwacl.DispatcherResponse{ 344 // GetHostedServiceProperties breaks. 345 gwacl.NewDispatcherResponse(nil, http.StatusInternalServerError, nil), 346 } 347 record := gwacl.PatchManagementAPIResponses(responses) 348 azInstance := azureInstance{*service, makeEnviron(c)} 349 350 err := azInstance.ClosePorts("machine-id", []instance.Port{ 351 {"tcp", 79}, {"tcp", 587}, {"udp", 9}, 352 }) 353 354 c.Check(err, gc.ErrorMatches, "GET request failed [(]500: Internal Server Error[)]") 355 c.Check(*record, gc.HasLen, 1) 356 } 357 358 func (*instanceSuite) TestClosePortsFailsWhenUnableToGetRole(c *gc.C) { 359 service := makeHostedServiceDescriptor("service-name") 360 responses := preparePortChangeConversation(c, service, 361 makeDeployment("deployment-one", makeRole("role-one"))) 362 failPortChangeConversationAt(2, responses) // 2nd request, GetRole 363 record := gwacl.PatchManagementAPIResponses(responses) 364 azInstance := azureInstance{*service, makeEnviron(c)} 365 366 err := azInstance.ClosePorts("machine-id", []instance.Port{ 367 {"tcp", 79}, {"tcp", 587}, {"udp", 9}, 368 }) 369 370 c.Check(err, gc.ErrorMatches, "GET request failed [(]500: Internal Server Error[)]") 371 c.Check(*record, gc.HasLen, 2) 372 } 373 374 func (*instanceSuite) TestClosePortsFailsWhenUnableToUpdateRole(c *gc.C) { 375 service := makeHostedServiceDescriptor("service-name") 376 responses := preparePortChangeConversation(c, service, 377 makeDeployment("deployment-one", makeRole("role-one"))) 378 failPortChangeConversationAt(3, responses) // 3rd request, UpdateRole 379 record := gwacl.PatchManagementAPIResponses(responses) 380 azInstance := azureInstance{*service, makeEnviron(c)} 381 382 err := azInstance.ClosePorts("machine-id", []instance.Port{ 383 {"tcp", 79}, {"tcp", 587}, {"udp", 9}, 384 }) 385 386 c.Check(err, gc.ErrorMatches, "PUT request failed [(]500: Internal Server Error[)]") 387 c.Check(*record, gc.HasLen, 3) 388 } 389 390 func (*instanceSuite) TestConvertAndFilterEndpoints(c *gc.C) { 391 env := makeEnviron(c) 392 endpoints := []gwacl.InputEndpoint{ 393 { 394 LocalPort: 123, 395 Protocol: "udp", 396 Name: "test123", 397 Port: 1123, 398 }, 399 { 400 LocalPort: 456, 401 Protocol: "tcp", 402 Name: "test456", 403 Port: 44, 404 }} 405 endpoints = append(endpoints, env.getInitialEndpoints()...) 406 expectedPorts := []instance.Port{ 407 { 408 Number: 1123, 409 Protocol: "udp", 410 }, 411 { 412 Number: 44, 413 Protocol: "tcp", 414 }} 415 c.Check(convertAndFilterEndpoints(endpoints, env), gc.DeepEquals, expectedPorts) 416 } 417 418 func (*instanceSuite) TestConvertAndFilterEndpointsEmptySlice(c *gc.C) { 419 env := makeEnviron(c) 420 ports := convertAndFilterEndpoints([]gwacl.InputEndpoint{}, env) 421 c.Check(ports, gc.HasLen, 0) 422 } 423 424 func (*instanceSuite) TestPorts(c *gc.C) { 425 service := makeHostedServiceDescriptor("service-name") 426 endpoints := []gwacl.InputEndpoint{ 427 { 428 LocalPort: 223, 429 Protocol: "udp", 430 Name: "test223", 431 Port: 2123, 432 }, 433 { 434 LocalPort: 123, 435 Protocol: "udp", 436 Name: "test123", 437 Port: 1123, 438 }, 439 { 440 LocalPort: 456, 441 Protocol: "tcp", 442 Name: "test456", 443 Port: 4456, 444 }} 445 446 responses := preparePortChangeConversation(c, service, 447 makeDeployment("deployment-one", 448 makeRole("role-one", endpoints...))) 449 record := gwacl.PatchManagementAPIResponses(responses) 450 azInstance := azureInstance{*service, makeEnviron(c)} 451 452 ports, err := azInstance.Ports("machine-id") 453 454 c.Assert(err, gc.IsNil) 455 assertPortChangeConversation(c, *record, []expectedRequest{ 456 {"GET", ".*/services/hostedservices/service-name[?].*"}, // GetHostedServiceProperties 457 {"GET", ".*/deployments/deployment-one/roles/role-one"}, // GetRole 458 }) 459 460 c.Check( 461 ports, 462 gc.DeepEquals, 463 // The result is sorted using instance.SortPorts() (i.e. first by protocol, 464 // then by number). 465 []instance.Port{ 466 {Number: 4456, Protocol: "tcp"}, 467 {Number: 1123, Protocol: "udp"}, 468 {Number: 2123, Protocol: "udp"}, 469 }) 470 } 471 472 func (*instanceSuite) TestPortsErrorsIfMoreThanOneRole(c *gc.C) { 473 service := makeHostedServiceDescriptor("service-name") 474 responses := preparePortChangeConversation(c, service, 475 makeDeployment("deployment-one", 476 makeRole("role-one"), makeRole("role-two"))) 477 gwacl.PatchManagementAPIResponses(responses) 478 azInstance := azureInstance{*service, makeEnviron(c)} 479 480 _, err := azInstance.Ports("machine-id") 481 482 c.Check(err, gc.ErrorMatches, ".*more than one Azure role inside the deployment.*") 483 } 484 485 func (*instanceSuite) TestPortsErrorsIfMoreThanOneDeployment(c *gc.C) { 486 service := makeHostedServiceDescriptor("service-name") 487 responses := preparePortChangeConversation(c, service, 488 makeDeployment("deployment-one", 489 makeRole("role-one")), 490 makeDeployment("deployment-two", 491 makeRole("role-two"))) 492 gwacl.PatchManagementAPIResponses(responses) 493 azInstance := azureInstance{*service, makeEnviron(c)} 494 495 _, err := azInstance.Ports("machine-id") 496 497 c.Check(err, gc.ErrorMatches, ".*more than one Azure deployment inside the service.*") 498 } 499 500 func (*instanceSuite) TestPortsReturnsEmptySliceIfNoDeployment(c *gc.C) { 501 service := makeHostedServiceDescriptor("service-name") 502 responses := preparePortChangeConversation(c, service) 503 gwacl.PatchManagementAPIResponses(responses) 504 azInstance := azureInstance{*service, makeEnviron(c)} 505 506 ports, err := azInstance.Ports("machine-id") 507 508 c.Assert(err, gc.IsNil) 509 c.Check(ports, gc.HasLen, 0) 510 }