github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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 jc "github.com/juju/testing/checkers" 12 gc "launchpad.net/gocheck" 13 "launchpad.net/gwacl" 14 15 "github.com/juju/juju/instance" 16 "github.com/juju/juju/testing" 17 ) 18 19 type instanceSuite struct { 20 testing.BaseSuite 21 env *azureEnviron 22 service *gwacl.HostedService 23 deployment *gwacl.Deployment 24 role *gwacl.Role 25 instance *azureInstance 26 } 27 28 var _ = gc.Suite(&instanceSuite{}) 29 30 func (s *instanceSuite) SetUpTest(c *gc.C) { 31 s.env = makeEnviron(c) 32 s.service = makeDeployment(s.env, "service-name") 33 s.deployment = &s.service.Deployments[0] 34 s.deployment.Name = "deployment-one" 35 s.role = &s.deployment.RoleList[0] 36 s.role.RoleName = "role-one" 37 inst, err := s.env.getInstance(s.service, s.role.RoleName) 38 c.Assert(err, gc.IsNil) 39 c.Assert(inst, gc.FitsTypeOf, &azureInstance{}) 40 s.instance = inst.(*azureInstance) 41 } 42 43 func configSetNetwork(role *gwacl.Role) *gwacl.ConfigurationSet { 44 for i, configSet := range role.ConfigurationSets { 45 if configSet.ConfigurationSetType == gwacl.CONFIG_SET_NETWORK { 46 return &role.ConfigurationSets[i] 47 } 48 } 49 return nil 50 } 51 52 // makeHostedServiceDescriptor creates a HostedServiceDescriptor with the 53 // given service name. 54 func makeHostedServiceDescriptor(name string) *gwacl.HostedServiceDescriptor { 55 labelBase64 := base64.StdEncoding.EncodeToString([]byte("label")) 56 return &gwacl.HostedServiceDescriptor{ServiceName: name, Label: labelBase64} 57 } 58 59 func (*instanceSuite) TestId(c *gc.C) { 60 azInstance := azureInstance{instanceId: "whatever"} 61 c.Check(azInstance.Id(), gc.Equals, instance.Id("whatever")) 62 } 63 64 func (*instanceSuite) TestStatus(c *gc.C) { 65 var inst azureInstance 66 c.Check(inst.Status(), gc.Equals, "") 67 inst.roleInstance = &gwacl.RoleInstance{InstanceStatus: "anyoldthing"} 68 c.Check(inst.Status(), gc.Equals, "anyoldthing") 69 } 70 71 func makeInputEndpoint(port int, protocol string) gwacl.InputEndpoint { 72 name := fmt.Sprintf("%s%d", protocol, port) 73 probe := &gwacl.LoadBalancerProbe{Port: port, Protocol: "TCP"} 74 if protocol == "udp" { 75 // We just use port 22 (SSH) for the 76 // probe when a UDP port is exposed. 77 probe.Port = 22 78 } 79 return gwacl.InputEndpoint{ 80 LocalPort: port, 81 Name: name, 82 LoadBalancedEndpointSetName: name, 83 LoadBalancerProbe: probe, 84 Port: port, 85 Protocol: protocol, 86 } 87 } 88 89 func serialize(c *gc.C, object gwacl.AzureObject) []byte { 90 xml, err := object.Serialize() 91 c.Assert(err, gc.IsNil) 92 return []byte(xml) 93 } 94 95 func prepareDeploymentInfoResponse( 96 c *gc.C, dep gwacl.Deployment) []gwacl.DispatcherResponse { 97 return []gwacl.DispatcherResponse{ 98 gwacl.NewDispatcherResponse( 99 serialize(c, &dep), http.StatusOK, nil), 100 } 101 } 102 103 func preparePortChangeConversation(c *gc.C, role *gwacl.Role) []gwacl.DispatcherResponse { 104 persistentRole := &gwacl.PersistentVMRole{ 105 XMLNS: gwacl.XMLNS, 106 RoleName: role.RoleName, 107 ConfigurationSets: role.ConfigurationSets, 108 } 109 return []gwacl.DispatcherResponse{ 110 // GetRole returns a PersistentVMRole. 111 gwacl.NewDispatcherResponse(serialize(c, persistentRole), http.StatusOK, nil), 112 // UpdateRole expects a 200 response, that's all. 113 gwacl.NewDispatcherResponse(nil, http.StatusOK, nil), 114 } 115 } 116 117 // point is 1-indexed; it represents which request should fail. 118 func failPortChangeConversationAt(point int, responses []gwacl.DispatcherResponse) { 119 responses[point-1] = gwacl.NewDispatcherResponse( 120 nil, http.StatusInternalServerError, nil) 121 } 122 123 type expectedRequest struct { 124 method string 125 urlpattern string 126 } 127 128 func assertPortChangeConversation(c *gc.C, record []*gwacl.X509Request, expected []expectedRequest) { 129 c.Assert(record, gc.HasLen, len(expected)) 130 for index, request := range record { 131 c.Check(request.Method, gc.Equals, expected[index].method) 132 c.Check(request.URL, gc.Matches, expected[index].urlpattern) 133 } 134 } 135 136 func (s *instanceSuite) TestAddresses(c *gc.C) { 137 vnn := s.env.getVirtualNetworkName() 138 responses := prepareDeploymentInfoResponse(c, gwacl.Deployment{ 139 RoleInstanceList: []gwacl.RoleInstance{{ 140 RoleName: s.role.RoleName, 141 IPAddress: "1.2.3.4", 142 }}, 143 VirtualNetworkName: vnn, 144 }) 145 gwacl.PatchManagementAPIResponses(responses) 146 147 expected := []instance.Address{ 148 instance.Address{ 149 "1.2.3.4", 150 instance.Ipv4Address, 151 vnn, 152 instance.NetworkCloudLocal, 153 }, 154 instance.Address{ 155 s.service.ServiceName + "." + AzureDomainName, 156 instance.HostName, 157 "", 158 instance.NetworkPublic, 159 }, 160 } 161 162 addrs, err := s.instance.Addresses() 163 c.Check(err, gc.IsNil) 164 c.Check(addrs, jc.SameContents, expected) 165 } 166 167 func (s *instanceSuite) TestOpenPorts(c *gc.C) { 168 // Close the default ports. 169 configSetNetwork((*gwacl.Role)(s.role)).InputEndpoints = nil 170 171 responses := preparePortChangeConversation(c, s.role) 172 record := gwacl.PatchManagementAPIResponses(responses) 173 err := s.instance.OpenPorts("machine-id", []instance.Port{ 174 {"tcp", 79}, {"tcp", 587}, {"udp", 9}, 175 }) 176 c.Assert(err, gc.IsNil) 177 178 assertPortChangeConversation(c, *record, []expectedRequest{ 179 {"GET", ".*/deployments/deployment-one/roles/role-one"}, // GetRole 180 {"PUT", ".*/deployments/deployment-one/roles/role-one"}, // UpdateRole 181 }) 182 183 // A representative UpdateRole payload includes configuration for the 184 // ports requested. 185 role := &gwacl.PersistentVMRole{} 186 err = role.Deserialize((*record)[1].Payload) 187 c.Assert(err, gc.IsNil) 188 c.Check( 189 *configSetNetwork((*gwacl.Role)(role)).InputEndpoints, 190 gc.DeepEquals, 191 []gwacl.InputEndpoint{ 192 makeInputEndpoint(79, "tcp"), 193 makeInputEndpoint(587, "tcp"), 194 makeInputEndpoint(9, "udp"), 195 }, 196 ) 197 } 198 199 func (s *instanceSuite) TestOpenPortsFailsWhenUnableToGetRole(c *gc.C) { 200 responses := preparePortChangeConversation(c, s.role) 201 failPortChangeConversationAt(1, responses) // 1st request, GetRole 202 record := gwacl.PatchManagementAPIResponses(responses) 203 err := s.instance.OpenPorts("machine-id", []instance.Port{ 204 {"tcp", 79}, {"tcp", 587}, {"udp", 9}, 205 }) 206 c.Check(err, gc.ErrorMatches, "GET request failed [(]500: Internal Server Error[)]") 207 c.Check(*record, gc.HasLen, 1) 208 } 209 210 func (s *instanceSuite) TestOpenPortsFailsWhenUnableToUpdateRole(c *gc.C) { 211 responses := preparePortChangeConversation(c, s.role) 212 failPortChangeConversationAt(2, responses) // 2nd request, UpdateRole 213 record := gwacl.PatchManagementAPIResponses(responses) 214 err := s.instance.OpenPorts("machine-id", []instance.Port{ 215 {"tcp", 79}, {"tcp", 587}, {"udp", 9}, 216 }) 217 c.Check(err, gc.ErrorMatches, "PUT request failed [(]500: Internal Server Error[)]") 218 c.Check(*record, gc.HasLen, 2) 219 } 220 221 func (s *instanceSuite) TestClosePorts(c *gc.C) { 222 type test struct { 223 inputPorts []instance.Port 224 removePorts []instance.Port 225 outputPorts []instance.Port 226 } 227 228 tests := []test{{ 229 inputPorts: []instance.Port{{"tcp", 1}, {"tcp", 2}, {"udp", 3}}, 230 removePorts: nil, 231 outputPorts: []instance.Port{{"tcp", 1}, {"tcp", 2}, {"udp", 3}}, 232 }, { 233 inputPorts: []instance.Port{{"tcp", 1}}, 234 removePorts: []instance.Port{{"udp", 1}}, 235 outputPorts: []instance.Port{{"tcp", 1}}, 236 }, { 237 inputPorts: []instance.Port{{"tcp", 1}, {"tcp", 2}, {"udp", 3}}, 238 removePorts: []instance.Port{{"tcp", 1}, {"tcp", 2}, {"udp", 3}}, 239 outputPorts: []instance.Port{}, 240 }, { 241 inputPorts: []instance.Port{{"tcp", 1}, {"tcp", 2}, {"udp", 3}}, 242 removePorts: []instance.Port{{"tcp", 99}}, 243 outputPorts: []instance.Port{{"tcp", 1}, {"tcp", 2}, {"udp", 3}}, 244 }} 245 246 for i, test := range tests { 247 c.Logf("test %d: %#v", i, test) 248 249 inputEndpoints := make([]gwacl.InputEndpoint, len(test.inputPorts)) 250 for i, port := range test.inputPorts { 251 inputEndpoints[i] = makeInputEndpoint(port.Number, port.Protocol) 252 } 253 configSetNetwork(s.role).InputEndpoints = &inputEndpoints 254 responses := preparePortChangeConversation(c, s.role) 255 record := gwacl.PatchManagementAPIResponses(responses) 256 257 err := s.instance.ClosePorts("machine-id", test.removePorts) 258 c.Assert(err, gc.IsNil) 259 assertPortChangeConversation(c, *record, []expectedRequest{ 260 {"GET", ".*/deployments/deployment-one/roles/role-one"}, // GetRole 261 {"PUT", ".*/deployments/deployment-one/roles/role-one"}, // UpdateRole 262 }) 263 264 // The first UpdateRole removes all endpoints from the role's 265 // configuration. 266 roleOne := &gwacl.PersistentVMRole{} 267 err = roleOne.Deserialize((*record)[1].Payload) 268 c.Assert(err, gc.IsNil) 269 endpoints := configSetNetwork((*gwacl.Role)(roleOne)).InputEndpoints 270 if len(test.outputPorts) == 0 { 271 c.Check(endpoints, gc.IsNil) 272 } else { 273 c.Check(endpoints, gc.NotNil) 274 c.Check(convertAndFilterEndpoints(*endpoints, s.env, false), gc.DeepEquals, test.outputPorts) 275 } 276 } 277 } 278 279 func (s *instanceSuite) TestClosePortsFailsWhenUnableToGetRole(c *gc.C) { 280 responses := preparePortChangeConversation(c, s.role) 281 failPortChangeConversationAt(1, responses) // 1st request, GetRole 282 record := gwacl.PatchManagementAPIResponses(responses) 283 err := s.instance.ClosePorts("machine-id", []instance.Port{ 284 {"tcp", 79}, {"tcp", 587}, {"udp", 9}, 285 }) 286 c.Check(err, gc.ErrorMatches, "GET request failed [(]500: Internal Server Error[)]") 287 c.Check(*record, gc.HasLen, 1) 288 } 289 290 func (s *instanceSuite) TestClosePortsFailsWhenUnableToUpdateRole(c *gc.C) { 291 responses := preparePortChangeConversation(c, s.role) 292 failPortChangeConversationAt(2, responses) // 2nd request, UpdateRole 293 record := gwacl.PatchManagementAPIResponses(responses) 294 err := s.instance.ClosePorts("machine-id", []instance.Port{ 295 {"tcp", 79}, {"tcp", 587}, {"udp", 9}, 296 }) 297 c.Check(err, gc.ErrorMatches, "PUT request failed [(]500: Internal Server Error[)]") 298 c.Check(*record, gc.HasLen, 2) 299 } 300 301 func (s *instanceSuite) TestConvertAndFilterEndpoints(c *gc.C) { 302 endpoints := []gwacl.InputEndpoint{ 303 { 304 LocalPort: 123, 305 Protocol: "udp", 306 Name: "test123", 307 Port: 1123, 308 }, 309 { 310 LocalPort: 456, 311 Protocol: "tcp", 312 Name: "test456", 313 Port: 44, 314 }} 315 endpoints = append(endpoints, s.env.getInitialEndpoints(true)...) 316 expectedPorts := []instance.Port{ 317 { 318 Number: 1123, 319 Protocol: "udp", 320 }, 321 { 322 Number: 44, 323 Protocol: "tcp", 324 }} 325 c.Check(convertAndFilterEndpoints(endpoints, s.env, true), gc.DeepEquals, expectedPorts) 326 } 327 328 func (s *instanceSuite) TestConvertAndFilterEndpointsEmptySlice(c *gc.C) { 329 ports := convertAndFilterEndpoints([]gwacl.InputEndpoint{}, s.env, true) 330 c.Check(ports, gc.HasLen, 0) 331 } 332 333 func (s *instanceSuite) TestPorts(c *gc.C) { 334 s.testPorts(c, false) 335 s.testPorts(c, true) 336 } 337 338 func (s *instanceSuite) testPorts(c *gc.C, maskStateServerPorts bool) { 339 // Update the role's endpoints by hand. 340 configSetNetwork(s.role).InputEndpoints = &[]gwacl.InputEndpoint{{ 341 LocalPort: 223, 342 Protocol: "udp", 343 Name: "test223", 344 Port: 2123, 345 }, { 346 LocalPort: 123, 347 Protocol: "udp", 348 Name: "test123", 349 Port: 1123, 350 }, { 351 LocalPort: 456, 352 Protocol: "tcp", 353 Name: "test456", 354 Port: 4456, 355 }, { 356 LocalPort: s.env.Config().StatePort(), 357 Protocol: "tcp", 358 Name: "stateserver", 359 Port: s.env.Config().StatePort(), 360 }, { 361 LocalPort: s.env.Config().APIPort(), 362 Protocol: "tcp", 363 Name: "apiserver", 364 Port: s.env.Config().APIPort(), 365 }} 366 367 responses := preparePortChangeConversation(c, s.role) 368 record := gwacl.PatchManagementAPIResponses(responses) 369 s.instance.maskStateServerPorts = maskStateServerPorts 370 ports, err := s.instance.Ports("machine-id") 371 c.Assert(err, gc.IsNil) 372 assertPortChangeConversation(c, *record, []expectedRequest{ 373 {"GET", ".*/deployments/deployment-one/roles/role-one"}, // GetRole 374 }) 375 376 expected := []instance.Port{ 377 {Number: 4456, Protocol: "tcp"}, 378 {Number: 1123, Protocol: "udp"}, 379 {Number: 2123, Protocol: "udp"}, 380 } 381 if !maskStateServerPorts { 382 expected = append(expected, instance.Port{Number: s.env.Config().StatePort(), Protocol: "tcp"}) 383 expected = append(expected, instance.Port{Number: s.env.Config().APIPort(), Protocol: "tcp"}) 384 instance.SortPorts(expected) 385 } 386 c.Check(ports, gc.DeepEquals, expected) 387 }