/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.ambari.server.state.kerberos;

import static org.easymock.EasyMock.anyString;
import static org.easymock.EasyMock.expect;

import java.util.Collections;
import java.util.Properties;

import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;

import org.apache.ambari.annotations.Experimental;
import org.apache.ambari.annotations.ExperimentalFeature;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.actionmanager.ActionDBAccessor;
import org.apache.ambari.server.actionmanager.ActionDBAccessorImpl;
import org.apache.ambari.server.actionmanager.HostRoleCommandFactory;
import org.apache.ambari.server.actionmanager.HostRoleCommandFactoryImpl;
import org.apache.ambari.server.actionmanager.StageFactory;
import org.apache.ambari.server.actionmanager.StageFactoryImpl;
import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.audit.AuditLogger;
import org.apache.ambari.server.audit.AuditLoggerDefaultImpl;
import org.apache.ambari.server.configuration.Configuration;
import org.apache.ambari.server.controller.AbstractRootServiceResponseFactory;
import org.apache.ambari.server.controller.AmbariManagementController;
import org.apache.ambari.server.controller.KerberosHelper;
import org.apache.ambari.server.controller.RootServiceResponseFactory;
import org.apache.ambari.server.hooks.HookService;
import org.apache.ambari.server.hooks.users.UserHookService;
import org.apache.ambari.server.metadata.CachedRoleCommandOrderProvider;
import org.apache.ambari.server.metadata.RoleCommandOrderProvider;
import org.apache.ambari.server.orm.DBAccessor;
import org.apache.ambari.server.orm.dao.ExtensionLinkDAO;
import org.apache.ambari.server.orm.entities.MetainfoEntity;
import org.apache.ambari.server.orm.entities.StackEntity;
import org.apache.ambari.server.scheduler.ExecutionScheduler;
import org.apache.ambari.server.scheduler.ExecutionSchedulerImpl;
import org.apache.ambari.server.security.encryption.CredentialStoreService;
import org.apache.ambari.server.stack.StackManagerFactory;
import org.apache.ambari.server.state.Clusters;
import org.apache.ambari.server.state.ServiceComponentHostFactory;
import org.apache.ambari.server.state.stack.OsFamily;
import org.apache.ambari.server.testutils.PartialNiceMockBinder;
import org.apache.ambari.server.topology.PersistedState;
import org.easymock.EasyMock;
import org.easymock.EasyMockSupport;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;

import com.google.gson.Gson;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import com.google.inject.persist.UnitOfWork;

import junit.framework.Assert;

@Category({category.KerberosTest.class})
public class KerberosDescriptorUpdateHelperTest extends EasyMockSupport {
  private static final KerberosDescriptorFactory KERBEROS_DESCRIPTOR_FACTORY = new KerberosDescriptorFactory();
  private static final Gson GSON = new Gson();

  @Test
  @Ignore
  @Experimental(
      feature = ExperimentalFeature.MULTI_SERVICE,
      comment = "This was a very useful test that no longer works since all of the kerberos "
          + "descriptor files for services are stored in stacks no longer shipped with Ambari. "
          + "Although we could checking a bunch of descriptors, perhaps this is better suited to "
          + "something that is run when mpacks are registered.")
  public void updateDefaultUserKerberosDescriptor() throws Exception {
    Injector injector = Guice.createInjector(new AbstractModule() {

      @Override
      protected void configure() {
        Properties properties = new Properties();
        properties.put("metadata.path", "src/main/resources/stacks");
        properties.put("common.services.path", "src/main/resources/common-services");
        properties.put("server.version.file", "target/version");
        properties.put("custom.action.definitions", "/tmp/nofile");
        properties.put("resources.dir", "src/main/resources");
        Configuration configuration = new Configuration(properties);

        PartialNiceMockBinder.newBuilder(KerberosDescriptorUpdateHelperTest.this).addConfigsBindings()
            .addFactoriesInstallBinding().build().configure(binder());

        install(new FactoryModuleBuilder().build(StackManagerFactory.class));

        bind(Clusters.class).toInstance(createNiceMock(Clusters.class));
        bind(DBAccessor.class).toInstance(createNiceMock(DBAccessor.class));
        bind(EntityManager.class).toInstance(createNiceMock(EntityManager.class));
        bind(OsFamily.class).toInstance(createNiceMock(OsFamily.class));
        bind(Configuration.class).toInstance(configuration);
        bind(ExtensionLinkDAO.class).toInstance(createNiceMock(ExtensionLinkDAO.class));
        bind(PersistedState.class).toInstance(createNiceMock(PersistedState.class));
        bind(HostRoleCommandFactory.class).to(HostRoleCommandFactoryImpl.class);
        bind(ActionDBAccessor.class).to(ActionDBAccessorImpl.class);
        bind(UnitOfWork.class).toInstance(createNiceMock(UnitOfWork.class));
        bind(RoleCommandOrderProvider.class).to(CachedRoleCommandOrderProvider.class);
        bind(StageFactory.class).to(StageFactoryImpl.class);
        bind(AuditLogger.class).toInstance(createNiceMock(AuditLoggerDefaultImpl.class));
        bind(PasswordEncoder.class).toInstance(new StandardPasswordEncoder());
        bind(HookService.class).to(UserHookService.class);
        bind(ServiceComponentHostFactory.class).toInstance(createNiceMock(ServiceComponentHostFactory.class));
        bind(AbstractRootServiceResponseFactory.class).to(RootServiceResponseFactory.class);
        bind(CredentialStoreService.class).toInstance(createNiceMock(CredentialStoreService.class));
        bind(AmbariManagementController.class).toInstance(createNiceMock(AmbariManagementController.class));
        bind(ExecutionScheduler.class).to(ExecutionSchedulerImpl.class);
        bind(KerberosHelper.class).toInstance(createNiceMock(KerberosHelper.class));
      }
    });

    OsFamily osFamily = injector.getInstance(OsFamily.class);
    expect(osFamily.os_list()).andReturn(Collections.singleton("centos6")).anyTimes();

    ExtensionLinkDAO linkDao = injector.getInstance(ExtensionLinkDAO.class);
    expect(linkDao.findByStack(anyString(), anyString())).andReturn(Collections.emptyList()).anyTimes();

    TypedQuery<StackEntity> query = createNiceMock(TypedQuery.class);
    expect(query.setMaxResults(1)).andReturn(query).anyTimes();
    expect(query.getSingleResult()).andReturn(null).anyTimes();

    EntityManager entityManager = injector.getInstance(EntityManager.class);
    expect(entityManager.createNamedQuery("StackEntity.findByNameAndVersion", StackEntity.class)).andReturn(query).anyTimes();
    expect(entityManager.find(EasyMock.eq(MetainfoEntity.class), anyString())).andReturn(createNiceMock(MetainfoEntity.class)).anyTimes();

    AmbariMetaInfo metaInfo = new AmbariMetaInfo(injector.getInstance(Configuration.class));

    replayAll();

    injector.injectMembers(metaInfo);
    metaInfo.init();

    KerberosDescriptor hdp24 = metaInfo.getKerberosDescriptor("HDP", "2.4", false);
    KerberosDescriptor hdp25 = metaInfo.getKerberosDescriptor("HDP", "2.5", false);
    KerberosDescriptor user = new KerberosDescriptor(hdp24.toMap());

    KerberosDescriptor updated = KerberosDescriptorUpdateHelper.updateUserKerberosDescriptor(hdp24, hdp25, user);

    KerberosDescriptor composite = new KerberosDescriptor(hdp25.toMap());
    composite.update(updated);
    Assert.assertEquals(GSON.toJson(hdp25.toMap()), GSON.toJson(composite.toMap()));
  }

  @Test
  public void testUpdateProperties() throws AmbariException {
    KerberosDescriptor oldValue = KERBEROS_DESCRIPTOR_FACTORY.createInstance("{" +
        "  \"properties\": {" +
        "    \"realm\": \"${kerberos-env/realm}\"," +
        "    \"keytab_dir\": \"/etc/security/keytabs\"," +
        "    \"additional_realms\": \"\"," +
        "    \"old_property\": \"old_value\"" +
        "  }" +
        "}");

    KerberosDescriptor newValue = KERBEROS_DESCRIPTOR_FACTORY.createInstance("{" +
        "  \"properties\": {" +
        "    \"realm\": \"${kerberos-env/realm}\"," +
        "    \"keytab_dir\": \"/etc/security/keytabs\"," +
        "    \"additional_realms\": \"\"," +
        "    \"new_property\": \"new_value\"" +
        "  }" +
        "}");

    KerberosDescriptor userValue = KERBEROS_DESCRIPTOR_FACTORY.createInstance("{" +
        "  \"properties\": {" +
        "    \"realm\": \"EXAMPLE.COM\"," +
        "    \"keytab_dir\": \"/etc/security/keytabs\"," +
        "    \"additional_realms\": \"\"," +
        "    \"old_property\": \"old_value\"" +
        "  }" +
        "}");

    KerberosDescriptor updatedUserValue = KerberosDescriptorUpdateHelper.updateUserKerberosDescriptor(
        oldValue,
        newValue,
        userValue);

    // "old_property" is removed
    // "new_property" is added
    // "realm" retains user set value
    Assert.assertEquals(
        KERBEROS_DESCRIPTOR_FACTORY.createInstance(
            "{\n" +
                "  \"properties\": {\n" +
                "    \"new_property\": \"new_value\",\n" +
                "    \"realm\": \"EXAMPLE.COM\",\n" +
                "    \"additional_realms\": \"\",\n" +
                "    \"keytab_dir\": \"/etc/security/keytabs\"\n" +
                "  }\n" +
                "}"),
        updatedUserValue);
  }

  @Test
  public void testUpdateIdentities() throws AmbariException {
    KerberosDescriptor oldValue = KERBEROS_DESCRIPTOR_FACTORY.createInstance(
        "{" +
            "  \"identities\": [" +
            "    {" +
            "      \"name\": \"spnego\"," +
            "      \"principal\": {" +
            "        \"value\": \"HTTP/_HOST@${realm}\"," +
            "        \"type\": \"service\"" +
            "      }," +
            "      \"keytab\": {" +
            "        \"file\": \"${keytab_dir}/spnego.service.keytab\"," +
            "        \"owner\": {" +
            "          \"name\": \"root\"," +
            "          \"access\": \"r\"" +
            "        }," +
            "        \"group\": {" +
            "          \"name\": \"${cluster-env/user_group}\"," +
            "          \"access\": \"r\"" +
            "        }" +
            "      }" +
            "    }," +
            "    {" +
            "      \"name\": \"smokeuser\"," +
            "      \"principal\": {" +
            "        \"value\": \"old_value@${realm}\"," +
            "        \"type\": \"user\"," +
            "        \"configuration\": \"cluster-env/smokeuser_principal_name\"," +
            "        \"local_username\": \"${cluster-env/smokeuser}\"" +
            "      }," +
            "      \"keytab\": {" +
            "        \"file\": \"${keytab_dir}/smokeuser.headless.keytab\"," +
            "        \"owner\": {" +
            "          \"name\": \"${cluster-env/smokeuser}\"," +
            "          \"access\": \"r\"" +
            "        }," +
            "        \"group\": {" +
            "          \"name\": \"${cluster-env/user_group}\"," +
            "          \"access\": \"r\"" +
            "        }," +
            "        \"configuration\": \"cluster-env/smokeuser_keytab\"" +
            "      }" +
            "    }," +
            "    {" +
            "      \"name\": \"old_identity\"," +
            "      \"principal\": {" +
            "        \"value\": \"foobar${principal_suffix}@${realm}\"," +
            "        \"type\": \"user\"," +
            "        \"configuration\": \"cluster-env/ambari_principal_name\"" +
            "      }," +
            "      \"keytab\": {" +
            "        \"file\": \"${keytab_dir}/ambari.server.keytab\"" +
            "      }" +
            "    }" +
            "  ]" +
            "}");

    KerberosDescriptor newValue = KERBEROS_DESCRIPTOR_FACTORY.createInstance(
        "{" +
            "  \"identities\": [" +
            "    {" +
            "      \"name\": \"spnego\"," +
            "      \"principal\": {" +
            "        \"value\": \"HTTP/_HOST@${realm}\"," +
            "        \"type\": \"service\"" +
            "      }," +
            "      \"keytab\": {" +
            "        \"file\": \"${keytab_dir}/spnego.service.keytab\"," +
            "        \"owner\": {" +
            "          \"name\": \"root\"," +
            "          \"access\": \"r\"" +
            "        }," +
            "        \"group\": {" +
            "          \"name\": \"${cluster-env/user_group}\"," +
            "          \"access\": \"r\"" +
            "        }" +
            "      }" +
            "    }," +
            "    {" +
            "      \"name\": \"smokeuser\"," +
            "      \"principal\": {" +
            "        \"value\": \"${cluster-env/smokeuser}${principal_suffix}@${realm}\"," +
            "        \"type\": \"user\"," +
            "        \"configuration\": \"cluster-env/smokeuser_principal_name\"," +
            "        \"local_username\": \"${cluster-env/smokeuser}\"" +
            "      }," +
            "      \"keytab\": {" +
            "        \"file\": \"updated_dir/smokeuser.headless.keytab\"," +
            "        \"owner\": {" +
            "          \"name\": \"${cluster-env/smokeuser}\"," +
            "          \"access\": \"r\"" +
            "        }," +
            "        \"group\": {" +
            "          \"name\": \"${cluster-env/user_group}\"," +
            "          \"access\": \"r\"" +
            "        }," +
            "        \"configuration\": \"cluster-env/smokeuser_keytab\"" +
            "      }" +
            "    }," +
            "    {" +
            "      \"name\": \"ambari-server\"," +
            "      \"principal\": {" +
            "        \"value\": \"ambari-server${principal_suffix}@${realm}\"," +
            "        \"type\": \"user\"," +
            "        \"configuration\": \"cluster-env/ambari_principal_name\"" +
            "      }," +
            "      \"keytab\": {" +
            "        \"file\": \"${keytab_dir}/ambari.server.keytab\"" +
            "      }" +
            "    }," +
            "    {" +
            "      \"name\": \"future_identity\"," +
            "      \"principal\": {" +
            "        \"value\": \"CHANGED_future${principal_suffix}@${realm}\"," +
            "        \"type\": \"user\"" +
            "      }," +
            "      \"keytab\": {" +
            "        \"file\": \"${keytab_dir}/future.user.keytab\"" +
            "      }" +
            "    }" +
            "  ]" +
            "}");

    KerberosDescriptor userValue = KERBEROS_DESCRIPTOR_FACTORY.createInstance(
        "{" +
            "  \"identities\": [" +
            "    {" +
            "      \"name\": \"spnego\"," +
            "      \"principal\": {" +
            "        \"value\": \"CHANGED_HTTP/_HOST@${realm}\"," +
            "        \"type\": \"service\"" +
            "      }," +
            "      \"keytab\": {" +
            "        \"file\": \"${keytab_dir}/spnego.service.keytab\"," +
            "        \"owner\": {" +
            "          \"name\": \"root\"," +
            "          \"access\": \"r\"" +
            "        }," +
            "        \"group\": {" +
            "          \"name\": \"${cluster-env/user_group}\"," +
            "          \"access\": \"r\"" +
            "        }" +
            "      }" +
            "    }," +
            "    {" +
            "      \"name\": \"smokeuser\"," +
            "      \"principal\": {" +
            "        \"value\": \"old_value@${realm}\"," +
            "        \"type\": \"user\"," +
            "        \"configuration\": \"cluster-env/smokeuser_principal_name\"," +
            "        \"local_username\": \"${cluster-env/smokeuser}\"" +
            "      }," +
            "      \"keytab\": {" +
            "        \"file\": \"custom_dir/smokeuser.headless.keytab\"," +
            "        \"owner\": {" +
            "          \"name\": \"${cluster-env/smokeuser}\"," +
            "          \"access\": \"r\"" +
            "        }," +
            "        \"group\": {" +
            "          \"name\": \"${cluster-env/user_group}\"," +
            "          \"access\": \"r\"" +
            "        }," +
            "        \"configuration\": \"cluster-env/smokeuser_keytab\"" +
            "      }" +
            "    }," +
            "    {" +
            "      \"name\": \"old_identity\"," +
            "      \"principal\": {" +
            "        \"value\": \"foobar${principal_suffix}@${realm}\"," +
            "        \"type\": \"user\"," +
            "        \"configuration\": \"cluster-env/ambari_principal_name\"" +
            "      }," +
            "      \"keytab\": {" +
            "        \"file\": \"${keytab_dir}/ambari.server.keytab\"" +
            "      }" +
            "    }," +
            "    {" +
            "      \"name\": \"custom_identity\"," +
            "      \"principal\": {" +
            "        \"value\": \"custom${principal_suffix}@${realm}\"," +
            "        \"type\": \"user\"" +
            "      }," +
            "      \"keytab\": {" +
            "        \"file\": \"${keytab_dir}/custom.user.keytab\"" +
            "      }" +
            "    }," +
            "    {" +
            "      \"name\": \"future_identity\"," +
            "      \"principal\": {" +
            "        \"value\": \"future${principal_suffix}@${realm}\"," +
            "        \"type\": \"user\"" +
            "      }," +
            "      \"keytab\": {" +
            "        \"file\": \"${keytab_dir}/future.user.keytab\"" +
            "      }" +
            "    }" +
            "  ]" +
            "}");


    KerberosDescriptor updatedUserValue = KerberosDescriptorUpdateHelper.updateUserKerberosDescriptor(
        oldValue,
        newValue,
        userValue);

    Assert.assertEquals(
        GSON.toJson(KERBEROS_DESCRIPTOR_FACTORY.createInstance(
            "{\n" +
                "  \"identities\": [\n" +
                "    {\n" +
                "      \"name\": \"future_identity\",\n" +
                "      \"principal\": {\n" +
                "        \"value\": \"future${principal_suffix}@${realm}\",\n" +
                "        \"type\": \"user\"\n" +
                "      },\n" +
                "      \"keytab\": {\n" +
                "        \"file\": \"${keytab_dir}/future.user.keytab\"\n" +
                "      }\n" +
                "    },\n" +
                "    {\n" +
                "      \"name\": \"custom_identity\",\n" +
                "      \"principal\": {\n" +
                "        \"value\": \"custom${principal_suffix}@${realm}\",\n" +
                "        \"type\": \"user\"\n" +
                "      },\n" +
                "      \"keytab\": {\n" +
                "        \"file\": \"${keytab_dir}/custom.user.keytab\"\n" +
                "      }\n" +
                "    },\n" +
                "    {\n" +
                "      \"name\": \"spnego\",\n" +
                "      \"principal\": {\n" +
                "        \"value\": \"CHANGED_HTTP/_HOST@${realm}\",\n" +
                "        \"type\": \"service\"\n" +
                "      },\n" +
                "      \"keytab\": {\n" +
                "        \"file\": \"${keytab_dir}/spnego.service.keytab\",\n" +
                "        \"owner\": {\n" +
                "          \"name\": \"root\",\n" +
                "          \"access\": \"r\"\n" +
                "        },\n" +
                "        \"group\": {\n" +
                "          \"name\": \"${cluster-env/user_group}\",\n" +
                "          \"access\": \"r\"\n" +
                "        }\n" +
                "      }\n" +
                "    },\n" +
                "    {\n" +
                "      \"name\": \"smokeuser\",\n" +
                "      \"principal\": {\n" +
                "        \"value\": \"${cluster-env/smokeuser}${principal_suffix}@${realm}\",\n" +
                "        \"local_username\": \"${cluster-env/smokeuser}\",\n" +
                "        \"configuration\": \"cluster-env/smokeuser_principal_name\",\n" +
                "        \"type\": \"user\"\n" +
                "      },\n" +
                "      \"keytab\": {\n" +
                "        \"file\": \"custom_dir/smokeuser.headless.keytab\",\n" +
                "        \"owner\": {\n" +
                "          \"name\": \"${cluster-env/smokeuser}\",\n" +
                "          \"access\": \"r\"\n" +
                "        },\n" +
                "        \"group\": {\n" +
                "          \"name\": \"${cluster-env/user_group}\",\n" +
                "          \"access\": \"r\"\n" +
                "        },\n" +
                "        \"configuration\": \"cluster-env/smokeuser_keytab\"\n" +
                "      }\n" +
                "    }\n" +
                "  ]\n" +
                "}").toMap()),
        GSON.toJson(updatedUserValue.toMap()));

    // Test that the merge of the default (stack) Kerberos descriptor and the updated user-specified
    // Kerberos descriptor yield the expected composite Kerberos descriptor.
    newValue.update(updatedUserValue);

    Assert.assertEquals(
        GSON.toJson(KERBEROS_DESCRIPTOR_FACTORY.createInstance(
            "{\n" +
                "  \"identities\": [\n" +
                "    {\n" +
                "      \"name\": \"ambari-server\",\n" +
                "      \"principal\": {\n" +
                "        \"value\": \"ambari-server${principal_suffix}@${realm}\",\n" +
                "        \"configuration\": \"cluster-env/ambari_principal_name\",\n" +
                "        \"type\": \"user\"\n" +
                "      },\n" +
                "      \"keytab\": {\n" +
                "        \"file\": \"${keytab_dir}/ambari.server.keytab\"\n" +
                "      }\n" +
                "    },\n" +
                "    {\n" +
                "      \"name\": \"custom_identity\",\n" +
                "      \"principal\": {\n" +
                "        \"value\": \"custom${principal_suffix}@${realm}\",\n" +
                "        \"type\": \"user\"\n" +
                "      },\n" +
                "      \"keytab\": {\n" +
                "        \"file\": \"${keytab_dir}/custom.user.keytab\"\n" +
                "      }\n" +
                "    },\n" +
                "    {\n" +
                "      \"name\": \"future_identity\",\n" +
                "      \"principal\": {\n" +
                "        \"value\": \"future${principal_suffix}@${realm}\",\n" +
                "        \"type\": \"user\"\n" +
                "      },\n" +
                "      \"keytab\": {\n" +
                "        \"file\": \"${keytab_dir}/future.user.keytab\"\n" +
                "      }\n" +
                "    },\n" +
                "    {\n" +
                "      \"name\": \"spnego\",\n" +
                "      \"principal\": {\n" +
                "        \"value\": \"CHANGED_HTTP/_HOST@${realm}\",\n" +
                "        \"type\": \"service\"\n" +
                "      },\n" +
                "      \"keytab\": {\n" +
                "        \"file\": \"${keytab_dir}/spnego.service.keytab\",\n" +
                "        \"owner\": {\n" +
                "          \"name\": \"root\",\n" +
                "          \"access\": \"r\"\n" +
                "        },\n" +
                "        \"group\": {\n" +
                "          \"name\": \"${cluster-env/user_group}\",\n" +
                "          \"access\": \"r\"\n" +
                "        }\n" +
                "      }\n" +
                "    },\n" +
                "    {\n" +
                "      \"name\": \"smokeuser\",\n" +
                "      \"principal\": {\n" +
                "        \"value\": \"${cluster-env/smokeuser}${principal_suffix}@${realm}\",\n" +
                "        \"local_username\": \"${cluster-env/smokeuser}\",\n" +
                "        \"configuration\": \"cluster-env/smokeuser_principal_name\",\n" +
                "        \"type\": \"user\"\n" +
                "      },\n" +
                "      \"keytab\": {\n" +
                "        \"file\": \"custom_dir/smokeuser.headless.keytab\",\n" +
                "        \"owner\": {\n" +
                "          \"name\": \"${cluster-env/smokeuser}\",\n" +
                "          \"access\": \"r\"\n" +
                "        },\n" +
                "        \"group\": {\n" +
                "          \"name\": \"${cluster-env/user_group}\",\n" +
                "          \"access\": \"r\"\n" +
                "        },\n" +
                "        \"configuration\": \"cluster-env/smokeuser_keytab\"\n" +
                "      }\n" +
                "    }\n" +
                "  ]\n" +
                "}").toMap()),
        GSON.toJson(newValue.toMap()));
  }

  @Test
  public void testUpdateConfigurations() throws AmbariException {
    KerberosDescriptor oldValue = KERBEROS_DESCRIPTOR_FACTORY.createInstance(
        "{\n" +
            "  \"configurations\": [\n" +
            "    {\n" +
            "      \"core-site\": {\n" +
            "        \"hadoop.security.authentication\": \"kerberos\",\n" +
            "        \"hadoop.security.authorization\": \"true\",\n" +
            "        \"hadoop.proxyuser.HTTP.groups\": \"${hadoop-env/proxyuser_group}\"\n" +
            "      }\n" +
            "    },\n" +
            "    {\n" +
            "      \"some-site\": {\n" +
            "        \"property.unchanged\": \"value 1\",\n" +
            "        \"property.removed\": \"removed value\",\n" +
            "        \"property.altered\": \"old value\"\n," +
            "        \"property.property.changed.in.new\": \"orig value\"\n" +
            "      }\n" +
            "    },\n" +
            "    {\n" +
            "      \"old-site\": {\n" +
            "        \"property\": \"value\"\n" +
            "      }\n" +
            "    }\n" +
            "  ]\n" +
            "}\n");

    KerberosDescriptor newValue = KERBEROS_DESCRIPTOR_FACTORY.createInstance(
        "{\n" +
            "  \"configurations\": [\n" +
            "    {\n" +
            "      \"core-site\": {\n" +
            "        \"hadoop.security.authentication\": \"kerberos\",\n" +
            "        \"hadoop.security.authorization\": \"true\",\n" +
            "        \"hadoop.proxyuser.HTTP.groups\": \"${hadoop-env/proxyuser_group}\"\n" +
            "      }\n" +
            "    },\n" +
            "    {\n" +
            "      \"some-site\": {\n" +
            "        \"property.unchanged\": \"value 1\",\n" +
            "        \"property.added\": \"added value\",\n" +
            "        \"property.altered\": \"new value\",\n" +
            "        \"property.changed.in.new\": \"new value\"\n" +
            "      }\n" +
            "    },\n" +
            "    {\n" +
            "      \"new-site\": {\n" +
            "        \"property.for.new.site\": \"value\"\n" +
            "      }\n" +
            "    }\n" +
            "  ]\n" +
            "}\n");

    KerberosDescriptor userValue = KERBEROS_DESCRIPTOR_FACTORY.createInstance(
        "{\n" +
            "  \"configurations\": [\n" +
            "    {\n" +
            "      \"core-site\": {\n" +
            "        \"hadoop.security.authentication\": \"kerberos\",\n" +
            "        \"hadoop.security.authorization\": \"true\",\n" +
            "        \"hadoop.proxyuser.HTTP.groups\": \"${hadoop-env/proxyuser_group}\"\n" +
            "      }\n" +
            "    },\n" +
            "    {\n" +
            "      \"some-site\": {\n" +
            "        \"property.unchanged\": \"value 1\",\n" +
            "        \"property.removed\": \"changed removed value\",\n" +
            "        \"property.altered\": \"custom value\"\n," +
            "        \"property.property.changed.in.new\": \"orig value\"\n" +
            "      }\n" +
            "    },\n" +
            "    {\n" +
            "      \"old-site\": {\n" +
            "        \"property\": \"value\"\n" +
            "      }\n" +
            "    }\n" +
            "  ]\n" +
            "}\n");

    KerberosDescriptor updatedUserValue = KerberosDescriptorUpdateHelper.updateUserKerberosDescriptor(
        oldValue,
        newValue,
        userValue);

    Assert.assertEquals(
        GSON.toJson(KERBEROS_DESCRIPTOR_FACTORY.createInstance(
            "{\n" +
                "  \"configurations\": [\n" +
                "    {\n" +
                "      \"core-site\": {\n" +
                "        \"hadoop.security.authentication\": \"kerberos\",\n" +
                "        \"hadoop.security.authorization\": \"true\",\n" +
                "        \"hadoop.proxyuser.HTTP.groups\": \"${hadoop-env/proxyuser_group}\"\n" +
                "      }\n" +
                "    },\n" +
                "    {\n" +
                "      \"some-site\": {\n" +
                "        \"property.unchanged\": \"value 1\",\n" +
                "        \"property.added\": \"added value\",\n" +
                "        \"property.altered\": \"custom value\",\n" +
                "        \"property.changed.in.new\": \"new value\"\n" +
                "      }\n" +
                "    }\n" +
                "  ]\n" +
                "}\n").toMap()),
        GSON.toJson(updatedUserValue.toMap()));
  }

  @Test
  public void testUpdateAuthToLocalRules() throws AmbariException {
    KerberosDescriptor oldValue = KERBEROS_DESCRIPTOR_FACTORY.createInstance(
        "{\n" +
            "  \"auth_to_local_properties\" : [\n" +
            "    \"core-site/hadoop.security.auth_to_local\",\n" +
            "    \"some-site/to.be.removed\"\n" +
            "  ]\n" +
            "}\n");

    KerberosDescriptor newValue = KERBEROS_DESCRIPTOR_FACTORY.createInstance(
        "{\n" +
            "  \"auth_to_local_properties\" : [\n" +
            "    \"core-site/hadoop.security.auth_to_local\",\n" +
            "    \"some-site/to.be.added\"\n" +
            "  ]\n" +
            "}\n");

    KerberosDescriptor userValue = KERBEROS_DESCRIPTOR_FACTORY.createInstance(
        "{\n" +
            "  \"auth_to_local_properties\" : [\n" +
            "    \"core-site/hadoop.security.auth_to_local\",\n" +
            "    \"some-site/added.by.user\"\n" +
            "  ]\n" +
            "}\n");

    KerberosDescriptor updatedUserValue = KerberosDescriptorUpdateHelper.updateUserKerberosDescriptor(
        oldValue,
        newValue,
        userValue);

    Assert.assertEquals(
        KERBEROS_DESCRIPTOR_FACTORY.createInstance(
            "{\n" +
                "  \"auth_to_local_properties\" : [\n" +
                "    \"core-site/hadoop.security.auth_to_local\",\n" +
                "    \"some-site/to.be.added\",\n" +
                "    \"some-site/added.by.user\"\n" +
                "  ]\n" +
                "}\n"),
        updatedUserValue);
  }

  @Test
  public void testUpdateServices() throws AmbariException {
    KerberosDescriptor oldValue = KERBEROS_DESCRIPTOR_FACTORY.createInstance(
        "{\n" +
            "  \"services\": [\n" +
            "    {\n" +
            "      \"components\": [\n" +
            "        {\n" +
