/**
   * Creates a security group with rules to:
   *
   * <ul>
   *   <li>Allow SSH access on port 22 from the world
   *   <li>Allow TCP, UDP and ICMP communication between machines in the same group
   * </ul>
   *
   * It needs to consider locationId as port ranges and groupId are cloud provider-dependent e.g
   * openstack nova wants from 1-65535 while aws-ec2 accepts from 0-65535.
   *
   * @param groupName The name of the security group to create
   * @param location The location in which the security group will be created
   * @param securityApi The API to use to create the security group
   * @return the created security group
   */
  private SecurityGroup createBaseSecurityGroupInLocation(
      String groupName, Location location, SecurityGroupExtension securityApi) {
    SecurityGroup group = addSecurityGroupInLocation(groupName, location, securityApi);

    String groupId = group.getProviderId();
    int fromPort = 0;
    if (isOpenstackNova(location)) {
      groupId = group.getId();
      fromPort = 1;
    }
    // Note: For groupName to work with GCE we also need to tag the machines with the same ID.
    // See sourceTags section at https://developers.google.com/compute/docs/networking#firewalls
    IpPermission.Builder allWithinGroup =
        IpPermission.builder().groupId(groupId).fromPort(fromPort).toPort(65535);
    addPermission(allWithinGroup.ipProtocol(IpProtocol.TCP).build(), group, securityApi);
    addPermission(allWithinGroup.ipProtocol(IpProtocol.UDP).build(), group, securityApi);
    if (!isAzure(location)) {
      addPermission(
          allWithinGroup.ipProtocol(IpProtocol.ICMP).fromPort(-1).toPort(-1).build(),
          group,
          securityApi);
    }

    IpPermission sshPermission =
        IpPermission.builder()
            .fromPort(22)
            .toPort(22)
            .ipProtocol(IpProtocol.TCP)
            .cidrBlock(getBrooklynCidrBlock())
            .build();
    addPermission(sshPermission, group, securityApi);

    return group;
  }
  /**
   * Applies the given security group permissions to the given node with the given compute service.
   *
   * <p>Takes no action if the compute service does not have a security group extension.
   *
   * @param permissions The set of permissions to be applied to the node
   * @param nodeId The id of the node to update
   * @param computeService The compute service to use to apply the changes
   */
  @VisibleForTesting
  Map<String, SecurityGroup> addPermissionsToLocation(
      Iterable<IpPermission> permissions, final String nodeId, ComputeService computeService) {
    if (!computeService.getSecurityGroupExtension().isPresent()) {
      LOG.warn(
          "Security group extension for {} absent; cannot update node {} with {}",
          new Object[] {computeService, nodeId, permissions});
      return ImmutableMap.of();
    }
    final SecurityGroupExtension securityApi = computeService.getSecurityGroupExtension().get();
    final String locationId = computeService.getContext().unwrap().getId();

    // Expect to have two security groups on the node: one shared between all nodes in the location,
    // that is cached in sharedGroupCache, and one created by Jclouds that is unique to the node.
    // Relies on customize having been called before. This should be safe because the arguments
    // needed to call this method are not available until post-instance creation.
    SecurityGroup machineUniqueSecurityGroup = getSecurityGroup(nodeId, securityApi, locationId);
    MutableList<IpPermission> newPermissions = MutableList.copyOf(permissions);
    Iterables.removeAll(newPermissions, machineUniqueSecurityGroup.getIpPermissions());
    MutableMap<String, SecurityGroup> addedSecurityGroups = MutableMap.of();
    for (IpPermission permission : newPermissions) {
      SecurityGroup addedPermission =
          addPermission(permission, machineUniqueSecurityGroup, securityApi);
      addedSecurityGroups.put(addedPermission.getId(), addedPermission);
    }
    return addedSecurityGroups;
  }
  @Test
  public void testApply() {
    IpPermissions authorization = IpPermissions.permitAnyProtocol();

    org.jclouds.ec2.domain.SecurityGroup origGroup =
        org.jclouds.ec2.domain.SecurityGroup.builder()
            .region("us-east-1")
            .id("some-id")
            .name("some-group")
            .ownerId("some-owner")
            .description("some-description")
            .ipPermission(authorization)
            .build();

    AWSEC2SecurityGroupToSecurityGroup parser = createGroupParser(ImmutableSet.of(provider));

    SecurityGroup group = parser.apply(origGroup);

    assertEquals(group.getLocation(), provider);
    assertEquals(group.getId(), provider.getId() + "/" + origGroup.getId());
    assertEquals(group.getProviderId(), origGroup.getId());
    assertEquals(group.getName(), origGroup.getName());
    assertEquals(group.getIpPermissions(), (Set<IpPermission>) origGroup);
    assertEquals(group.getOwnerId(), origGroup.getOwnerId());
  }
  /**
   * 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);
  }
  private void setSecurityGroupOnTemplate(
      final JcloudsLocation location,
      final Template template,
      final SecurityGroupExtension securityApi) {
    SecurityGroup shared;
    Tasks.setBlockingDetails(
        "Loading security group shared by instances in "
            + template.getLocation()
            + " in app "
            + applicationId);
    try {
      shared =
          sharedGroupCache.get(
              template.getLocation(),
              new Callable<SecurityGroup>() {
                @Override
                public SecurityGroup call() throws Exception {
                  return getOrCreateSharedSecurityGroup(template.getLocation(), securityApi);
                }
              });
    } catch (ExecutionException e) {
      throw Throwables.propagate(new Exception(e.getCause()));
    } finally {
      Tasks.resetBlockingDetails();
    }

    Set<String> originalGroups = template.getOptions().getGroups();
    template.getOptions().securityGroups(shared.getName());
    if (!originalGroups.isEmpty()) {
      LOG.info(
          "Replaced configured security groups: configured={}, replaced with={}",
          originalGroups,
          template.getOptions().getGroups());
    } else {
      LOG.debug(
          "Configured security groups at {} to: {}", location, template.getOptions().getGroups());
    }
  }
 protected SecurityGroup removePermission(
     final IpPermission permission,
     final SecurityGroup group,
     final SecurityGroupExtension securityApi) {
   LOG.debug("Removing permission from security group {}: {}", group.getName(), permission);
   Callable<SecurityGroup> callable =
       new Callable<SecurityGroup>() {
         @Override
         public SecurityGroup call() throws Exception {
           return securityApi.removeIpPermission(permission, group);
         }
       };
   return runOperationWithRetry(callable);
 }
 protected SecurityGroup addPermission(
     final IpPermission permission,
     final SecurityGroup group,
     final SecurityGroupExtension securityApi) {
   LOG.debug("Adding permission to security group {}: {}", group.getName(), permission);
   Callable<SecurityGroup> callable =
       new Callable<SecurityGroup>() {
         @Override
         public SecurityGroup call() throws Exception {
           try {
             return securityApi.addIpPermission(permission, group);
           } catch (AWSResponseException e) {
             if ("InvalidPermission.Duplicate".equals(e.getError().getCode())) {
               // already exists
               LOG.info(
                   "Permission already exists for security group; continuing (logging underlying exception at debug): permission="
                       + permission
                       + "; group="
                       + group);
               LOG.debug(
                   "Permission already exists for security group; continuing: permission="
                       + permission
                       + "; group="
                       + group,
                   e);
               return null;
             } else {
               throw e;
             }
           } catch (Exception e) {
             Exceptions.propagateIfFatal(e);
             if (e.toString().contains("InvalidPermission.Duplicate")) {
               // belt-and-braces, in case
               // already exists
               LOG.info(
                   "Permission already exists for security group; continuing (but unexpected exception type): permission="
                       + permission
                       + "; group="
                       + group,
                   e);
               return null;
             } else {
               throw Exceptions.propagate(e);
             }
           }
         }
       };
   return runOperationWithRetry(callable);
 }
  @Test
  public void testApplyWithGroup() {
    NovaSecurityGroupInZoneToSecurityGroup parser = createGroupParser();

    SecurityGroupInZone origGroup = new SecurityGroupInZone(securityGroupWithGroup(), zone.getId());

    SecurityGroup newGroup = parser.apply(origGroup);

    assertEquals(
        newGroup.getId(), origGroup.getZone() + "/" + origGroup.getSecurityGroup().getId());
    assertEquals(newGroup.getProviderId(), origGroup.getSecurityGroup().getId());
    assertEquals(newGroup.getName(), origGroup.getSecurityGroup().getName());
    assertEquals(newGroup.getOwnerId(), origGroup.getSecurityGroup().getTenantId());
    assertEquals(
        newGroup.getIpPermissions(),
        ImmutableSet.copyOf(
            transform(
                origGroup.getSecurityGroup().getRules(),
                NovaSecurityGroupToSecurityGroupTest.ruleConverter)));
    assertEquals(newGroup.getLocation().getId(), origGroup.getZone());
  }