@Test
  public void testAggregatingMembersEnricher() throws Exception {
    origApp.start(ImmutableList.of(origLoc));
    origCluster.resize(2);

    origApp.addEnricher(
        Enrichers.builder()
            .aggregating(METRIC1)
            .from(origCluster)
            .fromMembers()
            .computing(StringFunctions.joiner(","))
            .publishing(METRIC2)
            .build());

    TestApplication newApp = rebind();
    DynamicCluster newCluster =
        (DynamicCluster)
            Iterables.find(newApp.getChildren(), Predicates.instanceOf(DynamicCluster.class));

    int i = 1;
    for (Entity member : newCluster.getMembers()) {
      ((EntityInternal) member).setAttribute(METRIC1, "myval" + (i++));
    }
    EntityTestUtils.assertAttributeEventually(
        newApp,
        METRIC2,
        Predicates.or(Predicates.equalTo("myval1,myval2"), Predicates.equalTo("myval2,myval1")));
  }
  @SuppressWarnings("unchecked")
  @Test
  public void testCombiningEnricher() throws Exception {
    origApp.addEnricher(
        Enrichers.builder()
            .combining(METRIC1, METRIC2)
            .from(origEntity)
            .computing(StringFunctions.joiner(","))
            .publishing(METRIC2)
            .build());

    TestApplication newApp = rebind();
    TestEntity newEntity =
        (TestEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(TestEntity.class));

    newEntity.setAttribute(METRIC1, "myval");
    newEntity.setAttribute(METRIC2, "myval2");
    EntityTestUtils.assertAttributeEventually(
        newApp,
        METRIC2,
        Predicates.or(Predicates.equalTo("myval,myval2"), Predicates.equalTo("myval2,myval")));
  }
  /*
   * TODO this is much messier than we would like because postgres runs as user postgres,
   * meaning the dirs must be RW by that user, and accessible (thus all parent paths),
   * which may rule out putting it in a location used by the default user.
   * Two irritating things:
   * * currently we sometimes make up a different onbox base dir;
   * * currently we put files to /tmp for staging
   * Could investigate if it really needs to run as user postgres;
   * could also see whether default user can be added to group postgres,
   * and the run dir (and all parents) made accessible to group postgres.
   */
  @Override
  public void install() {
    String version = getEntity().getConfig(SoftwareProcess.SUGGESTED_VERSION);
    String majorMinorVersion = version.substring(0, version.lastIndexOf("-"));
    String shortVersion = majorMinorVersion.replace(".", "");

    String altTarget = "/opt/brooklyn/postgres/";
    String altInstallDir = Urls.mergePaths(altTarget, "install/" + majorMinorVersion);

    Iterable<String> pgctlLocations =
        ImmutableList.of(
            altInstallDir + "/bin",
            "/usr/lib/postgresql/" + majorMinorVersion + "/bin/",
            "/opt/local/lib/postgresql" + shortVersion + "/bin/",
            "/usr/pgsql-" + majorMinorVersion + "/bin",
            "/usr/local/bin/",
            "/usr/bin/",
            "/bin/");

    DynamicTasks.queueIfPossible(
            SshTasks.dontRequireTtyForSudo(
                getMachine(),
                // sudo is absolutely required here, in customize we set user to postgres
                OnFailingTask.FAIL))
        .orSubmitAndBlock();
    DynamicTasks.waitForLast();

    // Check whether we can find a usable pg_ctl, and if not install one
    MutableList<String> findOrInstall =
        MutableList.<String>of()
            .append("which pg_ctl")
            .appendAll(
                Iterables.transform(pgctlLocations, StringFunctions.formatter("test -x %s/pg_ctl")))
            .append(
                installPackage(
                    ImmutableMap.of(
                        "yum",
                            "postgresql" + shortVersion + " postgresql" + shortVersion + "-server",
                        "apt", "postgresql-" + majorMinorVersion,
                        "port",
                            "postgresql" + shortVersion + " postgresql" + shortVersion + "-server"),
                    null))
            // due to impl of installPackage, it will not come to the line below I don't think
            .append(
                warn(
                    format(
                        "WARNING: failed to find or install postgresql %s binaries",
                        majorMinorVersion)));

    // Link to correct binaries folder (different versions of pg_ctl and psql don't always play well
    // together)
    MutableList<String> linkFromHere =
        MutableList.<String>of()
            .append(
                ifExecutableElse1(
                    "pg_ctl",
                    chainGroup(
                        "PG_EXECUTABLE=`which pg_ctl`",
                        "PG_DIR=`dirname $PG_EXECUTABLE`",
                        "echo 'found pg_ctl in '$PG_DIR' on path so linking PG bin/ to that dir'",
                        "ln -s $PG_DIR bin")))
            .appendAll(
                Iterables.transform(
                    pgctlLocations, givenDirIfFileExistsInItLinkToDir("pg_ctl", "bin")))
            .append(
                fail(
                    format(
                        "WARNING: failed to find postgresql %s binaries for pg_ctl, may already have another version installed; aborting",
                        majorMinorVersion),
                    9));

    newScript(INSTALLING)
        .body
        .append(
            dontRequireTtyForSudo(),
            ifExecutableElse0("yum", getYumRepository(version, majorMinorVersion, shortVersion)),
            ifExecutableElse0("apt-get", getAptRepository()),
            "rm -f bin", // if left over from previous incomplete/failed install (not sure why that
                         // keeps happening!)
            alternativesGroup(findOrInstall),
            alternativesGroup(linkFromHere))
        .failOnNonZeroResultCode()
        .queue();

    // check that the proposed install dir is one that user postgres can access
    // TODO if command above fails then the following `getUnchecked` blocks forever
    if (DynamicTasks.queue(
                SshEffectorTasks.ssh(sudoAsUser("postgres", "ls " + getInstallDir()))
                    .allowingNonZeroExitCode()
                    .summary("check postgres user can access install dir"))
            .asTask()
            .getUnchecked()
        != 0) {
      log.info(
          "Postgres install dir "
              + getInstallDir()
              + " for "
              + getEntity()
              + " is not accessible to user 'postgres'; "
              + "using "
              + altInstallDir
              + " instead");
      String newRunDir =
          Urls.mergePaths(
              altTarget, "apps", getEntity().getApplication().getId(), getEntity().getId());
      if (DynamicTasks.queue(
                  SshEffectorTasks.ssh("ls " + altInstallDir + "/pg_ctl")
                      .allowingNonZeroExitCode()
                      .summary("check whether " + altInstallDir + " is set up"))
              .asTask()
              .getUnchecked()
          == 0) {
        // alt target already exists with binary; nothing to do for install
      } else {
        DynamicTasks.queue(
            SshEffectorTasks.ssh(
                    "mkdir -p " + altInstallDir,
                    "rm -rf '" + altInstallDir + "'",
                    "mv " + getInstallDir() + " " + altInstallDir,
                    "rm -rf '" + getInstallDir() + "'",
                    "ln -s " + altInstallDir + " " + getInstallDir(),
                    "mkdir -p " + newRunDir,
                    "chown -R postgres:postgres " + altTarget)
                .runAsRoot()
                .requiringExitCodeZero()
                .summary("move install dir from user to postgres owned space"));
      }
      DynamicTasks.waitForLast();
      setInstallDir(altInstallDir);
      setRunDir(newRunDir);
    }
  }