@Unstable
public interface AuthorityDescriptor {

  /**
   * Optional. Which authority is required for adding or removing.
   */
  @Unstable
  String getAuthorityForAddRemove();

  /**
   * Optional. The default authority for Parameters.
   */
  @Unstable
  String getDefaultAuthorityForParameters();

  /**
   * Optional. Which authority is required for start/stop.
   */
  @Unstable
  String getAuthorityForPowerState();
} public enum CsdConfigEntryType {
  /**
   * Value is added as-is or interpolated if subsitution variables are used.
   * Value field is required for this type.
   */
  SIMPLE,
  /**
   * Value is computed by using Auth-to-Local rules configuration properties
   * of the DFS service that is a dependency/dependent of the service.
   * Value field is ignored in this case.
   */
  AUTH_TO_LOCAL;

  @JsonValue
  public String toJson() {
    return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name());
  }
} public enum CsdLoggingType {
  /**
   * CM auto-generates the following ParamSpecs for a daemon role that uses this method:
   * <ol>
   * <li>Log threshold
   * <li>Max file size
   * <li>Max backup index size
   * <li>Log4j safety valve
   * </ol>
   * <p>
   * CM auto-generates the following ParamSpecs for a gateway role that uses this method:
   * <ol>
   * <li>Log threshold
   * <li>Log4j safety valve
   * </ol>
   * <p>
   * It also creates a ConfigFileGenerator for log4j.properties file for the role.
   */
  LOG4J("log4j.properties"),
  /**
   * CM auto-generates the following ParamSpecs for a daemon role that uses this method:
   * <ol>
   * <li>Log threshold
   * <li>Max file size
   * <li>Max backup index size
   * <li>Logback XML override
   * </ol>
   * <p>
   * CM auto-generates the following ParamSpecs for a gateway role that uses this method:
   * <ol>
   * <li>Log threshold
   * <li>Logback XML override
   * </ol>
   * <p>
   * It also creates a ConfigFileGenerator for logback.xml file for the role.
   */
  LOGBACK("logback.xml"),
  /**
   * CM auto-generates some ParamSpecs for a daemon role that uses this method.
   * Each ParamSpec will automatically be emitted into the environment. The
   * ParamSpecs are listed below with their corresponding environment variable
   * names in parentheses:
   * <ol>
   * <li>Log directory (GLOG_log_dir)
   * <li>Minimum log level (GLOG_minloglevel)
   * <li>Maximum log level to buffer (GLOG_logbuflevel)
   * <li>Minimum log verbosity (GLOG_v)
   * <li>Maximum log size (GLOG_max_log_size)
   * </ol>
   * <p>
   * When using glog-based logging, {@link LoggingDescriptor#getFilename()}
   * must end in ".INFO".
   */
  GLOG(null),
  /** CM doesn't do anything automatically for this logging type. */
  OTHER(null);

  private final String defaultLogConfigFilename;

  private CsdLoggingType(String defaultLogConfigFilename) {
    this.defaultLogConfigFilename = defaultLogConfigFilename;
  }

  /**
   * @return the default logging configuration filename for this logging type,
   *         or null if it is unknown.
   */
  public String getDefaultLogConfigFilename() {
    return defaultLogConfigFilename;
  }

  @JsonValue
  public String toJson() {
    return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name());
  }
} public enum CertificateFileFormat {
  /** Java Keystore or Truststore format. */
  JKS,
  /**
   * PEM format, single file for all keys. Can either be private key +
   * certificate in one file (equivalent to keystore), or several certificates
   * (equivalent to trust store).
   */
  PEM;

  @JsonValue
  public String toJson() {
    return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name());
  }
} public interface PlacementRuleDescriptor {

  public interface AlwaysWithRule extends PlacementRuleDescriptor {
    /**
     * Role type that determines where this role must always be located.
     * In wizards, this role will not be visible for manual assignment.
     * This role will automatically be assigned to the same host as
     * any roles of the specified type.
     */
    @NotNull
    @Referencing(type=ReferenceType.ROLE)
    String getRoleType();
  }

  public interface NeverWithRule extends PlacementRuleDescriptor {
    /**
     * Role types that cannot be placed on the same host as this role.
     */
    @NotNull
    @Referencing(type=ReferenceType.ROLE)
    List<String> getRoleTypes();
  }
} public interface AlwaysWithRule extends PlacementRuleDescriptor {
  /**
   * Role type that determines where this role must always be located.
   * In wizards, this role will not be visible for manual assignment.
   * This role will automatically be assigned to the same host as
   * any roles of the specified type.
   */
  @NotNull
  @Referencing(type=ReferenceType.ROLE)
  String getRoleType();
} public interface NeverWithRule extends PlacementRuleDescriptor {
  /**
   * Role types that cannot be placed on the same host as this role.
   */
  @NotNull
  @Referencing(type=ReferenceType.ROLE)
  List<String> getRoleTypes();
} public interface RollingRestartDescriptor {

  /**
   * Specifies the steps performed to rolling restart non-worker roles. Roles of
   * different types are restarted in the order they are specified in here, and
   * within each role type, roles are ordered by their hostnames.
   */
  List<RollingRestartNonWorkerStepDescriptor> getNonWorkerSteps();

  /**
   * Specifies the steps performed to rolling restart worker roles. Worker roles
   * are restarted in batches and batch size is specified by the user while
   * triggering rolling restart.
   *
   * Since the commands are applied to multiple worker roles at the same time, they
   * should all be service level commands.
   */
  RollingRestartWorkerStepDescriptor getWorkerSteps();
} public interface RollingRestartNonWorkerStepDescriptor {

  /** Role for which the steps are applied during rolling restart. */
  @NotBlank
  @Referencing(type=ReferenceType.ROLE)
  String getRoleName();

  /**
   * List of role-level commands to run while bringing the role down during rolling
   * restart. If "Stop" is specified as one of the commands, the regular role
   * stop command is called.
   *
   * If this is not provided, role is simply stopped to bring it down.
   */
  @Referencing(type=ReferenceType.ROLE_COMMAND)
  List<String> getBringDownCommands();

  /**
   * List of role-level commands to run while bringing the role up during rolling restart.
   * If "Start" is specified as one of the commands, the regular role start
   * command is called.
   *
   * If this is not provided, role is simply started to bring it up.
   */
  @Referencing(type=ReferenceType.ROLE_COMMAND)
  List<String> getBringUpCommands();
} public interface RollingRestartWorkerStepDescriptor {

  /** Role for which the steps are applied during rolling restart. */
  @NotBlank
  @Referencing(type=ReferenceType.ROLE)
  String getRoleName();

  /**
   * List of service-level commands to run while bringing the role down during rolling
   * restart. If "Stop" is specified as one of the commands, the regular role
   * stop command is called.
   *
   * If this is not provided, role is simply stopped to bring it down.
   */
  @Referencing(type=ReferenceType.SERVICE_COMMAND)
  List<String> getBringDownCommands();

  /**
   * List of service-level commands to run while bringing the role up during rolling restart.
   * If "Start" is specified as one of the commands, the regular role start
   * command is called.
   *
   * If this is not provided, role is simply started to bring it up.
   */
  @Referencing(type=ReferenceType.SERVICE_COMMAND)
  List<String> getBringUpCommands();
} @Referenced(type = ReferenceType.PARAMETER, as = {
    "ssl_client_truststore_location", "ssl_client_truststore_password" })
public interface JksSslClientDescriptor extends SslClientDescriptor {
  // JKS-specific fields
  /**
   * Optional. Config name to emit when ssl_client_truststore_password is used
   * in a config file. If null, ssl_client_truststore_password will not be
   * emitted into config files, and can only be used in substitutions like
   * ${ssl_client_truststore_password}.
   * <p>
   * You must set this in order to use
   * truststorePasswordCredentialProviderCompatible or
   * truststorePasswordScriptBased.
   */
  String getTruststorePasswordConfigName();

  /**
   * Optional. Defaults to false. Whether ssl_client_truststore_password can
   * use the Credential Provider, a Hadoop mechanism that allows for the
   * encrypting of sensitive items in an encrypted store.
   * <p>
   * Has no effect on substitutions like ${ssl_client_truststore_password}
   * <p>
   * Requires truststorePasswordConfigName to be set. Mutually exclusive with
   * truststorePasswordScriptBased.
   */
  boolean isTruststorePasswordCredentialProviderCompatible();

  /**
   * Optional. Defaults to false. If true, the following things happen when used
   * in a configFile (not through substitutions like
   * ${ssl_client_truststore_password}):
   * <ul>
   * <li>1) The regular password for the truststore is no longer emitted</li>
   * <li>2) In its place, CM will emit the full path to a script, and that
   * script will echo the value of this desired password to stdout.</li>
   * </ul>
   * Requires truststorePasswordConfigName to be set.
   * <p>
   * Has no effect on substitutions like ${ssl_client_truststore_password}.
   * <p>
   * For this functionality to be useful, your code must run the script in the
   * parameter to get the real password.
   */
  boolean isTruststorePasswordScriptBased();
} @Referenced(type = ReferenceType.PARAMETER, as = {"ssl_enabled",
    "ssl_server_keystore_location", "ssl_server_keystore_password",
    // TODO we should only conditionally allow references to key password
    "ssl_server_keystore_keypassword" })
public interface JksSslServerDescriptor extends SslServerDescriptor {
  // fields that only apply to JKS format

  /**
   * Optional. Config name to emit when ssl_server_keystore_location is used in
   * a config file. If null, ssl_server_keystore_location will not be emitted
   * into config files, and can only be used in substitutions like
   * ${ssl_server_keystore_location}.
   */
  String getKeystoreLocationConfigName();

  /**
   * Optional. Default value for ssl_server_keystore_location.
   */
  String getKeystoreLocationDefault();

  /**
   * Optional. Config name to emit when ssl_server_keystore_password is used in
   * a config file. If null, ssl_server_keystore_password will not be emitted
   * into config files, and can only be used in substitutions like
   * ${ssl_server_keystore_password}.
   * <p>
   * You must set this in order to use
   * keystorePasswordCredentialProviderCompatible keystorePasswordScriptBased.
   */
  String getKeystorePasswordConfigName();

  /**
   * Optional. Defaults to false. Whether ssl_server_keystore_password can use
   * the Credential Provider, a Hadoop mechanism that allows for the encrypting
   * of sensitive items in an encrypted store.
   * <p>
   * Has no effect on substitutions like ${ssl_server_keystore_password}
   * <p>
   * Requires keystorePasswordConfigName to be set. Mutually exclusive with
   * keystorePasswordScriptBased.
   */
  boolean isKeystorePasswordCredentialProviderCompatible();

  /**
   * Optional. Defaults to false. If true, the following things happen when used
   * in a configFile (not through substitutions like
   * ${ssl_server_keystore_password}):
   * <ul>
   * <li>1) The regular password for the keystore is no longer emitted</li>
   * <li>2) In its place, CM will emit the full path to a script, and that
   * script will echo the value of this desired password to stdout.</li>
   * </ul>
   * Requires keystorePasswordConfigName to be set. Mutually exclusive with
   * keystorePasswordCredentialProviderCompatible.
   * <p>
   * Has no effect on substitutions like ${ssl_server_keystore_password}.
   * <p>
   * For this functionality to be useful, your code must run the script in the
   * parameter to get the real password.
   */
  boolean isKeystorePasswordScriptBased();

  /**
   * The identifier for this role's key in the keystore.
   */
  @NotBlank
  @AvailableSubstitutions(type = { PARAMETERS, HOST, USER, GROUP })
  String getKeyIdentifier();

  /**
   * Optional. Whether to allow and / or require the cluster administrator to
   * configure a password for the SSL server's key in the keystore, exposed as
   * parameter ssl_server_keystore_keypassword.
   *
   * @return parameter optionality, or null to indicate NOT_EXPOSED.
   */
  CsdParameterOptionality getKeyPasswordOptionality();

  /**
   * Optional. Config name to emit when ssl_server_keystore_keypassword is
   * used in a config file. If null, ssl_server_keystore_keypassword will not
   * be emitted into config files, and can only be used in substitutions like
   * ${ssl_server_keystore_keypassword}.
   * <p>
   * You must set this in order to use keystoreKeyPasswordScriptBased.
   */
  String getKeystoreKeyPasswordConfigName();

  /**
   * Optional. Defaults to false. Whether ssl_server_keystore_keypassword can
   * use the Credential Provider, a Hadoop mechanism that allows for the
   * encrypting of sensitive items in an encrypted store.
   * <p>
   * Has no effect on substitutions like ${ssl_server_keystore_keypassword}
   * <p>
   * Requires keystoreKeyPasswordConfigName to be set. Mutually exclusive with
   * keystoreKeyPasswordScriptBased.
   */
  boolean isKeystoreKeyPasswordCredentialProviderCompatible();

  /**
   * Optional. Defaults to false. If true, the following things happen when
   * used in a configFile (not through substitutions like
   * ${ssl_server_keystore_keypassword}):
   * <ul>
   * <li>1) The regular password for the keystore is no longer emitted</li>
   * <li>2) In its place, CM will emit the full path to a script, and that
   * script will echo the value of this desired password to stdout.</li>
   * </ul>
   * Requires keystoreKeyPasswordConfigName to be set. Mutually exclusive
   * with keystoreKeyPasswordCredentialProviderCompatible.
   * <p>
   * Has no effect on substitutions like ${ssl_server_keystore_keypassword}.
   * <p>
   * For this functionality to be useful, your code must run the script in the
   * parameter to get the real password.
   */
  boolean isKeystoreKeyPasswordScriptBased();
} @Named
public interface ServiceDescriptor {

  @EntityTypeFormat
  @UniqueServiceType
  String getName();

  @NotBlank
  String getLabel();

  @NotBlank
  String getDescription();

  @NotBlank
  String getVersion();

  @Valid
  CompatibilityDescriptor getCompatibility();

  @Min(1)
  Integer getMaxInstances();

  @Valid
  RunAs getRunAs();

  String getIcon();

  @Valid
  ParcelDescriptor getParcel();

  /**
   * When set to true, this service participates
   * in the express wizard.
   */
  boolean isInExpressWizard();

  /**
   * The list of roles that have external links
   * that should also be surfaced on the service.
   * It is recommended that this is a list of only
   * singleton/master roles.
   */
  @Referencing(type= ReferenceType.ROLE)
  Set<String> getRolesWithExternalLinks();

  @UniqueField("name")
  @Valid
  List<ServiceCommandDescriptor> getCommands();

  @Valid
  GracefulStopDescriptor getStopRunner();

  @UniqueField("name")
  @Valid
  List<RoleDescriptor> getRoles();

  @UniqueField.List({
    @UniqueField("name"),
    @UniqueField("configName")
  })
  @Valid
  List<Parameter<?>> getParameters();

  @UniqueField("name")
  @Valid
  List<ServiceDependency> getServiceDependencies();

  @Valid
  @Unstable
  List<DependencyExtension> getDependencyExtensions();

  @Valid
  GatewayDescriptor getGateway();

  @UniqueField("name")
  @Valid
  Set<CreateHdfsDirDescriptor> getHdfsDirs();

  @Valid
  ServiceInitDescriptor getServiceInit();

  @Valid
  ProvidesDfs getProvidesDfs();

  @Valid
  ProvidesKms getProvidesKms();

  /**
   * Whether kerberos authentication is used.
   * <p>
   * A service will require kerberos authentication if any of the following is true:
   * <ol>
   * <li>Any dependency of the service requires kerberos authentication
   * <li>This field returns a value of "true" or "kerberos" (case-insensitive)
   * </ol>
   */
  @AvailableSubstitutions(type={PARAMETERS})
  public String getKerberos();

  /**
   * List of external kerberos principals used by the service.
   * Cloudera Manager will not manage these principals, but this can be
   * used to refer to any external principals in configuration.
   */
  @Valid
  @UniqueField("name")
  List<KerberosPrincipalDescriptor> getExternalKerberosPrincipals();

  /**
   * Optional. If set, gets the license feature that all roles of this service
   * will require. If not set, then roles will not require any license feature.
   * Not intended for use outside of Cloudera, as License Features are not part
   * of any documentation or stable API.
   */
  @Unstable
  String getLicenseFeature();

  /**
   * Optional. Configure which users can perform various actions on this
   * service. Not intended for use outside of Cloudera, as Authorities are not
   * part of any documentation or stable API.
   */
  @Unstable
  AuthorityDescriptor getAuthorities();

  /**
   * If service supports rolling restart, the steps can be specified using this.
   */
  RollingRestartDescriptor getRollingRestart();
} public interface SslClientDescriptor {

  /**
   * Optional. Format of the truststore. If null, use JKS format by default.
   */
  CertificateFileFormat getTruststoreFormat();

  /**
   * Optional. Config name to emit when ssl_client_truststore_location is used
   * in a config file. If null, ssl_client_truststore_location will not be
   * emitted into config files, and can only be used in substitutions like
   * ${ssl_client_truststore_location}.
   */
  String getTruststoreLocationConfigName();

  /**
   * Optional. Default value for ssl_server_truststore_location.
   */
  String getTruststoreLocationDefault();

  @Referenced(type = ReferenceType.PARAMETER, as = {
      "ssl_client_truststore_location", "ssl_client_truststore_password" })
  public interface JksSslClientDescriptor extends SslClientDescriptor {
    // JKS-specific fields
    /**
     * Optional. Config name to emit when ssl_client_truststore_password is used
     * in a config file. If null, ssl_client_truststore_password will not be
     * emitted into config files, and can only be used in substitutions like
     * ${ssl_client_truststore_password}.
     * <p>
     * You must set this in order to use
     * truststorePasswordCredentialProviderCompatible or
     * truststorePasswordScriptBased.
     */
    String getTruststorePasswordConfigName();

    /**
     * Optional. Defaults to false. Whether ssl_client_truststore_password can
     * use the Credential Provider, a Hadoop mechanism that allows for the
     * encrypting of sensitive items in an encrypted store.
     * <p>
     * Has no effect on substitutions like ${ssl_client_truststore_password}
     * <p>
     * Requires truststorePasswordConfigName to be set. Mutually exclusive with
     * truststorePasswordScriptBased.
     */
    boolean isTruststorePasswordCredentialProviderCompatible();

    /**
     * Optional. Defaults to false. If true, the following things happen when used
     * in a configFile (not through substitutions like
     * ${ssl_client_truststore_password}):
     * <ul>
     * <li>1) The regular password for the truststore is no longer emitted</li>
     * <li>2) In its place, CM will emit the full path to a script, and that
     * script will echo the value of this desired password to stdout.</li>
     * </ul>
     * Requires truststorePasswordConfigName to be set.
     * <p>
     * Has no effect on substitutions like ${ssl_client_truststore_password}.
     * <p>
     * For this functionality to be useful, your code must run the script in the
     * parameter to get the real password.
     */
    boolean isTruststorePasswordScriptBased();
  }

  @Referenced(type = ReferenceType.PARAMETER, as = {
      "ssl_client_truststore_location" })
  public interface PemSslClientDescriptor extends SslClientDescriptor {
    // no PEM-specific fields
  }
} public interface SslServerDescriptor {

  /**
   * Optional. Format of the keystore. If null, use JKS format by default.
   */
  CertificateFileFormat getKeystoreFormat();

  /**
   * Optional. Determine whether to expose the ssl_enabled parameter. When
   * set to "optional" (the default), then ssl_enabled is generated as a
   * boolean parameter with a default of false. When set to "not_exposed" or
   * "required", there is no ssl_enabled parameter and CM will assume that
   * SSL is always enabled for this role.
   */
  CsdParameterOptionality getEnabledOptionality();

  /**
   * Optional. Config name to emit when ssl_enabled is used in a config file.
   * If null, ssl_enabled will not be emitted into config files, and can only
   * be used in substitutions like ${ssl_enabled}.
   */
  String getEnabledConfigName();

  @Referenced(type = ReferenceType.PARAMETER, as = {"ssl_enabled",
      "ssl_server_keystore_location", "ssl_server_keystore_password",
      // TODO we should only conditionally allow references to key password
      "ssl_server_keystore_keypassword" })
  public interface JksSslServerDescriptor extends SslServerDescriptor {
    // fields that only apply to JKS format

    /**
     * Optional. Config name to emit when ssl_server_keystore_location is used in
     * a config file. If null, ssl_server_keystore_location will not be emitted
     * into config files, and can only be used in substitutions like
     * ${ssl_server_keystore_location}.
     */
    String getKeystoreLocationConfigName();

    /**
     * Optional. Default value for ssl_server_keystore_location.
     */
    String getKeystoreLocationDefault();

    /**
     * Optional. Config name to emit when ssl_server_keystore_password is used in
     * a config file. If null, ssl_server_keystore_password will not be emitted
     * into config files, and can only be used in substitutions like
     * ${ssl_server_keystore_password}.
     * <p>
     * You must set this in order to use
     * keystorePasswordCredentialProviderCompatible keystorePasswordScriptBased.
     */
    String getKeystorePasswordConfigName();

    /**
     * Optional. Defaults to false. Whether ssl_server_keystore_password can use
     * the Credential Provider, a Hadoop mechanism that allows for the encrypting
     * of sensitive items in an encrypted store.
     * <p>
     * Has no effect on substitutions like ${ssl_server_keystore_password}
     * <p>
     * Requires keystorePasswordConfigName to be set. Mutually exclusive with
     * keystorePasswordScriptBased.
     */
    boolean isKeystorePasswordCredentialProviderCompatible();

    /**
     * Optional. Defaults to false. If true, the following things happen when used
     * in a configFile (not through substitutions like
     * ${ssl_server_keystore_password}):
     * <ul>
     * <li>1) The regular password for the keystore is no longer emitted</li>
     * <li>2) In its place, CM will emit the full path to a script, and that
     * script will echo the value of this desired password to stdout.</li>
     * </ul>
     * Requires keystorePasswordConfigName to be set. Mutually exclusive with
     * keystorePasswordCredentialProviderCompatible.
     * <p>
     * Has no effect on substitutions like ${ssl_server_keystore_password}.
     * <p>
     * For this functionality to be useful, your code must run the script in the
     * parameter to get the real password.
     */
    boolean isKeystorePasswordScriptBased();

    /**
     * The identifier for this role's key in the keystore.
     */
    @NotBlank
    @AvailableSubstitutions(type = { PARAMETERS, HOST, USER, GROUP })
    String getKeyIdentifier();

    /**
     * Optional. Whether to allow and / or require the cluster administrator to
     * configure a password for the SSL server's key in the keystore, exposed as
     * parameter ssl_server_keystore_keypassword.
     *
     * @return parameter optionality, or null to indicate NOT_EXPOSED.
     */
    CsdParameterOptionality getKeyPasswordOptionality();

    /**
     * Optional. Config name to emit when ssl_server_keystore_keypassword is
     * used in a config file. If null, ssl_server_keystore_keypassword will not
     * be emitted into config files, and can only be used in substitutions like
     * ${ssl_server_keystore_keypassword}.
     * <p>
     * You must set this in order to use keystoreKeyPasswordScriptBased.
     */
    String getKeystoreKeyPasswordConfigName();

    /**
     * Optional. Defaults to false. Whether ssl_server_keystore_keypassword can
     * use the Credential Provider, a Hadoop mechanism that allows for the
     * encrypting of sensitive items in an encrypted store.
     * <p>
     * Has no effect on substitutions like ${ssl_server_keystore_keypassword}
     * <p>
     * Requires keystoreKeyPasswordConfigName to be set. Mutually exclusive with
     * keystoreKeyPasswordScriptBased.
     */
    boolean isKeystoreKeyPasswordCredentialProviderCompatible();

    /**
     * Optional. Defaults to false. If true, the following things happen when
     * used in a configFile (not through substitutions like
     * ${ssl_server_keystore_keypassword}):
     * <ul>
     * <li>1) The regular password for the keystore is no longer emitted</li>
     * <li>2) In its place, CM will emit the full path to a script, and that
     * script will echo the value of this desired password to stdout.</li>
     * </ul>
     * Requires keystoreKeyPasswordConfigName to be set. Mutually exclusive
     * with keystoreKeyPasswordCredentialProviderCompatible.
     * <p>
     * Has no effect on substitutions like ${ssl_server_keystore_keypassword}.
     * <p>
     * For this functionality to be useful, your code must run the script in the
     * parameter to get the real password.
     */
    boolean isKeystoreKeyPasswordScriptBased();
  }

  @Referenced(type = ReferenceType.PARAMETER, as = {"ssl_enabled",
      "ssl_server_privatekey_location", "ssl_server_privatekey_password",
      "ssl_server_certificate_location", "ssl_server_ca_certificate_location"})
  public interface PemSslServerDescriptor extends SslServerDescriptor {
    /**
     * Optional. Config name to emit when ssl_server_privatekey_location is used in
     * a config file. If null, ssl_server_privatekey_location will not be emitted
     * into config files, and can only be used in substitutions like
     * ${ssl_server_privatekey_location}.
     */
    String getPrivateKeyLocationConfigName();

    /**
     * Optional. Default value for ssl_server_privatekey_location.
     */
    String getPrivateKeyLocationDefault();

    /**
     * Optional. Config name to emit when ssl_server_privatekey_password is used
     * in a config file. If null, ssl_server_privatekey_password will not be
     * emitted into config files, and can only be used in substitutions like
     * ${ssl_server_privatekey_password}.
     * <p>
     * You must set this in order to use
     * privateKeyPasswordCredentialProviderCompatible or
     * privateKeyPasswordScriptBased.
     */
    String getPrivateKeyPasswordConfigName();

    /**
     * Optional. Defaults to false. Whether ssl_server_privatekey_password can
     * use the Credential Provider, a Hadoop mechanism that allows for the
     * encrypting of sensitive items in an encrypted store.
     * <p>
     * Has no effect on substitutions like ${ssl_server_privatekey_password}
     * <p>
     * Requires privateKeyPasswordConfigName to be set. Mutually exclusive with
     * privateKeyPasswordScriptBased.
     */
    boolean isPrivateKeyPasswordCredentialProviderCompatible();

    /**
     * Optional. Defaults to false. If true, the following things happen when used
     * in a configFile (not through substitutions like
     * ${ssl_server_privatekey_password}):
     * <ul>
     * <li>1) The regular password for the private key is no longer emitted</li>
     * <li>2) In its place, CM will emit the full path to a script, and that
     * script will echo the value of this desired password to stdout.</li>
     * </ul>
     * Requires privateKeyPasswordConfigName to be set. Mutually exclusive with
     * privateKeyPasswordCredentialProviderCompatible.
     * <p>
     * Has no effect on substitutions like ${ssl_server_privatekey_password}.
     * <p>
     * For this functionality to be useful, your code must run the script in the
     * parameter to get the real password.
     */
    boolean isPrivateKeyPasswordScriptBased();

    /**
     * Optional. Config name to emit when ssl_server_certificate_location is
     * used in a config file. If null, ssl_server_certificate_location will not
     * be emitted into config files, and can only be used in substitutions like
     * ${ssl_server_certificate_location}.
     */
    String getCertificateLocationConfigName();

    /**
     * Optional. Default value for ssl_server_certificate_location.
     */
    String getCertificateLocationDefault();

    /**
     * Optional. Config name to emit when ssl_server_ca_certificate_location is
     * used in a config file. If null, ssl_server_ca_certificate_location will
     * not be emitted into config files, and can only be used in substitutions
     * like ${ssl_server_ca_certificate_location}.
     */
    String getCaCertificateLocationConfigName();

    /**
     * Optional. Default value for ssl_server_ca_certificate_location.
     */
    String getCaCertificateLocationDefault();
  }
} @Referenced(type = ReferenceType.PARAMETER, as = {"ssl_enabled",
    "ssl_server_privatekey_location", "ssl_server_privatekey_password",
    "ssl_server_certificate_location", "ssl_server_ca_certificate_location"})
public interface PemSslServerDescriptor extends SslServerDescriptor {
  /**
   * Optional. Config name to emit when ssl_server_privatekey_location is used in
   * a config file. If null, ssl_server_privatekey_location will not be emitted
   * into config files, and can only be used in substitutions like
   * ${ssl_server_privatekey_location}.
   */
  String getPrivateKeyLocationConfigName();

  /**
   * Optional. Default value for ssl_server_privatekey_location.
   */
  String getPrivateKeyLocationDefault();

  /**
   * Optional. Config name to emit when ssl_server_privatekey_password is used
   * in a config file. If null, ssl_server_privatekey_password will not be
   * emitted into config files, and can only be used in substitutions like
   * ${ssl_server_privatekey_password}.
   * <p>
   * You must set this in order to use
   * privateKeyPasswordCredentialProviderCompatible or
   * privateKeyPasswordScriptBased.
   */
  String getPrivateKeyPasswordConfigName();

  /**
   * Optional. Defaults to false. Whether ssl_server_privatekey_password can
   * use the Credential Provider, a Hadoop mechanism that allows for the
   * encrypting of sensitive items in an encrypted store.
   * <p>
   * Has no effect on substitutions like ${ssl_server_privatekey_password}
   * <p>
   * Requires privateKeyPasswordConfigName to be set. Mutually exclusive with
   * privateKeyPasswordScriptBased.
   */
  boolean isPrivateKeyPasswordCredentialProviderCompatible();

  /**
   * Optional. Defaults to false. If true, the following things happen when used
   * in a configFile (not through substitutions like
   * ${ssl_server_privatekey_password}):
   * <ul>
   * <li>1) The regular password for the private key is no longer emitted</li>
   * <li>2) In its place, CM will emit the full path to a script, and that
   * script will echo the value of this desired password to stdout.</li>
   * </ul>
   * Requires privateKeyPasswordConfigName to be set. Mutually exclusive with
   * privateKeyPasswordCredentialProviderCompatible.
   * <p>
   * Has no effect on substitutions like ${ssl_server_privatekey_password}.
   * <p>
   * For this functionality to be useful, your code must run the script in the
   * parameter to get the real password.
   */
  boolean isPrivateKeyPasswordScriptBased();

  /**
   * Optional. Config name to emit when ssl_server_certificate_location is
   * used in a config file. If null, ssl_server_certificate_location will not
   * be emitted into config files, and can only be used in substitutions like
   * ${ssl_server_certificate_location}.
   */
  String getCertificateLocationConfigName();

  /**
   * Optional. Default value for ssl_server_certificate_location.
   */
  String getCertificateLocationDefault();

  /**
   * Optional. Config name to emit when ssl_server_ca_certificate_location is
   * used in a config file. If null, ssl_server_ca_certificate_location will
   * not be emitted into config files, and can only be used in substitutions
   * like ${ssl_server_ca_certificate_location}.
   */
  String getCaCertificateLocationConfigName();

  /**
   * Optional. Default value for ssl_server_ca_certificate_location.
   */
  String getCaCertificateLocationDefault();
} @Expression.List({
    @Expression("minInstances == null or softMinInstances == null or minInstances < softMinInstances"),
    @Expression("minInstances == null or softMaxInstances == null or minInstances <= softMaxInstances"),
    @Expression("minInstances == null or maxInstances == null or minInstances <= maxInstances"),
    @Expression("softMinInstances == null or softMaxInstances == null or softMinInstances <= softMaxInstances"),
    @Expression("softMinInstances == null or maxInstances == null or softMinInstances <= maxInstances"),
    @Expression("softMaxInstances == null or maxInstances == null or softMaxInstances < maxInstances")
})
public interface TopologyDescriptor {

  /** Minimum number of roles of this type allowed. Defaults to 1. */
  @Min(0)
  Integer getMinInstances();

  /** Maximum number of roles of this type allowed. Defaults to Integer.MAX_VALUE. */
  @Min(1)
  Integer getMaxInstances();

  /** Recommended minimum number of roles of this type. By default there is no recommended minimum. */
  @Min(1)
  Integer getSoftMinInstances();

  /** Recommended maximum number of roles of this type. By default there is no recommended maximum. */
  @Min(1)
  Integer getSoftMaxInstances();

  /**
   * Optional. Specify rules about where this role can be placed relative to other roles.
   */
  @Valid
  List<PlacementRuleDescriptor> getPlacementRules();
} public interface ConfigEntry {

  @NotBlank
  @AvailableSubstitutions(type={PARAMETERS, HOST, GROUP, USER})
  String getKey();

  @AvailableSubstitutions(type={PARAMETERS, HOST, GROUP, USER})
  String getValue();

  /** Type of the entry. If none is specified, it defaults to "simple". */
  CsdConfigEntryType getType();
} @Named("filename")
public interface ConfigGenerator {

  @NotBlank
  String getFilename();

  /**
   * Optional. Whether this file can be refreshed. If at least one file is
   * refreshable, the role will have a refresh command automatically created for
   * it that will refresh all files for the role.
   * <p>
   * Defaults to False.
   */
  boolean isRefreshable();

  @Referencing(type=ReferenceType.PARAMETER)
  Set<String> getIncludedParams();

  @Referencing(type=ReferenceType.PARAMETER)
  Set<String> getExcludedParams();

  /** Kerberos principals to emit into config file. */
  @Valid
  List<KerberosPrincipalConfigEntry> getKerberosPrincipals();

  /**
   * Emitted after parameter configs, before safety valves.
   */
  @Valid
  List<ConfigEntry> getAdditionalConfigs();

  // These subclasses don't have any fields yet,
  // but we want to be consistent with how Parameters are defined.
  public interface HadoopXMLGenerator extends ConfigGenerator {
  }
  
  public interface PropertiesGenerator extends ConfigGenerator {
  }

  public interface GFlagsGenerator extends ConfigGenerator {
  }
} public interface GFlagsGenerator extends ConfigGenerator {
} public interface HadoopXMLGenerator extends ConfigGenerator {
} public interface PropertiesGenerator extends ConfigGenerator {
} @Named("filename")
@IncludeAdditionalReferences("roleName")
public interface PeerConfigGenerator {

  /** The file name of the config file that will be written **/
  @NotBlank
  String getFilename();

  /**
   * Optional. Whether this file can be refreshed. If at least one file is
   * refreshable, the role will have a refresh command automatically created for
   * it that will refresh all files for the role.
   * <p>
   * Defaults to False.
   */
  boolean isRefreshable();

  /** The parameters to include from each peer **/
  @NotEmpty
  @Referencing(type=ReferenceType.PARAMETER)
  Set<String> getParams();

  /** specifies which role type to use, be default it uses the current role type **/
  @Referencing(type=ReferenceType.ROLE)
  String getRoleName();
} @Expression("credentialProviderCompatible == false or alternateScriptParameterName == null")
public interface PasswordParameter extends StringParameter {

  /**
   * Whether this parameter can use the Credential Provider, a Hadoop
   * mechanism that allows for the encrypting of sensitive items in
   * an encrypted store.
   *
   * This is mutually exclusive with alternateScriptParameterName.
   */
  boolean isCredentialProviderCompatible();

  /**
   * If "alternateScriptParameterName" is specified for this password
   * parameter, the following things happen:
   *
   * 1) The "configName" of this parameter is no longer emitted
   * 2) In its place, a parameter with the name specified by the
   *    "alternateScriptParameterName" is emitted. This parameter contains
   *    the full path to a script, and that script will echo the value
   *    of this desired password to stdout. This only affects when this
   *    parameter is used normally in a config file, not through
   *    substitutions like ${paramter_name}. Substitutions will always get
   *    the raw password.
   *
   * For this functionality to be useful, your code must accept the
   * parameter specified in "alternateScriptParameterName" as a parameter
   * that replaces the "configName" and is known to point to the full path
   * of a script that will print the desired password to stdout.
   *
   * This is mutually exclusive with credentialProviderCompatible.
   */
  String getAlternateScriptParameterName();
} public class SdlTestUtils {

  public static final JsonSdlObjectMapper OBJECT_MAPPER;
  public static final JsonSdlParser SDL_PARSER;
  public static final JsonMdlParser MDL_PARSER;
  public static final ServiceDescriptor FULL_DESCRIPTOR;
  public static final DescriptorValidator<ServiceDescriptor>
    FAKE_SDL_VALIDATOR;
  public static final DescriptorValidator<ServiceMonitoringDefinitionsDescriptor>
    FAKE_MDL_VALIDATOR;

  private static final String RESOURCE_PATH = "/com/cloudera/csd/";
  public static final String SDL_VALIDATOR_RESOURCE_PATH = RESOURCE_PATH + "validator/";
  public static final String SDL_REFERENCE_VALIDATOR_RESOURCE_PATH = SDL_VALIDATOR_RESOURCE_PATH + "references/";
  public static final String SDL_PARSER_RESOURCE_PATH = RESOURCE_PATH + "parser/";

  // Initialize our variables
  static {
    OBJECT_MAPPER = new JsonSdlObjectMapper();
    SDL_PARSER = new JsonSdlParser(OBJECT_MAPPER);
    MDL_PARSER = new JsonMdlParser(OBJECT_MAPPER);
    FULL_DESCRIPTOR = getParserSdl("service_full.sdl");
    FAKE_SDL_VALIDATOR = getAlwaysPassingSdlValidator();
    FAKE_MDL_VALIDATOR = getAlwaysPassingMdlValidator();
  }

  public static ServiceDescriptor parseSDL(String path) {
    try {
      InputStream stream = SdlTestUtils.class.getResourceAsStream(path);
      return SDL_PARSER.parse(IOUtils.toByteArray(stream));
    } catch (IOException io) {
      throw new RuntimeException(io);
    }
  }

  public static ServiceMonitoringDefinitionsDescriptor parseMDL(String path) {
    try {
      InputStream stream = SdlTestUtils.class.getResourceAsStream(path);
      return MDL_PARSER.parse(IOUtils.toByteArray(stream));
    } catch (IOException io) {
      throw new RuntimeException(io);
    }
  }

  public static ServiceDescriptor getParserSdl(String filename) {
    return parseSDL(SDL_PARSER_RESOURCE_PATH + filename);
  }

  public static ServiceDescriptor getValidatorSdl(String filename) {
    return parseSDL(SDL_VALIDATOR_RESOURCE_PATH + filename);
  }

  public static ServiceMonitoringDefinitionsDescriptor getValidatorMdl(
      String filename) {
    return parseMDL(SDL_VALIDATOR_RESOURCE_PATH + filename);
  }

  public static ServiceDescriptor getReferenceValidatorSdl(String filename) {
    return parseSDL(SDL_REFERENCE_VALIDATOR_RESOURCE_PATH + filename);
  }

  public static DescriptorValidator<ServiceDescriptor>
      getAlwaysPassingSdlValidator() {
    return new DescriptorValidator<ServiceDescriptor>() {
      public Set<String> validate(ServiceDescriptor serviceDescriptor) {
        return Sets.newHashSet();
      }
    };
  }

  public static DescriptorValidator<ServiceMonitoringDefinitionsDescriptor>
      getAlwaysPassingMdlValidator() {
    return new DescriptorValidator<ServiceMonitoringDefinitionsDescriptor>() {
      public Set<String> validate(
          ServiceMonitoringDefinitionsDescriptor serviceDescriptor) {
        return Sets.newHashSet();
      }
    };
  }

  public static Map<String, RoleDescriptor> makeRoleMap(Collection<RoleDescriptor> roles) {
    ImmutableMap.Builder<String, RoleDescriptor> builder = ImmutableMap.builder();
    for (RoleDescriptor r : roles) {
      builder.put(r.getName(), r);
    }
    return builder.build();
  }
}
import static org.junit.Assert.*;
import com.cloudera.csd.descriptors.AuthorityDescriptor;
import com.cloudera.csd.descriptors.CompatibilityDescriptor;
import com.cloudera.csd.descriptors.CompatibilityDescriptor.VersionRange;
import com.cloudera.csd.descriptors.ConfigWriter;
import com.cloudera.csd.descriptors.CsdConfigEntryType;
import com.cloudera.csd.descriptors.CsdLoggingType;
import com.cloudera.csd.descriptors.CsdParameterOptionality;
import com.cloudera.csd.descriptors.CsdRoleState;
import com.cloudera.csd.descriptors.GatewayDescriptor;
import com.cloudera.csd.descriptors.GracefulStopDescriptor;
import com.cloudera.csd.descriptors.CertificateFileFormat;
import com.cloudera.csd.descriptors.PlacementRuleDescriptor;
import com.cloudera.csd.descriptors.PlacementRuleDescriptor.AlwaysWithRule;
import com.cloudera.csd.descriptors.PlacementRuleDescriptor.NeverWithRule;
import com.cloudera.csd.descriptors.ProvidesKms;
import com.cloudera.csd.descriptors.RoleCommandDescriptor;
import com.cloudera.csd.descriptors.RoleDescriptor;
import com.cloudera.csd.descriptors.RoleExternalLink;
import com.cloudera.csd.descriptors.RollingRestartDescriptor;
import com.cloudera.csd.descriptors.RollingRestartNonWorkerStepDescriptor;
import com.cloudera.csd.descriptors.RollingRestartWorkerStepDescriptor;
import com.cloudera.csd.descriptors.ServiceCommandDescriptor;
import com.cloudera.csd.descriptors.ServiceCommandDescriptor.RunMode;
import com.cloudera.csd.descriptors.SslClientDescriptor.JksSslClientDescriptor;
import com.cloudera.csd.descriptors.SslServerDescriptor.JksSslServerDescriptor;
import com.cloudera.csd.descriptors.ServiceDependency;
import com.cloudera.csd.descriptors.ServiceDescriptor;
import com.cloudera.csd.descriptors.ServiceInitDescriptor;
import com.cloudera.csd.descriptors.SslClientDescriptor;
import com.cloudera.csd.descriptors.SslServerDescriptor;
import com.cloudera.csd.descriptors.SslServerDescriptor.PemSslServerDescriptor;
import com.cloudera.csd.descriptors.TopologyDescriptor;
import com.cloudera.csd.descriptors.dependencyExtension.ClassAndConfigsExtension;
import com.cloudera.csd.descriptors.dependencyExtension.DependencyExtension;
import com.cloudera.csd.descriptors.dependencyExtension.ExtensionConfigEntry;
import com.cloudera.csd.descriptors.generators.AuxConfigGenerator;
import com.cloudera.csd.descriptors.generators.ConfigEntry;
import com.cloudera.csd.descriptors.generators.ConfigGenerator;
import com.cloudera.csd.descriptors.generators.ConfigGenerator.GFlagsGenerator;
import com.cloudera.csd.descriptors.generators.ConfigGenerator.HadoopXMLGenerator;
import com.cloudera.csd.descriptors.generators.ConfigGenerator.PropertiesGenerator;
import com.cloudera.csd.descriptors.generators.PeerConfigGenerator;
import com.cloudera.csd.descriptors.parameters.BooleanParameter;
import com.cloudera.csd.descriptors.parameters.CsdParamUnits;
import com.cloudera.csd.descriptors.parameters.CsdPathType;
import com.cloudera.csd.descriptors.parameters.DoubleParameter;
import com.cloudera.csd.descriptors.parameters.LongParameter;
import com.cloudera.csd.descriptors.parameters.MemoryParameter;
import com.cloudera.csd.descriptors.parameters.Parameter;
import com.cloudera.csd.descriptors.parameters.PasswordParameter;
import com.cloudera.csd.descriptors.parameters.PathArrayParameter;
import com.cloudera.csd.descriptors.parameters.PathParameter;
import com.cloudera.csd.descriptors.parameters.PortNumberParameter;
import com.cloudera.csd.descriptors.parameters.StringArrayParameter;
import com.cloudera.csd.descriptors.parameters.StringEnumParameter;
import com.cloudera.csd.descriptors.parameters.StringParameter;
import com.cloudera.csd.descriptors.parameters.StringParameter.InitType;
import com.cloudera.csd.descriptors.parameters.URIArrayParameter;
import com.cloudera.csd.descriptors.parameters.URIParameter;
import com.cloudera.csd.validation.SdlTestUtils;
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import org.junit.Test;
    assertTrue(jksSslServer.isKeystoreKeyPasswordCredentialProviderCompatible());

    sslClient = role.getSslClient();
    assertNotNull(sslClient);
    assertEquals(null, sslClient.getTruststoreFormat());
    assertTrue(sslClient instanceof JksSslClientDescriptor);
    JksSslClientDescriptor jksSslClient = (JksSslClientDescriptor) sslClient;
    assertTrue(jksSslClient.isTruststorePasswordCredentialProviderCompatible());

    ConfigWriter roleConfigWriter = role.getConfigWriter();
    assertNotNull(roleConfigWriter);
    assertNotNull(roleConfigWriter.getGenerators());
    ConfigGenerator roleGenerator = Iterables.getFirst(roleConfigWriter.getGenerators(), null);
    assertTrue(roleGenerator instanceof HadoopXMLGenerator);
    assertEquals("sample_xml_file.xml", roleGenerator.getFilename());
    assertFalse(
        "unspecified ConfigGenerator.isRefreshable should default to false",
        roleGenerator.isRefreshable());
    assertEquals(1, roleGenerator.getKerberosPrincipals().size());
    assertConfigEntries(
        ImmutableMap.of(
            "additional.config.hardcoded.key", "additional.config.hardcoded.value",
            "additional.config.template.key", "{{REPLACE_ME}}",
            "additional.config.interpolate.host.${host}.svcvar1.${service_var1}.key", "prefix://${host}:${role_var1}"),
            roleGenerator.getAdditionalConfigs());
    Set<String> foundGenerators = Sets.newHashSet();
    for (PeerConfigGenerator peerGenerator : roleConfigWriter.getPeerConfigGenerators()) {
      String filename = peerGenerator.getFilename();
      foundGenerators.add(filename);
      if ("sample_role_peer_file.properties".equals(filename)) {
        assertTrue(peerGenerator.isRefreshable());
        assertEquals(ImmutableSet.of("service_var1", "role_var3"),
            peerGenerator.getParams());
      } else if ("sample_master_peer_file.properties".equals(filename)) {
        assertFalse(peerGenerator.isRefreshable());
        assertEquals(ImmutableSet.of("master_server_var1"),
            peerGenerator.getParams());
        assertEquals("ECHO_MASTER_SERVER", peerGenerator.getRoleName());
      } else {
        fail("unexpected peer generator: " + filename);
      }
    }
    assertEquals(ImmutableSet.of("sample_role_peer_file.properties",
        "sample_master_peer_file.properties"), foundGenerators);

    assertNotNull(descriptor.getCommands());
    assertEquals(2, descriptor.getCommands().size());
    int found = 0;
    for (ServiceCommandDescriptor cmd : descriptor.getCommands()) {
      assertEquals("role_cmd1", cmd.getRoleCommand());
      if (cmd.getName().equals("service_cmd1")) {
        assertEquals(RunMode.ALL, cmd.getRunMode());
        found++;
      } else if (cmd.getName().equals("service_cmd2")) {
        assertEquals(RunMode.SINGLE, cmd.getRunMode());
        found++;
      } else {
        fail();
      }
    }
    assertEquals(2, found);

    assertNotNull(descriptor.getStopRunner());
    GracefulStopDescriptor stopDesc = descriptor.getStopRunner();
    assertEquals(180000, stopDesc.getTimeout());
    assertEquals("ECHO_MASTER_SERVER", stopDesc.getMasterRole());
    assertEquals("scripts/graceful_stop.sh", stopDesc.getRunner().getProgram());
    assertEquals(ImmutableList.of("ECHO_WEBSERVER"), stopDesc.getRelevantRoleTypes());

    AuthorityDescriptor authorityDescriptor = descriptor.getAuthorities();
    assertNotNull(authorityDescriptor);
    assertEquals("AUTH_BDR_ADMIN", authorityDescriptor.getAuthorityForAddRemove());
    assertEquals("AUTH_NAVIGATOR", authorityDescriptor.getDefaultAuthorityForParameters());
    assertEquals("AUTH_AUDITS", authorityDescriptor.getAuthorityForPowerState());

    RoleDescriptor webserverBuddy = name2role.get("ECHO_WEBSERVER_BUDDY");
    assertNotNull(webserverBuddy);
    topology = webserverBuddy.getTopology();
    assertNotNull(topology);
    List<PlacementRuleDescriptor> placementRules = topology.getPlacementRules();
    assertNotNull(placementRules);
    assertEquals(1, placementRules.size());
    assertTrue(placementRules.get(0) instanceof AlwaysWithRule);
    assertEquals("ECHO_WEBSERVER",
        ((AlwaysWithRule) placementRules.get(0)).getRoleType());

    RoleDescriptor exile = name2role.get("ECHO_EXILE");
    assertNotNull(exile);
    topology = exile.getTopology();
    assertNotNull(topology);
    assertEquals(Integer.valueOf(0), topology.getMinInstances());
    assertEquals(Integer.valueOf(3), topology.getMaxInstances());
    assertEquals(Integer.valueOf(1), topology.getSoftMinInstances());
    assertEquals(Integer.valueOf(2), topology.getSoftMaxInstances());
    placementRules = topology.getPlacementRules();
    assertNotNull(placementRules);
    assertEquals(1, placementRules.size());
    assertTrue(placementRules.get(0) instanceof NeverWithRule);
    assertEquals(
        ImmutableList.of(
            "ECHO_MASTER_SERVER", "ECHO_WEBSERVER", "ECHO_WEBSERVER_BUDDY"),
        ((NeverWithRule) placementRules.get(0)).getRoleTypes());

    RollingRestartDescriptor rrDesc = descriptor.getRollingRestart();
    assertNotNull(rrDesc);
    assertEquals(1, rrDesc.getNonWorkerSteps().size());
    RollingRestartNonWorkerStepDescriptor nwStep = rrDesc.getNonWorkerSteps().get(0);
    assertNonWorkerRRStep(nwStep, "ECHO_MASTER_SERVER",
        null /* uses auto-stop */, ImmutableList.of("Start", "role_cmd2"));
    assertWorkerRRStep(rrDesc.getWorkerSteps(), "ECHO_WEBSERVER",
        ImmutableList.of("service_cmd1", "Stop"), null /*uses auto-start */);
  }

  private void assertNonWorkerRRStep(RollingRestartNonWorkerStepDescriptor rrStep,
      String expectedRoleName, List<String> expectedBringDown, List<String> expectedBringUp) {
    assertEquals(expectedRoleName, rrStep.getRoleName());
    assertEquals(expectedBringDown, rrStep.getBringDownCommands());
    assertEquals(expectedBringUp, rrStep.getBringUpCommands());
  }

  private void assertWorkerRRStep(RollingRestartWorkerStepDescriptor rrStep,