[ https://issues.apache.org/jira/browse/JCLOUDS-1235?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=15854616#comment-15854616 ]
Geoff Macartney commented on JCLOUDS-1235: ------------------------------------------ I have raised a candidate fix for this in https://github.com/jclouds/jclouds/pull/1059. > openstack-nova: list-security-groups request is slow, with O(n^2) behaviour > --------------------------------------------------------------------------- > > Key: JCLOUDS-1235 > URL: https://issues.apache.org/jira/browse/JCLOUDS-1235 > Project: jclouds > Issue Type: Bug > Components: jclouds-compute > Affects Versions: 2.0.0 > Reporter: Geoff Macartney > > On an Openstack cloud with more than a trivial number of security groups, the > “list security groups” operation takes a very long time, displaying an > “O(n^2)” response behaviour. For example a simple live test like this, > against an Openstack deployment I am using which has around 160 security > groups, some with up to about 40 rules permitting access from other groups: > {code} > @Test(groups = { "integration", "live" }, singleThreaded = true) > public void testListSecurityGroups() throws RunNodesException, > InterruptedException, ExecutionException { > skipIfSecurityGroupsNotSupported(); > ComputeService computeService = view.getComputeService(); > Optional<SecurityGroupExtension> securityGroupExtension = > computeService.getSecurityGroupExtension(); > assertTrue(securityGroupExtension.isPresent(), "security extension > was not present"); > Set<SecurityGroup> groups = > securityGroupExtension.get().listSecurityGroups(); > System.out.println(groups.size()); > for (SecurityGroup group : groups) { > System.out.println(group); > } > } > {code} > ran for up to an hour before I gave up waiting. The problem can be seen in > the following stack trace: > {code} > at > sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1440) > at > sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254) > - locked <0x170a> (a > sun.net.www.protocol.https.HttpsURLConnectionImpl) > at > org.jclouds.http.internal.JavaUrlHttpCommandExecutorService.invoke(JavaUrlHttpCommandExecutorService.java:97) > at > org.jclouds.http.internal.JavaUrlHttpCommandExecutorService.invoke(JavaUrlHttpCommandExecutorService.java:65) > at > org.jclouds.http.internal.BaseHttpCommandExecutorService.invoke(BaseHttpCommandExecutorService.java:100) > at > org.jclouds.rest.internal.InvokeHttpMethod.invoke(InvokeHttpMethod.java:90) > at > org.jclouds.rest.internal.InvokeHttpMethod.apply(InvokeHttpMethod.java:73) > at > org.jclouds.rest.internal.InvokeHttpMethod.apply(InvokeHttpMethod.java:44) > at > org.jclouds.reflect.FunctionalReflection$FunctionalInvocationHandler.handleInvocation(FunctionalReflection.java:117) > at > com.google.common.reflect.AbstractInvocationHandler.invoke(AbstractInvocationHandler.java:87) > at com.sun.proxy.$Proxy88.list(Unknown Source:-1) > at > org.jclouds.openstack.nova.v2_0.predicates.FindSecurityGroupWithNameAndReturnTrue.apply(FindSecurityGroupWithNameAndReturnTrue.java:66) > at > org.jclouds.openstack.nova.v2_0.predicates.FindSecurityGroupWithNameAndReturnTrue.apply(FindSecurityGroupWithNameAndReturnTrue.java:44) > at > org.jclouds.util.Predicates2$RetryablePredicate.apply(Predicates2.java:117) > at > org.jclouds.openstack.nova.v2_0.compute.functions.SecurityGroupRuleToIpPermission$1.apply(SecurityGroupRuleToIpPermission.java:92) > at > org.jclouds.openstack.nova.v2_0.compute.functions.SecurityGroupRuleToIpPermission$1.apply(SecurityGroupRuleToIpPermission.java:87) > at > com.google.common.collect.Iterators$7.computeNext(Iterators.java:652) > at > com.google.common.collect.AbstractIterator.tryToComputeNext(AbstractIterator.java:143) > at > com.google.common.collect.AbstractIterator.hasNext(AbstractIterator.java:138) > at com.google.common.collect.Iterators.getNext(Iterators.java:865) > at com.google.common.collect.Iterables.getFirst(Iterables.java:775) > at > org.jclouds.openstack.nova.v2_0.compute.functions.SecurityGroupRuleToIpPermission.apply(SecurityGroupRuleToIpPermission.java:73) > at > org.jclouds.openstack.nova.v2_0.compute.functions.SecurityGroupRuleToIpPermission.apply(SecurityGroupRuleToIpPermission.java:48) > at com.google.common.collect.Iterators$8.transform(Iterators.java:799) > at > com.google.common.collect.TransformedIterator.next(TransformedIterator.java:48) > at > com.google.common.collect.ImmutableCollection$Builder.addAll(ImmutableCollection.java:281) > at > com.google.common.collect.ImmutableCollection$ArrayBasedBuilder.addAll(ImmutableCollection.java:360) > at > com.google.common.collect.ImmutableSet$Builder.addAll(ImmutableSet.java:508) > at > org.jclouds.compute.domain.SecurityGroupBuilder.ipPermissions(SecurityGroupBuilder.java:43) > at > org.jclouds.openstack.nova.v2_0.compute.functions.NovaSecurityGroupToSecurityGroup.apply(NovaSecurityGroupToSecurityGroup.java:61) > at > org.jclouds.openstack.nova.v2_0.compute.functions.NovaSecurityGroupToSecurityGroup.apply(NovaSecurityGroupToSecurityGroup.java:39) > at > org.jclouds.openstack.nova.v2_0.compute.functions.NovaSecurityGroupInRegionToSecurityGroup.apply(NovaSecurityGroupInRegionToSecurityGroup.java:61) > at > org.jclouds.openstack.nova.v2_0.compute.functions.NovaSecurityGroupInRegionToSecurityGroup.apply(NovaSecurityGroupInRegionToSecurityGroup.java:43) > at com.google.common.collect.Iterators$8.transform(Iterators.java:799) > at > com.google.common.collect.TransformedIterator.next(TransformedIterator.java:48) > at > com.google.common.collect.ImmutableCollection$Builder.addAll(ImmutableCollection.java:301) > at > com.google.common.collect.ImmutableSet$Builder.addAll(ImmutableSet.java:522) > at > com.google.common.collect.ImmutableSet.copyOf(ImmutableSet.java:321) > at > com.google.common.collect.ImmutableSet.copyOf(ImmutableSet.java:300) > at > org.jclouds.openstack.nova.v2_0.compute.extensions.NovaSecurityGroupExtension.listSecurityGroupsInLocation(NovaSecurityGroupExtension.java:116) > at > org.jclouds.openstack.nova.v2_0.compute.extensions.NovaSecurityGroupExtension.listSecurityGroupsInLocation(NovaSecurityGroupExtension.java:109) > {code} > The {{listSecurityGroupsInRegion}} operation invokes > {{pollSecurityGroupsByRegion}} > which lists all security groups > https://github.com/apache/jclouds/blob/rel/jclouds-2.0.0/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtension.java#L362 > {code} > return sgApi.get().list().transform(groupToGroupInRegion(from)).toSet(); > {code} > and then it transforms each in turn from a Nova object to a jclouds > {{SecurityGroup}} using the injected {{groupConverter}} > https://github.com/apache/jclouds/blob/rel/jclouds-2.0.0/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtension.java#L114-L115 > {code} > Iterable<SecurityGroup> groups = transform(filter(rawGroups, notNull()), > groupConverter); > {code} > The converter is {{NovaSecurityGroupInRegionToSecurityGroup}} which > implements {{Function<SecurityGroupInRegion, SecurityGroup>}} > This is where the trouble arises - this signature transforms one Nova group > to its jclouds representation independently of any others. However, because > a security groups’s ingress rules can refer to other groups, it’s not > actually possible to do this; you need information about the other groups > that may be referred to in the ingress rules. > Hence in the converter’s operation, where it delegates to > {{NovaSecurityGroupToSecurityGroup}} > https://github.com/apache/jclouds/blob/rel/jclouds-2.0.0/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupToSecurityGroup.java#L61 > That class does the conversion of each ingress rule permitting access to a > group using an injected {{Function<SecurityGroupRule, IpPermission>}} > converter, {{ruleToPermission}}: > {code} > builder.ipPermissions(transform(group.getRules(), ruleToPermission)); > {code} > This rule converter is {{SecurityGroupRuleToIpPermission}}, which, _for every > such rule_, checks it against _every location_ with a predicate > {{isSecurityGroupInRegion}} > > https://github.com/apache/jclouds/blob/rel/jclouds-2.0.0/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/SecurityGroupRuleToIpPermission.java#L73-L74 > {code} > String region = getFirst(filter(locationIndex.get().keySet(), > isSecurityGroupInRegion(rule.getGroup().getName())), null); > {code} > This method works using a predicate {{returnSecurityGroupExistsInRegion}} > that is again injected by Guice, from > {{FindSecurityGroupWithNameAndReturnTrue}}. There is no information > available to this class about the list of security groups that was fetched at > the start of this process, so it must find the security group with the name > by doing _another listing of the security groups_, giving the n^2 behaviour, > and then checking the group name against each result. > https://github.com/apache/jclouds/blob/rel/jclouds-2.0.0/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/predicates/FindSecurityGroupWithNameAndReturnTrue.java#L66 > {code} > SecurityGroup returnVal = Iterables.find(api.get().list(), new > Predicate<SecurityGroup>() { > {code} > Thus we have “for every group * every ingress-rule-from-group * every > location * every group”. -- This message was sent by Atlassian JIRA (v6.3.15#6346)