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