github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/provider/azure/instance_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package azure_test 5 6 import ( 7 "net/http" 8 "path" 9 10 "github.com/Azure/azure-sdk-for-go/arm/compute" 11 "github.com/Azure/azure-sdk-for-go/arm/network" 12 "github.com/Azure/azure-sdk-for-go/arm/resources/resources" 13 "github.com/Azure/go-autorest/autorest/mocks" 14 "github.com/Azure/go-autorest/autorest/to" 15 jc "github.com/juju/testing/checkers" 16 gc "gopkg.in/check.v1" 17 18 "github.com/juju/juju/environs" 19 "github.com/juju/juju/instance" 20 jujunetwork "github.com/juju/juju/network" 21 "github.com/juju/juju/provider/azure" 22 "github.com/juju/juju/provider/azure/internal/azureauth" 23 "github.com/juju/juju/provider/azure/internal/azuretesting" 24 "github.com/juju/juju/status" 25 "github.com/juju/juju/testing" 26 ) 27 28 type instanceSuite struct { 29 testing.BaseSuite 30 31 provider environs.EnvironProvider 32 requests []*http.Request 33 sender azuretesting.Senders 34 env environs.Environ 35 deployments []resources.DeploymentExtended 36 networkInterfaces []network.Interface 37 publicIPAddresses []network.PublicIPAddress 38 } 39 40 var _ = gc.Suite(&instanceSuite{}) 41 42 func (s *instanceSuite) SetUpTest(c *gc.C) { 43 s.BaseSuite.SetUpTest(c) 44 s.provider = newProvider(c, azure.ProviderConfig{ 45 Sender: &s.sender, 46 RequestInspector: azuretesting.RequestRecorder(&s.requests), 47 RandomWindowsAdminPassword: func() string { return "sorandom" }, 48 InteractiveCreateServicePrincipal: azureauth.InteractiveCreateServicePrincipal, 49 }) 50 s.env = openEnviron(c, s.provider, &s.sender) 51 s.sender = nil 52 s.requests = nil 53 s.networkInterfaces = []network.Interface{ 54 makeNetworkInterface("nic-0", "machine-0"), 55 } 56 s.publicIPAddresses = nil 57 s.deployments = []resources.DeploymentExtended{ 58 makeDeployment("machine-0"), 59 makeDeployment("machine-1"), 60 } 61 } 62 63 func makeDeployment(name string) resources.DeploymentExtended { 64 dependsOn := []resources.BasicDependency{{ 65 ResourceType: to.StringPtr("Microsoft.Compute/availabilitySets"), 66 ResourceName: to.StringPtr("mysql"), 67 }} 68 dependencies := []resources.Dependency{{ 69 ResourceType: to.StringPtr("Microsoft.Compute/virtualMachines"), 70 DependsOn: &dependsOn, 71 }} 72 return resources.DeploymentExtended{ 73 Name: to.StringPtr(name), 74 Properties: &resources.DeploymentPropertiesExtended{ 75 ProvisioningState: to.StringPtr("Succeeded"), 76 Dependencies: &dependencies, 77 }, 78 } 79 } 80 81 func makeVirtualMachine(name string) compute.VirtualMachine { 82 return compute.VirtualMachine{ 83 Name: to.StringPtr(name), 84 Properties: &compute.VirtualMachineProperties{ 85 ProvisioningState: to.StringPtr("Succeeded"), 86 }, 87 } 88 } 89 90 func makeNetworkInterface(nicName, vmName string, ipConfigurations ...network.InterfaceIPConfiguration) network.Interface { 91 tags := map[string]*string{"juju-machine-name": &vmName} 92 return network.Interface{ 93 Name: to.StringPtr(nicName), 94 Tags: &tags, 95 Properties: &network.InterfacePropertiesFormat{ 96 IPConfigurations: &ipConfigurations, 97 }, 98 } 99 } 100 101 func makeIPConfiguration(privateIPAddress string) network.InterfaceIPConfiguration { 102 ipConfiguration := network.InterfaceIPConfiguration{ 103 Properties: &network.InterfaceIPConfigurationPropertiesFormat{}, 104 } 105 if privateIPAddress != "" { 106 ipConfiguration.Properties.PrivateIPAddress = to.StringPtr(privateIPAddress) 107 } 108 return ipConfiguration 109 } 110 111 func makePublicIPAddress(pipName, vmName, ipAddress string) network.PublicIPAddress { 112 tags := map[string]*string{"juju-machine-name": &vmName} 113 pip := network.PublicIPAddress{ 114 Name: to.StringPtr(pipName), 115 Tags: &tags, 116 Properties: &network.PublicIPAddressPropertiesFormat{}, 117 } 118 if ipAddress != "" { 119 pip.Properties.IPAddress = to.StringPtr(ipAddress) 120 } 121 return pip 122 } 123 124 func makeSecurityGroup(rules ...network.SecurityRule) network.SecurityGroup { 125 return network.SecurityGroup{ 126 Properties: &network.SecurityGroupPropertiesFormat{ 127 SecurityRules: &rules, 128 }, 129 } 130 } 131 132 func makeSecurityRule(name, ipAddress, ports string) network.SecurityRule { 133 return network.SecurityRule{ 134 Name: to.StringPtr(name), 135 Properties: &network.SecurityRulePropertiesFormat{ 136 Protocol: network.TCP, 137 DestinationAddressPrefix: to.StringPtr(ipAddress), 138 DestinationPortRange: to.StringPtr(ports), 139 Access: network.Allow, 140 Priority: to.Int32Ptr(200), 141 Direction: network.Inbound, 142 }, 143 } 144 } 145 146 func (s *instanceSuite) getInstance(c *gc.C) instance.Instance { 147 instances := s.getInstances(c, "machine-0") 148 c.Assert(instances, gc.HasLen, 1) 149 return instances[0] 150 } 151 152 func (s *instanceSuite) getInstances(c *gc.C, ids ...instance.Id) []instance.Instance { 153 s.sender = s.getInstancesSender() 154 instances, err := s.env.Instances(ids) 155 c.Assert(err, jc.ErrorIsNil) 156 s.sender = azuretesting.Senders{} 157 s.requests = nil 158 return instances 159 } 160 161 func (s *instanceSuite) getInstancesSender() azuretesting.Senders { 162 deploymentsSender := azuretesting.NewSenderWithValue(&resources.DeploymentListResult{ 163 Value: &s.deployments, 164 }) 165 deploymentsSender.PathPattern = ".*/deployments" 166 nicsSender := azuretesting.NewSenderWithValue(&network.InterfaceListResult{ 167 Value: &s.networkInterfaces, 168 }) 169 nicsSender.PathPattern = ".*/networkInterfaces" 170 pipsSender := azuretesting.NewSenderWithValue(&network.PublicIPAddressListResult{ 171 Value: &s.publicIPAddresses, 172 }) 173 pipsSender.PathPattern = ".*/publicIPAddresses" 174 return azuretesting.Senders{deploymentsSender, nicsSender, pipsSender} 175 } 176 177 func networkSecurityGroupSender(rules []network.SecurityRule) *azuretesting.MockSender { 178 nsgSender := azuretesting.NewSenderWithValue(&network.SecurityGroup{ 179 Properties: &network.SecurityGroupPropertiesFormat{ 180 SecurityRules: &rules, 181 }, 182 }) 183 nsgSender.PathPattern = ".*/networkSecurityGroups/juju-internal-nsg" 184 return nsgSender 185 } 186 187 func (s *instanceSuite) TestInstanceStatus(c *gc.C) { 188 inst := s.getInstance(c) 189 assertInstanceStatus(c, inst.Status(), status.Running, "") 190 } 191 192 func (s *instanceSuite) TestInstanceStatusDeploymentFailed(c *gc.C) { 193 s.deployments[0].Properties.ProvisioningState = to.StringPtr("Failed") 194 inst := s.getInstance(c) 195 assertInstanceStatus(c, inst.Status(), status.ProvisioningError, "Failed") 196 } 197 198 func (s *instanceSuite) TestInstanceStatusDeploymentCanceled(c *gc.C) { 199 s.deployments[0].Properties.ProvisioningState = to.StringPtr("Canceled") 200 inst := s.getInstance(c) 201 assertInstanceStatus(c, inst.Status(), status.ProvisioningError, "Canceled") 202 } 203 204 func (s *instanceSuite) TestInstanceStatusNilProvisioningState(c *gc.C) { 205 s.deployments[0].Properties.ProvisioningState = nil 206 inst := s.getInstance(c) 207 assertInstanceStatus(c, inst.Status(), status.Allocating, "") 208 } 209 210 func assertInstanceStatus(c *gc.C, actual instance.InstanceStatus, status status.Status, message string) { 211 c.Assert(actual, jc.DeepEquals, instance.InstanceStatus{ 212 Status: status, 213 Message: message, 214 }) 215 } 216 217 func (s *instanceSuite) TestInstanceAddressesEmpty(c *gc.C) { 218 addresses, err := s.getInstance(c).Addresses() 219 c.Assert(err, jc.ErrorIsNil) 220 c.Assert(addresses, gc.HasLen, 0) 221 } 222 223 func (s *instanceSuite) TestInstanceAddresses(c *gc.C) { 224 nic0IPConfigurations := []network.InterfaceIPConfiguration{ 225 makeIPConfiguration("10.0.0.4"), 226 makeIPConfiguration("10.0.0.5"), 227 } 228 nic1IPConfigurations := []network.InterfaceIPConfiguration{ 229 makeIPConfiguration(""), 230 } 231 s.networkInterfaces = []network.Interface{ 232 makeNetworkInterface("nic-0", "machine-0", nic0IPConfigurations...), 233 makeNetworkInterface("nic-1", "machine-0", nic1IPConfigurations...), 234 makeNetworkInterface("nic-2", "machine-0"), 235 // unrelated NIC 236 makeNetworkInterface("nic-3", "machine-1"), 237 } 238 s.publicIPAddresses = []network.PublicIPAddress{ 239 makePublicIPAddress("pip-0", "machine-0", "1.2.3.4"), 240 makePublicIPAddress("pip-1", "machine-0", "1.2.3.5"), 241 // unrelated PIP 242 makePublicIPAddress("pip-2", "machine-1", "1.2.3.6"), 243 } 244 addresses, err := s.getInstance(c).Addresses() 245 c.Assert(err, jc.ErrorIsNil) 246 c.Assert(addresses, jc.DeepEquals, jujunetwork.NewAddresses( 247 "10.0.0.4", "10.0.0.5", "1.2.3.4", "1.2.3.5", 248 )) 249 } 250 251 func (s *instanceSuite) TestMultipleInstanceAddresses(c *gc.C) { 252 nic0IPConfiguration := makeIPConfiguration("10.0.0.4") 253 nic1IPConfiguration := makeIPConfiguration("10.0.0.5") 254 s.networkInterfaces = []network.Interface{ 255 makeNetworkInterface("nic-0", "machine-0", nic0IPConfiguration), 256 makeNetworkInterface("nic-1", "machine-1", nic1IPConfiguration), 257 } 258 s.publicIPAddresses = []network.PublicIPAddress{ 259 makePublicIPAddress("pip-0", "machine-0", "1.2.3.4"), 260 makePublicIPAddress("pip-1", "machine-1", "1.2.3.5"), 261 } 262 instances := s.getInstances(c, "machine-0", "machine-1") 263 c.Assert(instances, gc.HasLen, 2) 264 265 inst0Addresses, err := instances[0].Addresses() 266 c.Assert(err, jc.ErrorIsNil) 267 c.Assert(inst0Addresses, jc.DeepEquals, jujunetwork.NewAddresses( 268 "10.0.0.4", "1.2.3.4", 269 )) 270 271 inst1Addresses, err := instances[1].Addresses() 272 c.Assert(err, jc.ErrorIsNil) 273 c.Assert(inst1Addresses, jc.DeepEquals, jujunetwork.NewAddresses( 274 "10.0.0.5", "1.2.3.5", 275 )) 276 } 277 278 func (s *instanceSuite) TestInstancePortsEmpty(c *gc.C) { 279 inst := s.getInstance(c) 280 nsgSender := networkSecurityGroupSender(nil) 281 s.sender = azuretesting.Senders{nsgSender} 282 ports, err := inst.Ports("0") 283 c.Assert(err, jc.ErrorIsNil) 284 c.Assert(ports, gc.HasLen, 0) 285 } 286 287 func (s *instanceSuite) TestInstancePorts(c *gc.C) { 288 inst := s.getInstance(c) 289 nsgSender := networkSecurityGroupSender([]network.SecurityRule{{ 290 Name: to.StringPtr("machine-0-xyzzy"), 291 Properties: &network.SecurityRulePropertiesFormat{ 292 Protocol: network.UDP, 293 DestinationPortRange: to.StringPtr("*"), 294 Access: network.Allow, 295 Priority: to.Int32Ptr(200), 296 Direction: network.Inbound, 297 }, 298 }, { 299 Name: to.StringPtr("machine-0-tcpcp"), 300 Properties: &network.SecurityRulePropertiesFormat{ 301 Protocol: network.TCP, 302 DestinationPortRange: to.StringPtr("1000-2000"), 303 Access: network.Allow, 304 Priority: to.Int32Ptr(201), 305 Direction: network.Inbound, 306 }, 307 }, { 308 Name: to.StringPtr("machine-0-http"), 309 Properties: &network.SecurityRulePropertiesFormat{ 310 Protocol: network.Asterisk, 311 DestinationPortRange: to.StringPtr("80"), 312 Access: network.Allow, 313 Priority: to.Int32Ptr(202), 314 Direction: network.Inbound, 315 }, 316 }, { 317 Name: to.StringPtr("machine-00-ignored"), 318 Properties: &network.SecurityRulePropertiesFormat{ 319 Protocol: network.TCP, 320 DestinationPortRange: to.StringPtr("80"), 321 Access: network.Allow, 322 Priority: to.Int32Ptr(202), 323 Direction: network.Inbound, 324 }, 325 }, { 326 Name: to.StringPtr("machine-0-ignored"), 327 Properties: &network.SecurityRulePropertiesFormat{ 328 Protocol: network.TCP, 329 DestinationPortRange: to.StringPtr("80"), 330 Access: network.Deny, 331 Priority: to.Int32Ptr(202), 332 Direction: network.Inbound, 333 }, 334 }, { 335 Name: to.StringPtr("machine-0-ignored"), 336 Properties: &network.SecurityRulePropertiesFormat{ 337 Protocol: network.TCP, 338 DestinationPortRange: to.StringPtr("80"), 339 Access: network.Allow, 340 Priority: to.Int32Ptr(202), 341 Direction: network.Outbound, 342 }, 343 }, { 344 Name: to.StringPtr("machine-0-ignored"), 345 Properties: &network.SecurityRulePropertiesFormat{ 346 Protocol: network.TCP, 347 DestinationPortRange: to.StringPtr("80"), 348 Access: network.Allow, 349 Priority: to.Int32Ptr(199), // internal range 350 Direction: network.Inbound, 351 }, 352 }}) 353 s.sender = azuretesting.Senders{nsgSender} 354 355 ports, err := inst.Ports("0") 356 c.Assert(err, jc.ErrorIsNil) 357 c.Assert(ports, jc.DeepEquals, []jujunetwork.PortRange{{ 358 FromPort: 0, 359 ToPort: 65535, 360 Protocol: "udp", 361 }, { 362 FromPort: 1000, 363 ToPort: 2000, 364 Protocol: "tcp", 365 }, { 366 FromPort: 80, 367 ToPort: 80, 368 Protocol: "tcp", 369 }, { 370 FromPort: 80, 371 ToPort: 80, 372 Protocol: "udp", 373 }}) 374 } 375 376 func (s *instanceSuite) TestInstanceClosePorts(c *gc.C) { 377 inst := s.getInstance(c) 378 sender := mocks.NewSender() 379 notFoundSender := mocks.NewSender() 380 notFoundSender.AppendResponse(mocks.NewResponseWithStatus( 381 "rule not found", http.StatusNotFound, 382 )) 383 s.sender = azuretesting.Senders{sender, notFoundSender} 384 385 err := inst.ClosePorts("0", []jujunetwork.PortRange{{ 386 Protocol: "tcp", 387 FromPort: 1000, 388 ToPort: 1000, 389 }, { 390 Protocol: "udp", 391 FromPort: 1000, 392 ToPort: 2000, 393 }}) 394 c.Assert(err, jc.ErrorIsNil) 395 396 c.Assert(s.requests, gc.HasLen, 2) 397 c.Assert(s.requests[0].Method, gc.Equals, "DELETE") 398 c.Assert(s.requests[0].URL.Path, gc.Equals, securityRulePath("machine-0-tcp-1000")) 399 c.Assert(s.requests[1].Method, gc.Equals, "DELETE") 400 c.Assert(s.requests[1].URL.Path, gc.Equals, securityRulePath("machine-0-udp-1000-2000")) 401 } 402 403 func (s *instanceSuite) TestInstanceOpenPorts(c *gc.C) { 404 internalSubnetId := path.Join( 405 "/subscriptions", fakeSubscriptionId, 406 "resourceGroups/juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d", 407 "providers/Microsoft.Network/virtualnetworks/juju-internal-network/subnets/juju-internal-subnet", 408 ) 409 ipConfiguration := network.InterfaceIPConfiguration{ 410 Properties: &network.InterfaceIPConfigurationPropertiesFormat{ 411 Primary: to.BoolPtr(true), 412 PrivateIPAddress: to.StringPtr("10.0.0.4"), 413 Subnet: &network.Subnet{ 414 ID: to.StringPtr(internalSubnetId), 415 }, 416 }, 417 } 418 s.networkInterfaces = []network.Interface{ 419 makeNetworkInterface("nic-0", "machine-0", ipConfiguration), 420 } 421 422 inst := s.getInstance(c) 423 okSender := mocks.NewSender() 424 okSender.AppendResponse(mocks.NewResponseWithContent("{}")) 425 nsgSender := networkSecurityGroupSender(nil) 426 s.sender = azuretesting.Senders{nsgSender, okSender, okSender} 427 428 err := inst.OpenPorts("0", []jujunetwork.PortRange{{ 429 Protocol: "tcp", 430 FromPort: 1000, 431 ToPort: 1000, 432 }, { 433 Protocol: "udp", 434 FromPort: 1000, 435 ToPort: 2000, 436 }}) 437 c.Assert(err, jc.ErrorIsNil) 438 439 c.Assert(s.requests, gc.HasLen, 3) 440 c.Assert(s.requests[0].Method, gc.Equals, "GET") 441 c.Assert(s.requests[0].URL.Path, gc.Equals, internalSecurityGroupPath) 442 c.Assert(s.requests[1].Method, gc.Equals, "PUT") 443 c.Assert(s.requests[1].URL.Path, gc.Equals, securityRulePath("machine-0-tcp-1000")) 444 assertRequestBody(c, s.requests[1], &network.SecurityRule{ 445 Properties: &network.SecurityRulePropertiesFormat{ 446 Description: to.StringPtr("1000/tcp"), 447 Protocol: network.TCP, 448 SourcePortRange: to.StringPtr("*"), 449 SourceAddressPrefix: to.StringPtr("*"), 450 DestinationPortRange: to.StringPtr("1000"), 451 DestinationAddressPrefix: to.StringPtr("10.0.0.4"), 452 Access: network.Allow, 453 Priority: to.Int32Ptr(200), 454 Direction: network.Inbound, 455 }, 456 }) 457 c.Assert(s.requests[2].Method, gc.Equals, "PUT") 458 c.Assert(s.requests[2].URL.Path, gc.Equals, securityRulePath("machine-0-udp-1000-2000")) 459 assertRequestBody(c, s.requests[2], &network.SecurityRule{ 460 Properties: &network.SecurityRulePropertiesFormat{ 461 Description: to.StringPtr("1000-2000/udp"), 462 Protocol: network.UDP, 463 SourcePortRange: to.StringPtr("*"), 464 SourceAddressPrefix: to.StringPtr("*"), 465 DestinationPortRange: to.StringPtr("1000-2000"), 466 DestinationAddressPrefix: to.StringPtr("10.0.0.4"), 467 Access: network.Allow, 468 Priority: to.Int32Ptr(201), 469 Direction: network.Inbound, 470 }, 471 }) 472 } 473 474 func (s *instanceSuite) TestInstanceOpenPortsAlreadyOpen(c *gc.C) { 475 internalSubnetId := path.Join( 476 "/subscriptions", fakeSubscriptionId, 477 "resourceGroups/juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d", 478 "providers/Microsoft.Network/virtualnetworks/juju-internal-network/subnets/juju-internal-subnet", 479 ) 480 ipConfiguration := network.InterfaceIPConfiguration{ 481 Properties: &network.InterfaceIPConfigurationPropertiesFormat{ 482 Primary: to.BoolPtr(true), 483 PrivateIPAddress: to.StringPtr("10.0.0.4"), 484 Subnet: &network.Subnet{ 485 ID: to.StringPtr(internalSubnetId), 486 }, 487 }, 488 } 489 s.networkInterfaces = []network.Interface{ 490 makeNetworkInterface("nic-0", "machine-0", ipConfiguration), 491 } 492 493 inst := s.getInstance(c) 494 okSender := mocks.NewSender() 495 okSender.AppendResponse(mocks.NewResponseWithContent("{}")) 496 nsgSender := networkSecurityGroupSender([]network.SecurityRule{{ 497 Name: to.StringPtr("machine-0-tcp-1000"), 498 Properties: &network.SecurityRulePropertiesFormat{ 499 Protocol: network.Asterisk, 500 DestinationPortRange: to.StringPtr("1000"), 501 Access: network.Allow, 502 Priority: to.Int32Ptr(202), 503 Direction: network.Inbound, 504 }, 505 }}) 506 s.sender = azuretesting.Senders{nsgSender, okSender, okSender} 507 508 err := inst.OpenPorts("0", []jujunetwork.PortRange{{ 509 Protocol: "tcp", 510 FromPort: 1000, 511 ToPort: 1000, 512 }, { 513 Protocol: "udp", 514 FromPort: 1000, 515 ToPort: 2000, 516 }}) 517 c.Assert(err, jc.ErrorIsNil) 518 519 c.Assert(s.requests, gc.HasLen, 2) 520 c.Assert(s.requests[0].Method, gc.Equals, "GET") 521 c.Assert(s.requests[0].URL.Path, gc.Equals, internalSecurityGroupPath) 522 c.Assert(s.requests[1].Method, gc.Equals, "PUT") 523 c.Assert(s.requests[1].URL.Path, gc.Equals, securityRulePath("machine-0-udp-1000-2000")) 524 assertRequestBody(c, s.requests[1], &network.SecurityRule{ 525 Properties: &network.SecurityRulePropertiesFormat{ 526 Description: to.StringPtr("1000-2000/udp"), 527 Protocol: network.UDP, 528 SourcePortRange: to.StringPtr("*"), 529 SourceAddressPrefix: to.StringPtr("*"), 530 DestinationPortRange: to.StringPtr("1000-2000"), 531 DestinationAddressPrefix: to.StringPtr("10.0.0.4"), 532 Access: network.Allow, 533 Priority: to.Int32Ptr(200), 534 Direction: network.Inbound, 535 }, 536 }) 537 } 538 539 func (s *instanceSuite) TestInstanceOpenPortsNoInternalAddress(c *gc.C) { 540 err := s.getInstance(c).OpenPorts("0", nil) 541 c.Assert(err, gc.ErrorMatches, "internal network address not found") 542 } 543 544 func (s *instanceSuite) TestAllInstances(c *gc.C) { 545 s.sender = s.getInstancesSender() 546 instances, err := s.env.AllInstances() 547 c.Assert(err, jc.ErrorIsNil) 548 c.Assert(instances, gc.HasLen, 2) 549 c.Assert(instances[0].Id(), gc.Equals, instance.Id("machine-0")) 550 c.Assert(instances[1].Id(), gc.Equals, instance.Id("machine-1")) 551 } 552 553 func (s *instanceSuite) TestControllerInstances(c *gc.C) { 554 *(*(*s.deployments[0].Properties.Dependencies)[0].DependsOn)[0].ResourceName = "juju-controller" 555 s.sender = s.getInstancesSender() 556 ids, err := s.env.ControllerInstances("foo") 557 c.Assert(err, jc.ErrorIsNil) 558 c.Assert(ids, gc.HasLen, 1) 559 c.Assert(ids[0], gc.Equals, instance.Id("machine-0")) 560 } 561 562 var internalSecurityGroupPath = path.Join( 563 "/subscriptions", fakeSubscriptionId, 564 "resourceGroups", "juju-testenv-model-"+testing.ModelTag.Id(), 565 "providers/Microsoft.Network/networkSecurityGroups/juju-internal-nsg", 566 ) 567 568 func securityRulePath(ruleName string) string { 569 return path.Join(internalSecurityGroupPath, "securityRules", ruleName) 570 }