/**
  * Loads the security group to be shared between nodes in the same application in the given
  * Location. If no such security group exists it is created.
  *
  * @param location The location in which the security group will be found
  * @param securityApi The API to use to list and create security groups
  * @return the security group to share between instances in the given location in this application
  */
 private SecurityGroup getOrCreateSharedSecurityGroup(
     Location location, SecurityGroupExtension securityApi) {
   final String groupName = getNameForSharedSecurityGroup();
   // Could sort-and-search if straight search is too expensive
   Optional<SecurityGroup> shared =
       Iterables.tryFind(
           securityApi.listSecurityGroupsInLocation(location),
           new Predicate<SecurityGroup>() {
             @Override
             public boolean apply(final SecurityGroup input) {
               // endsWith because Jclouds prepends 'jclouds#' to security group names.
               return input.getName().endsWith(groupName);
             }
           });
   if (shared.isPresent()) {
     LOG.info(
         "Found existing shared security group in {} for app {}: {}",
         new Object[] {location, applicationId, groupName});
     return shared.get();
   } else {
     LOG.info(
         "Creating new shared security group in {} for app {}: {}",
         new Object[] {location, applicationId, groupName});
     return createBaseSecurityGroupInLocation(groupName, location, securityApi);
   }
 }
  /**
   * Loads the security groups attached to the node with the given ID and returns the group that is
   * unique to the node, per the application context. This method will also update {@link
   * #sharedGroupCache} if no mapping for the shared group's location previously existed (e.g.
   * Brooklyn was restarted and rebound to an existing application).
   *
   * <p>Notice that jclouds will attach 2 securityGroups to the node if the locationId is `aws-ec2`
   * so it needs to look for the uniqueSecurityGroup rather than the shared securityGroup.
   *
   * @param nodeId The id of the node in question
   * @param locationId The id of the location in question
   * @param securityApi The API to use to list security groups
   * @return the security group unique to the given node, or null if one could not be determined.
   */
  private SecurityGroup getUniqueSecurityGroupForNodeCachingSharedGroupIfPreviouslyUnknown(
      String nodeId, String locationId, SecurityGroupExtension securityApi) {
    Set<SecurityGroup> groupsOnNode = securityApi.listSecurityGroupsForNode(nodeId);

    if (groupsOnNode == null || groupsOnNode.isEmpty()) {
      return null;
    }

    SecurityGroup unique;
    if (locationId.equals("aws-ec2")) {
      if (groupsOnNode.size() == 2) {
        String expectedSharedName = getNameForSharedSecurityGroup();
        Iterator<SecurityGroup> it = groupsOnNode.iterator();
        SecurityGroup shared = it.next();
        if (shared.getName().endsWith(expectedSharedName)) {
          unique = it.next();
        } else {
          unique = shared;
          shared = it.next();
        }
        if (!shared.getName().endsWith(expectedSharedName)) {
          LOG.warn(
              "Couldn't determine which security group is shared between instances in app {}. Expected={}, found={}",
              new Object[] {applicationId, expectedSharedName, groupsOnNode});
          return null;
        }
        // Shared entry might be missing if Brooklyn has rebound to an application
        SecurityGroup old = sharedGroupCache.asMap().putIfAbsent(shared.getLocation(), shared);
        LOG.info(
            "Loaded unique security group for node {} (in {}): {}",
            new Object[] {nodeId, applicationId, unique});
        if (old == null) {
          LOG.info("Proactively set shared group for app {} to: {}", applicationId, shared);
        }
        return unique;
      } else {
        LOG.warn(
            "Expected to find two security groups on node {} in app {} (one shared, one unique). Found {}: {}",
            new Object[] {nodeId, applicationId, groupsOnNode.size(), groupsOnNode});
      }
    }
    return Iterables.getOnlyElement(groupsOnNode);
  }