Переглянути джерело

AMBARI-7230. LDAP Sync Scale issues for thousands of users/100's of groups. (mahadev)

Mahadev Konar 11 роки тому
батько
коміт
17b48bf40f
25 змінених файлів з 1065 додано та 251 видалено
  1. 14 0
      ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
  2. 5 11
      ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java
  3. 13 21
      ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
  4. 3 3
      ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
  5. 13 9
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ControllerResourceProvider.java
  6. 28 2
      ambari-server/src/main/java/org/apache/ambari/server/orm/dao/GroupDAO.java
  7. 32 2
      ambari-server/src/main/java/org/apache/ambari/server/orm/dao/MemberDAO.java
  8. 20 7
      ambari-server/src/main/java/org/apache/ambari/server/orm/dao/PrincipalDAO.java
  9. 28 1
      ambari-server/src/main/java/org/apache/ambari/server/orm/dao/PrincipalTypeDAO.java
  10. 38 11
      ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UserDAO.java
  11. 6 1
      ambari-server/src/main/java/org/apache/ambari/server/orm/entities/MemberEntity.java
  12. 1 1
      ambari-server/src/main/java/org/apache/ambari/server/orm/entities/PrincipalEntity.java
  13. 3 2
      ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UserEntity.java
  14. 1 1
      ambari-server/src/main/java/org/apache/ambari/server/security/authorization/User.java
  15. 156 2
      ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Users.java
  16. 127 106
      ambari-server/src/main/java/org/apache/ambari/server/security/ldap/AmbariLdapDataPopulator.java
  17. 67 0
      ambari-server/src/main/java/org/apache/ambari/server/security/ldap/LdapBatchDto.java
  18. 113 0
      ambari-server/src/main/java/org/apache/ambari/server/security/ldap/LdapGroupDto.java
  19. 72 0
      ambari-server/src/main/java/org/apache/ambari/server/security/ldap/LdapSyncDto.java
  20. 133 0
      ambari-server/src/main/java/org/apache/ambari/server/security/ldap/LdapUserDto.java
  21. 82 0
      ambari-server/src/main/java/org/apache/ambari/server/security/ldap/LdapUserGroupMemberDto.java
  22. 3 3
      ambari-server/src/main/python/ambari-server.py
  23. 4 0
      ambari-server/src/main/resources/META-INF/persistence.xml
  24. 9 68
      ambari-server/src/test/java/org/apache/ambari/server/security/ldap/AmbariLdapDataPopulatorTest.java
  25. 94 0
      ambari-server/src/test/java/org/apache/ambari/server/security/ldap/LdapPerformanceTest.java

+ 14 - 0
ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java

@@ -576,6 +576,20 @@ public class Configuration {
     properties.setProperty(CLIENT_SECURITY_KEY, type.toString());
   }
 
+  public void setLdap(String host, String userClass, String userNameAttr, String groupClass, String groupName, String groupMember,
+      String baseDN, boolean anon, String managerDN, String managerPass) {
+    properties.setProperty(LDAP_PRIMARY_URL_KEY, host);
+    properties.setProperty(LDAP_USER_OBJECT_CLASS_KEY, userClass);
+    properties.setProperty(LDAP_USERNAME_ATTRIBUTE_KEY, userNameAttr);
+    properties.setProperty(LDAP_GROUP_OBJECT_CLASS_KEY, groupClass);
+    properties.setProperty(LDAP_GROUP_NAMING_ATTR_KEY, groupName);
+    properties.setProperty(LDAP_GROUP_MEMEBERSHIP_ATTR_KEY, groupMember);
+    properties.setProperty(LDAP_BASE_DN_KEY, baseDN);
+    properties.setProperty(LDAP_BIND_ANONYMOUSLY_KEY, String.valueOf(anon));
+    properties.setProperty(LDAP_MANAGER_DN_KEY, managerDN);
+    properties.setProperty(LDAP_MANAGER_PASSWORD_KEY, managerPass);
+  }
+
   public String getWebAppDir() {
     LOG.info("Web App DIR test " + properties.getProperty(WEBAPP_DIR));
     return properties.getProperty(WEBAPP_DIR, "web");

+ 5 - 11
ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java

@@ -24,6 +24,7 @@ import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.controller.internal.RequestStageContainer;
 import org.apache.ambari.server.metadata.RoleCommandOrder;
 import org.apache.ambari.server.scheduler.ExecutionScheduleManager;
+import org.apache.ambari.server.security.ldap.LdapSyncDto;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.ConfigHelper;
@@ -36,6 +37,7 @@ import org.apache.ambari.server.state.ServiceFactory;
 import org.apache.ambari.server.state.State;
 import org.apache.ambari.server.state.configgroup.ConfigGroupFactory;
 import org.apache.ambari.server.state.scheduler.RequestExecutionFactory;
+
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -689,20 +691,12 @@ public interface AmbariManagementController {
   public boolean checkLdapConfigured();
 
   /**
-   * Retrieves users from external LDAP.
-   *
-   * @return key-value pairs UserName-Synced
-   * @throws AmbariException if LDAP is configured incorrectly
-   */
-  public Map<String, Boolean> getLdapUsersSyncInfo() throws AmbariException;
-
-  /**
-   * Retrieves groups from external LDAP.
+   * Retrieves groups and users from external LDAP.
    *
-   * @return key-value pairs GroupName-Synced
+   * @return ldap sync DTO
    * @throws AmbariException if LDAP is configured incorrectly
    */
-  public Map<String, Boolean> getLdapGroupsSyncInfo() throws AmbariException;
+  public LdapSyncDto getLdapSyncInfo() throws AmbariException;
 
   /**
    * Synchronizes local users and groups with given data.

+ 13 - 21
ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java

@@ -91,11 +91,13 @@ import org.apache.ambari.server.customactions.ActionDefinition;
 import org.apache.ambari.server.metadata.ActionMetadata;
 import org.apache.ambari.server.metadata.RoleCommandOrder;
 import org.apache.ambari.server.scheduler.ExecutionScheduleManager;
-import org.apache.ambari.server.security.authorization.AmbariLdapDataPopulator;
 import org.apache.ambari.server.security.authorization.AuthorizationHelper;
 import org.apache.ambari.server.security.authorization.Group;
 import org.apache.ambari.server.security.authorization.User;
 import org.apache.ambari.server.security.authorization.Users;
+import org.apache.ambari.server.security.ldap.AmbariLdapDataPopulator;
+import org.apache.ambari.server.security.ldap.LdapBatchDto;
+import org.apache.ambari.server.security.ldap.LdapSyncDto;
 import org.apache.ambari.server.stageplanner.RoleGraph;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
@@ -143,7 +145,6 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
-import com.google.common.collect.Multimaps;
 import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
@@ -724,11 +725,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
         throw new AmbariException("User already exists.");
       }
 
-      users.createUser(request.getUsername(), request.getPassword(), request.isActive(), request.isAdmin());
-
-      if (null != request.isActive() && null != user) {
-        users.setUserActive(user, request.isActive());
-      }
+      users.createUser(request.getUsername(), request.getPassword(), request.isActive(), request.isAdmin(), false);
     }
   }
 
@@ -1805,7 +1802,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
             RoleCommand roleCommand;
             State oldSchState = scHost.getState();
             ServiceComponentHostEvent event;
-            
+
             switch (newState) {
               case INSTALLED:
                 if (oldSchState == State.INIT
@@ -1960,7 +1957,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
                     break;
                 }
               }
-              
+
               if (null == requestParameters) {
                 requestParameters = new HashMap<String, String>();
               }
@@ -1972,7 +1969,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
           }
         }
       }
-      
+
       for (String serviceName : smokeTestServices) { // Creates smoke test commands
         Service s = cluster.getService(serviceName);
         // find service component host
@@ -3313,7 +3310,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
     }
     return response;
   }
-  
+
   @Override
   public Set<StackConfigurationResponse> getStackLevelConfigurations(
       Set<StackLevelConfigurationRequest> requests) throws AmbariException {
@@ -3322,7 +3319,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
 
       String stackName    = request.getStackName();
       String stackVersion = request.getStackVersion();
-      
+
       Set<StackConfigurationResponse> stackConfigurations = getStackLevelConfigurations(request);
 
       for (StackConfigurationResponse stackConfigurationResponse : stackConfigurations) {
@@ -3673,19 +3670,14 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
   }
 
   @Override
-  public Map<String, Boolean> getLdapUsersSyncInfo() throws AmbariException {
-    return ldapDataPopulator.getLdapUsersSyncInfo();
-  }
-
-  @Override
-  public Map<String, Boolean> getLdapGroupsSyncInfo() throws AmbariException {
-    return ldapDataPopulator.getLdapGroupsSyncInfo();
+  public LdapSyncDto getLdapSyncInfo() throws AmbariException {
+    return ldapDataPopulator.getLdapSyncInfo();
   }
 
   @Override
   public synchronized void synchronizeLdapUsersAndGroups(Set<String> users,
       Set<String> groups) throws AmbariException {
-    ldapDataPopulator.synchronizeLdapUsersAndGroups(users, groups);
+    final LdapBatchDto batchInfo = ldapDataPopulator.synchronizeLdapUsersAndGroups(users, groups);
+    this.users.processLdapSync(batchInfo);
   }
-
 }

+ 3 - 3
ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java

@@ -76,11 +76,11 @@ import org.apache.ambari.server.scheduler.ExecutionScheduleManager;
 import org.apache.ambari.server.security.CertificateManager;
 import org.apache.ambari.server.security.SecurityFilter;
 import org.apache.ambari.server.security.authorization.AmbariLdapAuthenticationProvider;
-import org.apache.ambari.server.security.authorization.AmbariLdapDataPopulator;
 import org.apache.ambari.server.security.authorization.AmbariLocalUserDetailsService;
 import org.apache.ambari.server.security.authorization.Users;
 import org.apache.ambari.server.security.authorization.AmbariAuthorizationFilter;
 import org.apache.ambari.server.security.authorization.internal.AmbariInternalAuthenticationProvider;
+import org.apache.ambari.server.security.ldap.AmbariLdapDataPopulator;
 import org.apache.ambari.server.security.unsecured.rest.CertificateDownload;
 import org.apache.ambari.server.security.unsecured.rest.CertificateSign;
 import org.apache.ambari.server.security.unsecured.rest.ConnectionInfo;
@@ -461,8 +461,8 @@ public class AmbariServer {
       LOG.info("Database init needed - creating default data");
       Users users = injector.getInstance(Users.class);
 
-      users.createUser("admin", "admin", true, true);
-      users.createUser("user", "user", true, false);
+      users.createUser("admin", "admin");
+      users.createUser("user", "user");
 
       MetainfoEntity schemaVersion = new MetainfoEntity();
       schemaVersion.setMetainfoName(Configuration.SERVER_VERSION_KEY);

+ 13 - 9
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ControllerResourceProvider.java

@@ -23,7 +23,6 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
 
 import org.apache.ambari.server.AmbariException;
@@ -40,6 +39,9 @@ import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
 import org.apache.ambari.server.controller.spi.SystemException;
 import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.security.ldap.LdapGroupDto;
+import org.apache.ambari.server.security.ldap.LdapSyncDto;
+import org.apache.ambari.server.security.ldap.LdapUserDto;
 import org.apache.commons.lang.StringUtils;
 
 /**
@@ -137,12 +139,14 @@ class ControllerResourceProvider extends AbstractControllerResourceProvider {
             ldapConfigured, requestedIds);
         if (ldapConfigured) {
           try {
+            final LdapSyncDto syncInfo = getManagementController().getLdapSyncInfo();
+
             final List<String> allUsers = new ArrayList<String>();
             final List<String> syncedUsers = new ArrayList<String>();
-            for (Entry<String, Boolean> user : getManagementController().getLdapUsersSyncInfo().entrySet()) {
-              allUsers.add(user.getKey());
-              if (user.getValue()) {
-                syncedUsers.add(user.getKey());
+            for (LdapUserDto user : syncInfo.getUsers()) {
+              allUsers.add(user.getUserName());
+              if (user.isSynced()) {
+                syncedUsers.add(user.getUserName());
               }
             }
             setResourceProperty(resource, CONTROLLER_LDAP_USERS_PROPERTY_ID,
@@ -151,10 +155,10 @@ class ControllerResourceProvider extends AbstractControllerResourceProvider {
                 syncedUsers, requestedIds);
             final List<String> allGroups = new ArrayList<String>();
             final List<String> syncedGroups = new ArrayList<String>();
-            for (Entry<String, Boolean> group : getManagementController().getLdapGroupsSyncInfo().entrySet()) {
-              allGroups.add(group.getKey());
-              if (group.getValue()) {
-                syncedGroups.add(group.getKey());
+            for (LdapGroupDto group : syncInfo.getGroups()) {
+              allGroups.add(group.getGroupName());
+              if (group.isSynced()) {
+                syncedGroups.add(group.getGroupName());
               }
             }
             setResourceProperty(resource, CONTROLLER_LDAP_GROUPS_PROPERTY_ID,

+ 28 - 2
ambari-server/src/main/java/org/apache/ambari/server/orm/dao/GroupDAO.java

@@ -17,8 +17,11 @@
  */
 package org.apache.ambari.server.orm.dao;
 
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import javax.persistence.EntityManager;
 import javax.persistence.NoResultException;
@@ -30,6 +33,7 @@ import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import com.google.inject.persist.Transactional;
+
 import org.apache.ambari.server.orm.entities.PrincipalEntity;
 
 @Singleton
@@ -79,8 +83,15 @@ public class GroupDAO {
 
   @Transactional
   public void create(GroupEntity group) {
-    group.setGroupName(group.getGroupName().toLowerCase());
-    entityManagerProvider.get().persist(group);
+    create(new HashSet<GroupEntity>(Arrays.asList(group)));
+  }
+
+  @Transactional
+  public void create(Set<GroupEntity> groups) {
+    for (GroupEntity group: groups) {
+      group.setGroupName(group.getGroupName().toLowerCase());
+      entityManagerProvider.get().persist(group);
+    }
   }
 
   @Transactional
@@ -89,12 +100,27 @@ public class GroupDAO {
     return entityManagerProvider.get().merge(group);
   }
 
+  @Transactional
+  public void merge(Set<GroupEntity> groups) {
+    for (GroupEntity group: groups) {
+      group.setGroupName(group.getGroupName().toLowerCase());
+      entityManagerProvider.get().merge(group);
+    }
+  }
+
   @Transactional
   public void remove(GroupEntity group) {
     entityManagerProvider.get().remove(merge(group));
     entityManagerProvider.get().getEntityManagerFactory().getCache().evictAll();
   }
 
+  @Transactional
+  public void remove(Set<GroupEntity> groups) {
+    for (GroupEntity groupEntity: groups) {
+      entityManagerProvider.get().remove(entityManagerProvider.get().merge(groupEntity));
+    }
+  }
+
   @Transactional
   public void removeByPK(Integer groupPK) {
     remove(findByPK(groupPK));

+ 32 - 2
ambari-server/src/main/java/org/apache/ambari/server/orm/dao/MemberDAO.java

@@ -17,19 +17,23 @@
  */
 package org.apache.ambari.server.orm.dao;
 
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import javax.persistence.EntityManager;
+import javax.persistence.NoResultException;
 import javax.persistence.TypedQuery;
 
 import org.apache.ambari.server.orm.RequiresSession;
 import org.apache.ambari.server.orm.entities.MemberEntity;
+import org.apache.ambari.server.orm.entities.UserEntity;
 
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import com.google.inject.persist.Transactional;
-import org.apache.ambari.server.orm.entities.UserEntity;
 
 @Singleton
 public class MemberDAO {
@@ -43,6 +47,18 @@ public class MemberDAO {
     return entityManagerProvider.get().find(MemberEntity.class, memberPK);
   }
 
+  @RequiresSession
+  public MemberEntity findByUserAndGroup(String userName, String groupName) {
+    final TypedQuery<MemberEntity> query = entityManagerProvider.get().createNamedQuery("memberByUserAndGroup", MemberEntity.class);
+    query.setParameter("username", userName.toLowerCase());
+    query.setParameter("groupname", groupName.toLowerCase());
+    try {
+      return query.getSingleResult();
+    } catch (NoResultException e) {
+      return null;
+    }
+  }
+
   @RequiresSession
   public List<MemberEntity> findAll() {
     final TypedQuery<MemberEntity> query = entityManagerProvider.get().createQuery("SELECT m FROM MemberEntity m", MemberEntity.class);
@@ -57,7 +73,14 @@ public class MemberDAO {
 
   @Transactional
   public void create(MemberEntity member) {
-    entityManagerProvider.get().persist(member);
+    create(new HashSet<MemberEntity>(Arrays.asList(member)));
+  }
+
+  @Transactional
+  public void create(Set<MemberEntity> members) {
+    for (MemberEntity member: members) {
+      entityManagerProvider.get().persist(member);
+    }
   }
 
   @Transactional
@@ -70,6 +93,13 @@ public class MemberDAO {
     entityManagerProvider.get().remove(merge(member));
   }
 
+  @Transactional
+  public void remove(Set<MemberEntity> members) {
+    for (MemberEntity member: members) {
+      entityManagerProvider.get().remove(entityManagerProvider.get().merge(member));
+    }
+  }
+
   @Transactional
   public void removeByPK(Integer memberPK) {
     remove(findByPK(memberPK));

+ 20 - 7
ambari-server/src/main/java/org/apache/ambari/server/orm/dao/PrincipalDAO.java

@@ -18,17 +18,18 @@
 
 package org.apache.ambari.server.orm.dao;
 
+import java.util.Arrays;
+import java.util.List;
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+
+import org.apache.ambari.server.orm.entities.PrincipalEntity;
+
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import com.google.inject.persist.Transactional;
 
-import org.apache.ambari.server.orm.entities.PrincipalEntity;
-import javax.persistence.EntityManager;
-import javax.persistence.TypedQuery;
-
-import java.util.List;
-
 /**
  * Principal Data Access Object.
  */
@@ -82,7 +83,19 @@ public class PrincipalDAO {
    */
   @Transactional
   public void create(PrincipalEntity entity) {
-    entityManagerProvider.get().persist(entity);
+    create(Arrays.asList(entity));
+  }
+
+  /**
+   * Make instances managed and persistent.
+   *
+   * @param entities entities to store
+   */
+  @Transactional
+  public void create(List<PrincipalEntity> entities) {
+    for (PrincipalEntity entity: entities) {
+      entityManagerProvider.get().persist(entity);
+    }
   }
 
   /**

+ 28 - 1
ambari-server/src/main/java/org/apache/ambari/server/orm/dao/PrincipalTypeDAO.java

@@ -22,10 +22,11 @@ import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import com.google.inject.persist.Transactional;
-import org.apache.ambari.server.orm.entities.PrincipalTypeEntity;
 
+import org.apache.ambari.server.orm.entities.PrincipalTypeEntity;
 import javax.persistence.EntityManager;
 import javax.persistence.TypedQuery;
+
 import java.util.List;
 
 /**
@@ -80,5 +81,31 @@ public class PrincipalTypeDAO {
   public PrincipalTypeEntity merge(PrincipalTypeEntity entity) {
     return entityManagerProvider.get().merge(entity);
   }
+
+  /**
+   * Creates and returns principal type if it wasn't persisted yet.
+   *
+   * @param principalType id of principal type
+   * @return principal type
+   */
+  public PrincipalTypeEntity ensurePrincipalTypeCreated(int principalType) {
+    PrincipalTypeEntity principalTypeEntity = findById(principalType);
+    if (principalTypeEntity == null) {
+      principalTypeEntity = new PrincipalTypeEntity();
+      principalTypeEntity.setId(principalType);
+      switch (principalType) {
+        case PrincipalTypeEntity.USER_PRINCIPAL_TYPE:
+          principalTypeEntity.setName(PrincipalTypeEntity.USER_PRINCIPAL_TYPE_NAME);
+          break;
+        case PrincipalTypeEntity.GROUP_PRINCIPAL_TYPE:
+          principalTypeEntity.setName(PrincipalTypeEntity.GROUP_PRINCIPAL_TYPE_NAME);
+          break;
+        default:
+          throw new IllegalArgumentException("Unknown principal type ID=" + principalType);
+      }
+      create(principalTypeEntity);
+    }
+    return principalTypeEntity;
+  }
 }
 

+ 38 - 11
ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UserDAO.java

@@ -17,19 +17,24 @@
  */
 package org.apache.ambari.server.orm.dao;
 
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import com.google.inject.persist.Transactional;
-import org.apache.ambari.server.orm.RequiresSession;
-import org.apache.ambari.server.orm.entities.PrincipalEntity;
-import org.apache.ambari.server.orm.entities.UserEntity;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 import javax.persistence.EntityManager;
 import javax.persistence.NoResultException;
 import javax.persistence.TypedQuery;
-import java.util.Collections;
-import java.util.List;
+
+import org.apache.ambari.server.orm.RequiresSession;
+import org.apache.ambari.server.orm.entities.PrincipalEntity;
+import org.apache.ambari.server.orm.entities.UserEntity;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.persist.Transactional;
 
 @Singleton
 public class UserDAO {
@@ -90,8 +95,15 @@ public class UserDAO {
 
   @Transactional
   public void create(UserEntity user) {
-    user.setUserName(user.getUserName().toLowerCase());
-    entityManagerProvider.get().persist(user);
+    create(new HashSet<UserEntity>(Arrays.asList(user)));
+  }
+
+  @Transactional
+  public void create(Set<UserEntity> users) {
+    for (UserEntity user: users) {
+      user.setUserName(user.getUserName().toLowerCase());
+      entityManagerProvider.get().persist(user);
+    }
   }
 
   @Transactional
@@ -100,12 +112,27 @@ public class UserDAO {
     return entityManagerProvider.get().merge(user);
   }
 
+  @Transactional
+  public void merge(Set<UserEntity> users) {
+    for (UserEntity user: users) {
+      user.setUserName(user.getUserName().toLowerCase());
+      entityManagerProvider.get().merge(user);
+    }
+  }
+
   @Transactional
   public void remove(UserEntity user) {
     entityManagerProvider.get().remove(merge(user));
     entityManagerProvider.get().getEntityManagerFactory().getCache().evictAll();
   }
 
+  @Transactional
+  public void remove(Set<UserEntity> users) {
+    for (UserEntity userEntity: users) {
+      entityManagerProvider.get().remove(entityManagerProvider.get().merge(userEntity));
+    }
+  }
+
   @Transactional
   public void removeByPK(Integer userPK) {
     remove(findByPK(userPK));

+ 6 - 1
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/MemberEntity.java

@@ -24,19 +24,24 @@ import javax.persistence.GenerationType;
 import javax.persistence.Id;
 import javax.persistence.JoinColumn;
 import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
 import javax.persistence.Table;
 import javax.persistence.TableGenerator;
 import javax.persistence.UniqueConstraint;
 
 @Entity
 @Table(name = "members", uniqueConstraints = {@UniqueConstraint(columnNames = {"group_id", "user_id"})})
+@NamedQueries({
+  @NamedQuery(name = "memberByUserAndGroup", query = "SELECT memberEnt FROM MemberEntity memberEnt where lower(memberEnt.user.userName)=:username AND lower(memberEnt.group.groupName)=:groupname")
+})
 @TableGenerator(name = "member_id_generator",
     table = "ambari_sequences",
     pkColumnName = "sequence_name",
     valueColumnName = "sequence_value",
     pkColumnValue = "member_id_seq",
     initialValue = 1,
-    allocationSize = 1
+    allocationSize = 500
     )
 public class MemberEntity {
   @Id

+ 1 - 1
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/PrincipalEntity.java

@@ -44,7 +44,7 @@ import javax.persistence.TableGenerator;
     table = "ambari_sequences", pkColumnName = "sequence_name", valueColumnName = "sequence_value"
     , pkColumnValue = "principal_id_seq"
     , initialValue = 2
-    , allocationSize = 1
+    , allocationSize = 500
 )
 @NamedQueries({
   @NamedQuery(name = "principalByPrivilegeId", query = "SELECT principal FROM PrincipalEntity principal JOIN principal.privileges privilege WHERE privilege.permission.id=:permission_id")

+ 3 - 2
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UserEntity.java

@@ -20,6 +20,7 @@ package org.apache.ambari.server.orm.entities;
 import javax.persistence.*;
 
 import java.util.Date;
+import java.util.HashSet;
 import java.util.Set;
 
 @Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_name", "ldap_user"})})
@@ -32,7 +33,7 @@ import java.util.Set;
     table = "ambari_sequences", pkColumnName = "sequence_name", valueColumnName = "sequence_value"
     , pkColumnValue = "user_id_seq"
     , initialValue = 2
-    , allocationSize = 1
+    , allocationSize = 500
     )
 public class UserEntity {
 
@@ -60,7 +61,7 @@ public class UserEntity {
   private Integer active = 1;
 
   @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
-  private Set<MemberEntity> memberEntities;
+  private Set<MemberEntity> memberEntities = new HashSet<MemberEntity>();
 
   @OneToOne
   @JoinColumns({

+ 1 - 1
ambari-server/src/main/java/org/apache/ambari/server/security/authorization/User.java

@@ -38,7 +38,7 @@ public class User {
   final Collection<String> groups = new ArrayList<String>();
   boolean admin = false;
 
-  User(UserEntity userEntity) {
+  public User(UserEntity userEntity) {
     userId = userEntity.getUserId();
     userName = userEntity.getUserName();
     createTime = userEntity.getCreateTime();

+ 156 - 2
ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Users.java

@@ -19,10 +19,14 @@ package org.apache.ambari.server.security.authorization;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
+import javax.persistence.EntityManager;
+
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.orm.dao.GroupDAO;
@@ -40,6 +44,8 @@ import org.apache.ambari.server.orm.entities.PrincipalEntity;
 import org.apache.ambari.server.orm.entities.PrincipalTypeEntity;
 import org.apache.ambari.server.orm.entities.PrivilegeEntity;
 import org.apache.ambari.server.orm.entities.UserEntity;
+import org.apache.ambari.server.security.ldap.LdapBatchDto;
+import org.apache.ambari.server.security.ldap.LdapUserGroupMemberDto;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.security.authentication.BadCredentialsException;
@@ -49,6 +55,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import com.google.inject.persist.Transactional;
 
@@ -60,6 +67,8 @@ public class Users {
 
   private final static Logger LOG = LoggerFactory.getLogger(Users.class);
 
+  @Inject
+  Provider<EntityManager> entityManagerProvider;
   @Inject
   protected UserDAO userDAO;
   @Inject
@@ -227,7 +236,7 @@ public class Users {
    * Creates new local user with provided userName and password.
    */
   public void createUser(String userName, String password) {
-    createUser(userName, password, true, false);
+    createUser(userName, password, true, false, false);
   }
 
   /**
@@ -237,9 +246,10 @@ public class Users {
    * @param password password
    * @param active is user active
    * @param admin is user admin
+   * @param ldapUser is user LDAP
    */
   @Transactional
-  public synchronized void createUser(String userName, String password, Boolean active, Boolean admin) {
+  public synchronized void createUser(String userName, String password, Boolean active, Boolean admin, Boolean ldapUser) {
 
     // create an admin principal to represent this user
     PrincipalTypeEntity principalTypeEntity = principalTypeDAO.findById(PrincipalTypeEntity.USER_PRINCIPAL_TYPE);
@@ -260,6 +270,9 @@ public class Users {
     if (active != null) {
       userEntity.setActive(active);
     }
+    if (ldapUser != null) {
+      userEntity.setLdapUser(ldapUser);
+    }
 
     userDAO.create(userEntity);
 
@@ -510,4 +523,145 @@ public class Users {
     return false;
   }
 
+  /**
+   * Executes batch queries to database to insert large amounts of LDAP data.
+   *
+   * @param batchInfo DTO with batch information
+   */
+  public void processLdapSync(LdapBatchDto batchInfo) {
+    final Map<String, UserEntity> allUsers = new HashMap<String, UserEntity>();
+    final Map<String, GroupEntity> allGroups = new HashMap<String, GroupEntity>();
+
+    // prefetch all user and group data to avoid heavy queries in membership creation
+
+    for (UserEntity userEntity: userDAO.findAll()) {
+      allUsers.put(userEntity.getUserName(), userEntity);
+    }
+
+    for (GroupEntity groupEntity: groupDAO.findAll()) {
+      allGroups.put(groupEntity.getGroupName(), groupEntity);
+    }
+
+    final PrincipalTypeEntity userPrincipalType = principalTypeDAO
+        .ensurePrincipalTypeCreated(PrincipalTypeEntity.USER_PRINCIPAL_TYPE);
+    final PrincipalTypeEntity groupPrincipalType = principalTypeDAO
+        .ensurePrincipalTypeCreated(PrincipalTypeEntity.GROUP_PRINCIPAL_TYPE);
+
+    // remove users
+    final Set<UserEntity> usersToRemove = new HashSet<UserEntity>();
+    for (String userName: batchInfo.getUsersToBeRemoved()) {
+      UserEntity userEntity = userDAO.findLocalUserByName(userName);
+      if (userEntity == null) {
+        userEntity = userDAO.findLdapUserByName(userName);
+        if (userEntity == null) {
+          continue;
+        }
+      }
+      allUsers.remove(userEntity.getUserName());
+      usersToRemove.add(userEntity);
+    }
+    userDAO.remove(usersToRemove);
+
+    // remove groups
+    final Set<GroupEntity> groupsToRemove = new HashSet<GroupEntity>();
+    for (String groupName: batchInfo.getGroupsToBeRemoved()) {
+      final GroupEntity groupEntity = groupDAO.findGroupByName(groupName);
+      allGroups.remove(groupEntity.getGroupName());
+      groupsToRemove.add(groupEntity);
+    }
+    groupDAO.remove(groupsToRemove);
+
+    // update users
+    final Set<UserEntity> usersToBecomeLdap = new HashSet<UserEntity>();
+    for (String userName: batchInfo.getUsersToBecomeLdap()) {
+      UserEntity userEntity = userDAO.findLocalUserByName(userName);
+      if (userEntity == null) {
+        userEntity = userDAO.findLdapUserByName(userName);
+        if (userEntity == null) {
+          continue;
+        }
+      }
+      userEntity.setLdapUser(true);
+      allUsers.put(userEntity.getUserName(), userEntity);
+      usersToBecomeLdap.add(userEntity);
+    }
+    userDAO.merge(usersToBecomeLdap);
+
+    // update groups
+    final Set<GroupEntity> groupsToBecomeLdap = new HashSet<GroupEntity>();
+    for (String groupName: batchInfo.getGroupsToBecomeLdap()) {
+      final GroupEntity groupEntity = groupDAO.findGroupByName(groupName);
+      groupEntity.setLdapGroup(true);
+      allGroups.put(groupEntity.getGroupName(), groupEntity);
+      groupsToBecomeLdap.add(groupEntity);
+    }
+    groupDAO.merge(groupsToBecomeLdap);
+
+    // prepare create principals
+    final List<PrincipalEntity> principalsToCreate = new ArrayList<PrincipalEntity>();
+
+    // prepare create users
+    final Set<UserEntity> usersToCreate = new HashSet<UserEntity>();
+    for (String userName: batchInfo.getUsersToBeCreated()) {
+      final PrincipalEntity principalEntity = new PrincipalEntity();
+      principalEntity.setPrincipalType(userPrincipalType);
+      principalsToCreate.add(principalEntity);
+
+      final UserEntity userEntity = new UserEntity();
+      userEntity.setUserName(userName);
+      userEntity.setUserPassword("");
+      userEntity.setPrincipal(principalEntity);
+      userEntity.setLdapUser(true);
+
+      allUsers.put(userEntity.getUserName(), userEntity);
+      usersToCreate.add(userEntity);
+    }
+
+    // prepare create groups
+    final Set<GroupEntity> groupsToCreate = new HashSet<GroupEntity>();
+    for (String groupName: batchInfo.getGroupsToBeCreated()) {
+      final PrincipalEntity principalEntity = new PrincipalEntity();
+      principalEntity.setPrincipalType(groupPrincipalType);
+      principalsToCreate.add(principalEntity);
+
+      final GroupEntity groupEntity = new GroupEntity();
+      groupEntity.setGroupName(groupName);
+      groupEntity.setPrincipal(principalEntity);
+      groupEntity.setLdapGroup(true);
+
+      allGroups.put(groupEntity.getGroupName(), groupEntity);
+      groupsToCreate.add(groupEntity);
+    }
+
+    // create users and groups
+    principalDAO.create(principalsToCreate);
+    userDAO.create(usersToCreate);
+    groupDAO.create(groupsToCreate);
+
+    // remove membership
+    final Set<MemberEntity> membersToRemove = new HashSet<MemberEntity>();
+    for (LdapUserGroupMemberDto member: batchInfo.getMembershipToRemove()) {
+      membersToRemove.add(memberDAO.findByUserAndGroup(member.getUserName(), member.getGroupName()));
+    }
+    memberDAO.remove(membersToRemove);
+
+    // create membership
+    final Set<MemberEntity> membersToCreate = new HashSet<MemberEntity>();
+    final Set<GroupEntity> groupsToUpdate = new HashSet<GroupEntity>();
+    for (LdapUserGroupMemberDto member: batchInfo.getMembershipToAdd()) {
+      final MemberEntity memberEntity = new MemberEntity();
+      final GroupEntity groupEntity = allGroups.get(member.getGroupName());
+      memberEntity.setGroup(groupEntity);
+      memberEntity.setUser(allUsers.get(member.getUserName()));
+      groupEntity.getMemberEntities().add(memberEntity);
+      groupsToUpdate.add(groupEntity);
+      membersToCreate.add(memberEntity);
+    }
+    memberDAO.create(membersToCreate);
+    groupDAO.merge(groupsToUpdate); // needed for Derby DB as it doesn't fetch newly added members automatically
+
+    // clear cached entities
+    entityManagerProvider.get().getEntityManagerFactory().getCache().evictAll();
+  }
+
 }

+ 127 - 106
ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapDataPopulator.java → ambari-server/src/main/java/org/apache/ambari/server/security/ldap/AmbariLdapDataPopulator.java

@@ -15,9 +15,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.ambari.server.security.authorization;
+package org.apache.ambari.server.security.ldap;
 
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -30,6 +31,10 @@ import javax.naming.directory.Attributes;
 
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.security.authorization.Group;
+import org.apache.ambari.server.security.authorization.LdapServerProperties;
+import org.apache.ambari.server.security.authorization.User;
+import org.apache.ambari.server.security.authorization.Users;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.springframework.ldap.core.AttributesMapper;
@@ -37,7 +42,6 @@ import org.springframework.ldap.core.ContextMapper;
 import org.springframework.ldap.core.DirContextAdapter;
 import org.springframework.ldap.core.LdapTemplate;
 import org.springframework.ldap.core.support.LdapContextSource;
-import org.springframework.ldap.filter.AndFilter;
 import org.springframework.ldap.filter.EqualsFilter;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
 
@@ -89,7 +93,13 @@ public class AmbariLdapDataPopulator {
     }
     try {
       final LdapTemplate ldapTemplate = loadLdapTemplate();
-      ldapTemplate.list(ldapServerProperties.getBaseDN());
+      ldapTemplate.search(ldapServerProperties.getBaseDN(), "uid=dummy_search", new AttributesMapper() {
+
+        @Override
+        public Object mapFromAttributes(Attributes arg0) throws NamingException {
+          return null;
+        }
+      });
       return true;
     } catch (Exception ex) {
       LOG.error("Could not connect to LDAP server - " + ex.getMessage());
@@ -98,49 +108,38 @@ public class AmbariLdapDataPopulator {
   }
 
   /**
-   * Retrieves a key-value map of all LDAP groups.
+   * Retrieves information about external groups and users and their synced/unsynced state.
    *
-   * @return map of GroupName-Synced pairs
+   * @return dto with information
    */
-  public Map<String, Boolean> getLdapGroupsSyncInfo() {
-    final Map<String, Boolean> ldapGroups = new HashMap<String, Boolean>();
+  public LdapSyncDto getLdapSyncInfo() {
+    final LdapSyncDto syncInfo = new LdapSyncDto();
+
     final Map<String, Group> internalGroupsMap = getInternalGroups();
-    final Set<String> externalGroups = getExternalLdapGroupNames();
-    for (String externalGroup : externalGroups) {
-      if (internalGroupsMap.containsKey(externalGroup)
-          && internalGroupsMap.get(externalGroup).isLdapGroup()) {
-        ldapGroups.put(externalGroup, true);
+    final Set<LdapGroupDto> externalGroups = getExternalLdapGroupInfo();
+    for (LdapGroupDto externalGroup : externalGroups) {
+      if (internalGroupsMap.containsKey(externalGroup.getGroupName())
+          && internalGroupsMap.get(externalGroup.getGroupName()).isLdapGroup()) {
+        externalGroup.setSynced(true);
       } else {
-        ldapGroups.put(externalGroup, false);
+        externalGroup.setSynced(false);
       }
     }
 
-    return ldapGroups;
-  }
-
-  /**
-   * Retrieves a key-value map of all LDAP users.
-   *
-   * @return map of UserName-Synced pairs.
-   */
-  public Map<String, Boolean> getLdapUsersSyncInfo() {
-    final Map<String, Boolean> ldapUsers = new HashMap<String, Boolean>();
-    final List<User> internalUsers = users.getAllUsers();
-    final Map<String, User> internalUsersMap = new HashMap<String, User>();
-    for (User user : internalUsers) {
-      internalUsersMap.put(user.getUserName(), user);
-    }
-    final Set<String> externalUsers = getExternalLdapUserNames();
-    for (String externalUser : externalUsers) {
+    final Map<String, User> internalUsersMap = getInternalUsers();
+    final Set<LdapUserDto> externalUsers = getExternalLdapUserInfo();
+    for (LdapUserDto externalUser : externalUsers) {
       if (internalUsersMap.containsKey(externalUser)
           && internalUsersMap.get(externalUser).isLdapUser()) {
-        ldapUsers.put(externalUser, true);
+        externalUser.setSynced(true);
       } else {
-        ldapUsers.put(externalUser, false);
+        externalUser.setSynced(false);
       }
     }
 
-    return ldapUsers;
+    syncInfo.setGroups(externalGroups);
+    syncInfo.setUsers(externalUsers);
+    return syncInfo;
   }
 
   /**
@@ -150,93 +149,117 @@ public class AmbariLdapDataPopulator {
    * @param groups set of groups to synchronize
    * @throws AmbariException if synchronization failed for any reason
    */
-  public void synchronizeLdapUsersAndGroups(Set<String> users,
+  public LdapBatchDto synchronizeLdapUsersAndGroups(Set<String> users,
       Set<String> groups) throws AmbariException {
+    final LdapBatchDto batchInfo = new LdapBatchDto();
+
     // validate request
-    final Set<String> externalUsers = getExternalLdapUserNames();
+    final Set<LdapUserDto> externalUsers = getExternalLdapUserInfo();
+    final Map<String, LdapUserDto> externalUsersMap = new HashMap<String, LdapUserDto>();
+    for (LdapUserDto user: externalUsers) {
+      externalUsersMap.put(user.getUserName(), user);
+    }
     for (String user : users) {
-      if (!externalUsers.contains(user)) {
+      if (!externalUsersMap.containsKey(user)) {
         throw new AmbariException("Couldn't sync LDAP user " + user
             + ", it doesn't exist");
       }
     }
-    final Set<String> externalGroups = getExternalLdapGroupNames();
+    final Set<LdapGroupDto> externalGroups = getExternalLdapGroupInfo();
+    final Map<String, LdapGroupDto> externalGroupsMap = new HashMap<String, LdapGroupDto>();
+    for (LdapGroupDto group: externalGroups) {
+      externalGroupsMap.put(group.getGroupName(), group);
+    }
     for (String group : groups) {
-      if (!externalGroups.contains(group)) {
+      if (!externalGroupsMap.containsKey(group)) {
         throw new AmbariException("Couldn't sync LDAP group " + group
             + ", it doesn't exist");
       }
     }
 
-    // processing groups
     final Map<String, Group> internalGroupsMap = getInternalGroups();
+    final Map<String, User> internalUsersMap = getInternalUsers();
+
+    // processing groups
     for (String groupName : groups) {
       if (internalGroupsMap.containsKey(groupName)) {
         final Group group = internalGroupsMap.get(groupName);
         if (!group.isLdapGroup()) {
-          this.users.setGroupLdap(groupName);
+          batchInfo.getGroupsToBecomeLdap().add(groupName);
         }
       } else {
-        this.users.createGroup(groupName);
-        this.users.setGroupLdap(groupName);
+        batchInfo.getGroupsToBeCreated().add(groupName);
       }
-      refreshGroupMembers(groupName);
+      refreshGroupMembers(batchInfo, externalGroupsMap.get(groupName), internalUsersMap, externalUsers);
       internalGroupsMap.remove(groupName);
     }
     for (Entry<String, Group> internalGroup : internalGroupsMap.entrySet()) {
       if (internalGroup.getValue().isLdapGroup()) {
-        this.users.removeGroup(internalGroup.getValue());
+        batchInfo.getGroupsToBeRemoved().add(internalGroup.getValue().getGroupName());
       }
     }
 
-    cleanUpLdapUsersWithoutGroup();
-
     // processing users
-    final Map<String, User> internalUsersMap = getInternalUsers();
     for (String userName : users) {
       if (internalUsersMap.containsKey(userName)) {
         final User user = internalUsersMap.get(userName);
-        if (!user.isLdapUser()) {
-          this.users.setUserLdap(userName);
+        if (user != null && !user.isLdapUser()) {
+          batchInfo.getUsersToBecomeLdap().add(userName);
         }
       } else {
-        this.users.createUser(userName, "", true, false);
-        this.users.setUserLdap(userName);
+        batchInfo.getUsersToBeCreated().add(userName);
       }
     }
 
+    return batchInfo;
   }
 
   /**
    * Check group members of the synced group: add missing ones and remove the ones absent in external LDAP.
    *
    * @param groupName group name
+   * @param internalUsers map of internal users
+   * @param externalUsers set of external users
    * @throws AmbariException if group refresh failed
    */
-  protected void refreshGroupMembers(String groupName) throws AmbariException {
-    final Set<String> externalMembers = getExternalLdapGroupMembers(groupName);
-    final Map<String, User> internalUsers = getInternalUsers();
-    final Map<String, User> internalMembers = getInternalMembers(groupName);
+  protected void refreshGroupMembers(LdapBatchDto batchInfo, LdapGroupDto group, Map<String, User> internalUsers, Set<LdapUserDto> externalUsers) throws AmbariException {
+    final Set<String> externalMembers = new HashSet<String>();
+    for (String memberAttribute: group.getMemberAttributes()) {
+      for (LdapUserDto externalUser: externalUsers) {
+        // memberAttribute may be either DN or UID, check both
+        if (externalUser.getDn().equals(memberAttribute) || externalUser.getUid().equals(memberAttribute)) {
+          externalMembers.add(externalUser.getUserName());
+          break;
+        }
+      }
+    }
+    final Map<String, User> internalMembers = getInternalMembers(group.getGroupName());
     for (String externalMember: externalMembers) {
       if (internalUsers.containsKey(externalMember)) {
         final User user = internalUsers.get(externalMember);
+        if (user == null) {
+          // user is fresh and is already added to batch info
+          if (!internalMembers.containsKey(externalMember)) {
+            batchInfo.getMembershipToAdd().add(new LdapUserGroupMemberDto(group.getGroupName(), externalMember));
+          }
+          continue;
+        }
         if (!user.isLdapUser()) {
-          users.setUserLdap(externalMember);
+          batchInfo.getUsersToBecomeLdap().add(externalMember);
         }
         if (!internalMembers.containsKey(externalMember)) {
-          users.addMemberToGroup(groupName, externalMember);
+          batchInfo.getMembershipToAdd().add(new LdapUserGroupMemberDto(group.getGroupName(), externalMember));
         }
         internalMembers.remove(externalMember);
-        internalUsers.remove(externalMember);
       } else {
-        users.createUser(externalMember, "");
-        users.setUserLdap(externalMember);
-        users.addMemberToGroup(groupName, externalMember);
+        batchInfo.getUsersToBeCreated().add(externalMember);
+        batchInfo.getMembershipToAdd().add(new LdapUserGroupMemberDto(group.getGroupName(), externalMember));
+        internalUsers.put(externalMember, null);
       }
     }
     for (Entry<String, User> userToBeUnsynced: internalMembers.entrySet()) {
       final User user = userToBeUnsynced.getValue();
-      users.removeMemberFromGroup(groupName, user.getUserName());
+      batchInfo.getMembershipToRemove().add(new LdapUserGroupMemberDto(group.getGroupName(), user.getUserName()));
     }
   }
 
@@ -259,20 +282,32 @@ public class AmbariLdapDataPopulator {
   /**
    * Retrieves groups from external LDAP server.
    *
-   * @return set of user names
+   * @return set of info about LDAP groups
    */
-  protected Set<String> getExternalLdapGroupNames() {
-    final Set<String> groups = new HashSet<String>();
+  protected Set<LdapGroupDto> getExternalLdapGroupInfo() {
+    final Set<LdapGroupDto> groups = new HashSet<LdapGroupDto>();
     final LdapTemplate ldapTemplate = loadLdapTemplate();
     final EqualsFilter equalsFilter = new EqualsFilter("objectClass",
         ldapServerProperties.getGroupObjectClass());
     String baseDn = ldapServerProperties.getBaseDN();
-    ldapTemplate.search(baseDn, equalsFilter.encode(), new AttributesMapper() {
+    ldapTemplate.search(baseDn, equalsFilter.encode(), new ContextMapper() {
+
+      @Override
+      public Object mapFromContext(Object ctx) {
+        final DirContextAdapter adapter = (DirContextAdapter) ctx;
 
-      public Object mapFromAttributes(Attributes attributes)
-          throws NamingException {
-        groups.add(attributes.get(ldapServerProperties.getGroupNamingAttr())
-            .get().toString().toLowerCase());
+        final LdapGroupDto group = new LdapGroupDto();
+        final String groupNameAttribute = adapter.getStringAttribute(ldapServerProperties.getGroupNamingAttr());
+        group.setGroupName(groupNameAttribute.toLowerCase());
+
+        final String[] uniqueMembers = adapter.getStringAttributes(ldapServerProperties.getGroupMembershipAttr());
+        if (uniqueMembers != null) {
+          for (String uniqueMember: uniqueMembers) {
+            group.getMemberAttributes().add(uniqueMember.toLowerCase());
+          }
+        }
+
+        groups.add(group);
         return null;
       }
     });
@@ -282,51 +317,35 @@ public class AmbariLdapDataPopulator {
   /**
    * Retrieves users from external LDAP server.
    *
-   * @return set of user names
+   * @return set of info about LDAP users
    */
-  protected Set<String> getExternalLdapUserNames() {
-    final Set<String> users = new HashSet<String>();
+  protected Set<LdapUserDto> getExternalLdapUserInfo() {
+    final Set<LdapUserDto> users = new HashSet<LdapUserDto>();
     final LdapTemplate ldapTemplate = loadLdapTemplate();
     final EqualsFilter equalsFilter = new EqualsFilter("objectClass",
         ldapServerProperties.getUserObjectClass());
     String baseDn = ldapServerProperties.getBaseDN();
-    ldapTemplate.search(baseDn, equalsFilter.encode(), new AttributesMapper() {
-
-      public Object mapFromAttributes(Attributes attributes)
-          throws NamingException {
-        users.add(attributes.get(ldapServerProperties.getUsernameAttribute())
-            .get().toString().toLowerCase());
-        return null;
-      }
-    });
-    return users;
-  }
-
-  /**
-   * Retrieves members of the specified group from external LDAP server.
-   *
-   * @param groupName group name
-   * @return set of group names
-   */
-  protected Set<String> getExternalLdapGroupMembers(String groupName) {
-    final Set<String> members = new HashSet<String>();
-    final LdapTemplate ldapTemplate = loadLdapTemplate();
-    final AndFilter andFilter = new AndFilter();
-    andFilter.and(new EqualsFilter("objectClass", ldapServerProperties.getGroupObjectClass()));
-    andFilter.and(new EqualsFilter(ldapServerProperties.getGroupNamingAttr(), groupName));
-    String baseDn = ldapServerProperties.getBaseDN();
-    ldapTemplate.search(baseDn, andFilter.encode(), new ContextMapper() {
+    ldapTemplate.search(baseDn, equalsFilter.encode(), new ContextMapper() {
 
+      @Override
       public Object mapFromContext(Object ctx) {
+        final LdapUserDto user = new LdapUserDto();
         final DirContextAdapter adapter  = (DirContextAdapter) ctx;
-        for (String uniqueMember: adapter.getStringAttributes(ldapServerProperties.getGroupMembershipAttr())) {
-          final DirContextAdapter userAdapter = (DirContextAdapter) ldapTemplate.lookup(uniqueMember);
-          members.add(userAdapter.getStringAttribute(ldapServerProperties.getUsernameAttribute()).toLowerCase());
+        final String usernameAttribute = adapter.getStringAttribute(ldapServerProperties.getUsernameAttribute());
+        final String uidAttribute = adapter.getStringAttribute("uid");
+        if (usernameAttribute != null && uidAttribute != null) {
+          user.setUserName(usernameAttribute.toLowerCase());
+          user.setUid(uidAttribute.toLowerCase());
+          user.setDn(adapter.getNameInNamespace().toLowerCase());
+        } else {
+          LOG.warn("Ignoring LDAP user " + adapter.getNameInNamespace() + " as it doesn't have required" +
+              " attributes uid and " + ldapServerProperties.getUsernameAttribute());
         }
+        users.add(user);
         return null;
       }
     });
-    return members;
+    return users;
   }
 
   /**
@@ -365,6 +384,9 @@ public class AmbariLdapDataPopulator {
    */
   protected Map<String, User> getInternalMembers(String groupName) {
     final Collection<User> internalMembers = users.getGroupMembers(groupName);
+    if (internalMembers == null) {
+      return Collections.emptyMap();
+    }
     final Map<String, User> internalMembersMap = new HashMap<String, User>();
     for (User user : internalMembers) {
       internalMembersMap.put(user.getUserName(), user);
@@ -373,7 +395,7 @@ public class AmbariLdapDataPopulator {
   }
 
   /**
-   * Checks LDAP configuration for changes and reloads LDAP template if they occured.
+   * Checks LDAP configuration for changes and reloads LDAP template if they occurred.
    *
    * @return LdapTemplate instance
    */
@@ -390,8 +412,7 @@ public class AmbariLdapDataPopulator {
 
       if (!ldapServerProperties.isAnonymousBind()) {
         ldapContextSource.setUserDn(ldapServerProperties.getManagerDn());
-        ldapContextSource
-            .setPassword(ldapServerProperties.getManagerPassword());
+        ldapContextSource.setPassword(ldapServerProperties.getManagerPassword());
       }
 
       try {

+ 67 - 0
ambari-server/src/main/java/org/apache/ambari/server/security/ldap/LdapBatchDto.java

@@ -0,0 +1,67 @@
+/**
+ * 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.security.ldap;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Contains information for batch database update on LDAP synchronization.
+ */
+public class LdapBatchDto {
+  private final Set<String> groupsToBecomeLdap = new HashSet<String>();
+  private final Set<String> groupsToBeCreated = new HashSet<String>();
+  private final Set<String> groupsToBeRemoved = new HashSet<String>();
+  private final Set<String> usersToBecomeLdap = new HashSet<String>();
+  private final Set<String> usersToBeCreated = new HashSet<String>();
+  private final Set<String> usersToBeRemoved = new HashSet<String>();
+  private final Set<LdapUserGroupMemberDto> membershipToAdd = new HashSet<LdapUserGroupMemberDto>();
+  private final Set<LdapUserGroupMemberDto> membershipToRemove = new HashSet<LdapUserGroupMemberDto>();
+
+  public Set<String> getGroupsToBecomeLdap() {
+    return groupsToBecomeLdap;
+  }
+
+  public Set<String> getGroupsToBeCreated() {
+    return groupsToBeCreated;
+  }
+
+  public Set<String> getUsersToBecomeLdap() {
+    return usersToBecomeLdap;
+  }
+
+  public Set<String> getUsersToBeCreated() {
+    return usersToBeCreated;
+  }
+
+  public Set<LdapUserGroupMemberDto> getMembershipToAdd() {
+    return membershipToAdd;
+  }
+
+  public Set<LdapUserGroupMemberDto> getMembershipToRemove() {
+    return membershipToRemove;
+  }
+
+  public Set<String> getGroupsToBeRemoved() {
+    return groupsToBeRemoved;
+  }
+
+  public Set<String> getUsersToBeRemoved() {
+    return usersToBeRemoved;
+  }
+}

+ 113 - 0
ambari-server/src/main/java/org/apache/ambari/server/security/ldap/LdapGroupDto.java

@@ -0,0 +1,113 @@
+/**
+ * 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.security.ldap;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Pojo with information about LDAP group of users.
+ */
+public class LdapGroupDto {
+  /**
+   * Name of the group.
+   */
+  private String groupName;
+
+  /**
+   * Set of member attributes. Usually it's either UID or DN of users.
+   */
+  private Set<String> memberAttributes = new HashSet<String>();
+
+  /**
+   * Determines if the LDAP group is synchronized with internal group in database.
+   */
+  private boolean synced;
+
+  /**
+   * Get the group name.
+   *
+   * @return the group name
+   */
+  public String getGroupName() {
+    return groupName;
+  }
+
+  /**
+   * Set the group name.
+   *
+   * @param groupName the group name
+   */
+  public void setGroupName(String groupName) {
+    this.groupName = groupName;
+  }
+
+  /**
+   * Get the member attributes.
+   *
+   * @return the set of member attributes
+   */
+  public Set<String> getMemberAttributes() {
+    return memberAttributes;
+  }
+
+  /**
+   * Set the member attributes.
+   *
+   * @param memberAttributes the member attributes
+   */
+  public void setMemberAttributes(Set<String> memberAttributes) {
+    this.memberAttributes = memberAttributes;
+  }
+
+  /**
+   * Get the synced flag.
+   *
+   * @return the synced flag
+   */
+  public boolean isSynced() {
+    return synced;
+  }
+
+  /**
+   * Set the synced flag
+   *
+   * @param synced the synced flag
+   */
+  public void setSynced(boolean synced) {
+    this.synced = synced;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = groupName != null ? groupName.hashCode() : 0;
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    LdapGroupDto that = (LdapGroupDto) o;
+
+    if (groupName != null ? !groupName.equals(that.getGroupName()) : that.getGroupName() != null) return false;
+
+    return true;
+  }
+}

+ 72 - 0
ambari-server/src/main/java/org/apache/ambari/server/security/ldap/LdapSyncDto.java

@@ -0,0 +1,72 @@
+/**
+ * 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.security.ldap;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Pojo with information about LDAP groups and users.
+ */
+public class LdapSyncDto {
+  /**
+   * LDAP groups.
+   */
+  private Set<LdapGroupDto> groups = new HashSet<LdapGroupDto>();
+
+  /**
+   * LDAP users.
+   */
+  private Set<LdapUserDto> users = new HashSet<LdapUserDto>();
+
+  /**
+   * Get the LDAP groups.
+   *
+   * @return the LDAP groups
+   */
+  public Set<LdapGroupDto> getGroups() {
+    return groups;
+  }
+
+  /**
+   * Set the LDAP groups.
+   *
+   * @param groups the LDAP groups
+   */
+  public void setGroups(Set<LdapGroupDto> groups) {
+    this.groups = groups;
+  }
+
+  /**
+   * Get the LDAP users.
+   *
+   * @return the LDAP users
+   */
+  public Set<LdapUserDto> getUsers() {
+    return users;
+  }
+
+  /**
+   * Set the LDAP users.
+   *
+   * @param users the LDAP users
+   */
+  public void setUsers(Set<LdapUserDto> users) {
+    this.users = users;
+  }
+}

+ 133 - 0
ambari-server/src/main/java/org/apache/ambari/server/security/ldap/LdapUserDto.java

@@ -0,0 +1,133 @@
+/**
+ * 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.security.ldap;
+
+/**
+ * Pojo with information about LDAP user.
+ */
+public class LdapUserDto {
+  /**
+   * Name of the user. Should be always unique.
+   */
+  private String userName;
+
+  /**
+   * Determines if the LDAP user is synchronized with internal user in database.
+   */
+  private boolean synced;
+
+  /**
+   * Unique identifier from LDAP.
+   */
+  private String uid;
+
+  /**
+   * Distinguished name from LDAP.
+   */
+  private String dn;
+
+  /**
+   * Get the user name.
+   *
+   * @return the user name
+   */
+  public String getUserName() {
+    return userName;
+  }
+
+  /**
+   * Set the user name.
+   *
+   * @param userName the user name
+   */
+  public void setUserName(String userName) {
+    this.userName = userName;
+  }
+
+  /**
+   * Get the synced flag.
+   *
+   * @return the synced flag
+   */
+  public boolean isSynced() {
+    return synced;
+  }
+
+  /**
+   * Set the synced flag
+   *
+   * @param synced the synced flag
+   */
+  public void setSynced(boolean synced) {
+    this.synced = synced;
+  }
+
+  /**
+   * Get the UID.
+   *
+   * @return the UID
+   */
+  public String getUid() {
+    return uid;
+  }
+
+  /**
+   * Set the UID.
+   *
+   * @param uid the UID
+   */
+  public void setUid(String uid) {
+    this.uid = uid;
+  }
+
+  /**
+   * Get the DN.
+   *
+   * @return the DN
+   */
+  public String getDn() {
+    return dn;
+  }
+
+  /**
+   * Set the DN.
+   *
+   * @param dn the DN
+   */
+  public void setDn(String dn) {
+    this.dn = dn;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = userName != null ? userName.hashCode() : 0;
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    LdapUserDto that = (LdapUserDto) o;
+
+    if (userName != null ? !userName.equals(that.getUserName()) : that.getUserName() != null) return false;
+
+    return true;
+  }
+}

+ 82 - 0
ambari-server/src/main/java/org/apache/ambari/server/security/ldap/LdapUserGroupMemberDto.java

@@ -0,0 +1,82 @@
+/**
+ * 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.security.ldap;
+
+/**
+ * Pojo with information about LDAP membership.
+ */
+public class LdapUserGroupMemberDto {
+  /**
+   * Name of the group.
+   */
+  private final String groupName;
+
+  /**
+   * Name of the user.
+   */
+  private final String userName;
+
+  /**
+   * Constructor.
+   *
+   * @param groupName group name
+   * @param userName user name
+   */
+  public LdapUserGroupMemberDto(String groupName, String userName) {
+    this.groupName = groupName;
+    this.userName = userName;
+  }
+
+  /**
+   * Get the group name.
+   *
+   * @return the group name
+   */
+  public String getGroupName() {
+    return groupName;
+  }
+
+  /**
+   * Get the user name.
+   *
+   * @return the user name
+   */
+  public String getUserName() {
+    return userName;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    LdapUserGroupMemberDto that = (LdapUserGroupMemberDto) o;
+
+    if (userName != null ? !userName.equals(that.userName) : that.userName != null) return false;
+    if (groupName != null ? !groupName.equals(that.groupName) : that.groupName != null) return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = userName != null ? userName.hashCode() : 0;
+    result = 31 * result + (groupName != null ? groupName.hashCode() : 0);
+    return result;
+  }
+}

+ 3 - 3
ambari-server/src/main/python/ambari-server.py

@@ -2940,11 +2940,11 @@ def setup_ldap():
   LDAP_PRIMARY_URL_DEFAULT = get_value_from_properties(properties, ldap_property_list_reqd[0])
   LDAP_SECONDARY_URL_DEFAULT = get_value_from_properties(properties, ldap_property_list_reqd[1])
   LDAP_USE_SSL_DEFAULT = get_value_from_properties(properties, ldap_property_list_reqd[2], "false")
-  LDAP_USER_CLASS_DEFAULT = get_value_from_properties(properties, ldap_property_list_reqd[3], "person")
+  LDAP_USER_CLASS_DEFAULT = get_value_from_properties(properties, ldap_property_list_reqd[3], "posixAccount")
   LDAP_USER_ATT_DEFAULT = get_value_from_properties(properties, ldap_property_list_reqd[4], "uid")
-  LDAP_GROUP_CLASS_DEFAULT = get_value_from_properties(properties, ldap_property_list_reqd[5], "groupOfUniqueNames")
+  LDAP_GROUP_CLASS_DEFAULT = get_value_from_properties(properties, ldap_property_list_reqd[5], "posixGroup")
   LDAP_GROUP_ATT_DEFAULT = get_value_from_properties(properties, ldap_property_list_reqd[6], "cn")
-  LDAP_GROUP_MEMBER_DEFAULT = get_value_from_properties(properties, ldap_property_list_reqd[7], "uniqueMember")
+  LDAP_GROUP_MEMBER_DEFAULT = get_value_from_properties(properties, ldap_property_list_reqd[7], "memberUid")
   LDAP_BASE_DN_DEFAULT = get_value_from_properties(properties, ldap_property_list_reqd[8])
   LDAP_BIND_DEFAULT = get_value_from_properties(properties, ldap_property_list_reqd[9], "false")
   LDAP_MGR_DN_DEFAULT = get_value_from_properties(properties, ldap_property_list_opt[0])

+ 4 - 0
ambari-server/src/main/resources/META-INF/persistence.xml

@@ -75,7 +75,11 @@
       <!--<property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver" />-->
       <property name="eclipselink.cache.size.default" value="10000" />
       <property name="eclipselink.jdbc.batch-writing" value="JDBC"/>
+      <property name="eclipselink.jdbc.batch-writing.size" value="4000"/>
+      <property name="eclipselink.jdbc.sequence-connection-pool" value="true" />
       <property name="eclipselink.weaving" value="static" />
+      
+      <!--<property name="eclipselink.logging.level.sql" value="FINEST" />-->
       <!--<property name="eclipselink.id-validation" value="NULL" />-->
 
     </properties>

+ 9 - 68
ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapDataPopulatorTest.java → ambari-server/src/test/java/org/apache/ambari/server/security/ldap/AmbariLdapDataPopulatorTest.java

@@ -15,17 +15,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.ambari.server.security.authorization;
+package org.apache.ambari.server.security.ldap;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
 import junit.framework.Assert;
 
 import org.apache.ambari.server.AmbariException;
@@ -35,10 +31,15 @@ import org.apache.ambari.server.orm.entities.MemberEntity;
 import org.apache.ambari.server.orm.entities.PrincipalEntity;
 import org.apache.ambari.server.orm.entities.PrivilegeEntity;
 import org.apache.ambari.server.orm.entities.UserEntity;
+import org.apache.ambari.server.security.authorization.LdapServerProperties;
+import org.apache.ambari.server.security.authorization.User;
+import org.apache.ambari.server.security.authorization.Users;
+import org.apache.ambari.server.security.ldap.AmbariLdapDataPopulator;
 import org.easymock.Capture;
 import org.easymock.EasyMock;
 import org.easymock.IAnswer;
 import org.junit.Test;
+import org.springframework.ldap.core.AttributesMapper;
 import org.springframework.ldap.core.LdapTemplate;
 
 public class AmbariLdapDataPopulatorTest {
@@ -62,66 +63,6 @@ public class AmbariLdapDataPopulatorTest {
     }
   }
 
-  @Test
-  public void testRefreshGroupMembers() throws AmbariException {
-    final Configuration configuration = EasyMock.createNiceMock(Configuration.class);
-    final Users users = EasyMock.createNiceMock(Users.class);
-
-    final GroupEntity ldapGroup = new GroupEntity();
-    ldapGroup.setGroupId(1);
-    ldapGroup.setGroupName("ldapGroup");
-    ldapGroup.setLdapGroup(true);
-    ldapGroup.setMemberEntities(new HashSet<MemberEntity>());
-
-    final User ldapUserWithoutGroup = createLdapUserWithoutGroup();
-    final User ldapUserWithGroup = createLdapUserWithGroup(ldapGroup);
-    final User localUserWithoutGroup = createLocalUserWithoutGroup();
-    final User localUserWithGroup = createLocalUserWithGroup(ldapGroup);
-
-    final AmbariLdapDataPopulator populator = new AmbariLdapDataPopulatorTestInstance(configuration, users) {
-      @Override
-      protected Set<String> getExternalLdapGroupMembers(String groupName) {
-        return new HashSet<String>() {
-          {
-            add(ldapUserWithGroup.getUserName());
-            add(ldapUserWithoutGroup.getUserName());
-          }
-        };
-      }
-
-      @Override
-      protected Map<String, User> getInternalUsers() {
-        return new HashMap<String, User>() {
-          {
-            put(localUserWithGroup.getUserName(), localUserWithGroup);
-            put(localUserWithoutGroup.getUserName(), localUserWithoutGroup);
-          }
-        };
-      }
-
-      @Override
-      protected Map<String, User> getInternalMembers(String groupName) {
-        return new HashMap<String, User>() {
-          {
-            put(localUserWithGroup.getUserName(), localUserWithGroup);
-          }
-        };
-      }
-    };
-
-    users.createUser(EasyMock.<String> anyObject(), EasyMock.<String> anyObject());
-    EasyMock.expectLastCall().times(2);
-
-    users.addMemberToGroup(EasyMock.<String> anyObject(), EasyMock.<String> anyObject());
-    EasyMock.expectLastCall().times(2);
-
-    EasyMock.replay(users);
-
-    populator.refreshGroupMembers(ldapGroup.getGroupName());
-
-    EasyMock.verify(users);
-  }
-
   @Test
   public void testIsLdapEnabled_badConfiguration() {
     final Configuration configuration = EasyMock.createNiceMock(Configuration.class);
@@ -130,7 +71,7 @@ public class AmbariLdapDataPopulatorTest {
     final AmbariLdapDataPopulator populator = new AmbariLdapDataPopulatorTestInstance(configuration, users);
 
     EasyMock.expect(configuration.isLdapConfigured()).andReturn(true);
-    EasyMock.expect(populator.loadLdapTemplate().list(EasyMock. <String>anyObject())).andThrow(new NullPointerException()).once();
+    EasyMock.expect(populator.loadLdapTemplate().search(EasyMock. <String>anyObject(), EasyMock. <String>anyObject(), EasyMock. <AttributesMapper>anyObject())).andThrow(new NullPointerException()).once();
     EasyMock.replay(populator.loadLdapTemplate(), configuration);
 
     Assert.assertFalse(populator.isLdapEnabled());
@@ -145,8 +86,8 @@ public class AmbariLdapDataPopulatorTest {
     final AmbariLdapDataPopulator populator = new AmbariLdapDataPopulatorTestInstance(configuration, users);
 
     EasyMock.expect(configuration.isLdapConfigured()).andReturn(true);
-    EasyMock.expect(populator.loadLdapTemplate().list(EasyMock. <String>anyObject())).andReturn(Collections.emptyList()).once();
-    EasyMock.replay(populator.loadLdapTemplate(),configuration);
+    EasyMock.expect(populator.loadLdapTemplate().search(EasyMock. <String>anyObject(), EasyMock. <String>anyObject(), EasyMock. <AttributesMapper>anyObject())).andReturn(Collections.emptyList()).once();
+    EasyMock.replay(populator.loadLdapTemplate(), configuration);
 
     Assert.assertTrue(populator.isLdapEnabled());
     EasyMock.verify(populator.loadLdapTemplate(), configuration);

+ 94 - 0
ambari-server/src/test/java/org/apache/ambari/server/security/ldap/LdapPerformanceTest.java

@@ -0,0 +1,94 @@
+/*
+ * 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.security.ldap;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.orm.GuiceJpaInitializer;
+import org.apache.ambari.server.security.ClientSecurityType;
+import org.apache.ambari.server.security.authorization.AuthorizationTestModule;
+import org.apache.ambari.server.security.authorization.Users;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+
+/**
+ * Performs sync request to real LDAP server.
+ */
+@Ignore
+public class LdapPerformanceTest {
+
+  private static Injector injector;
+
+  @Inject
+  private AmbariLdapDataPopulator populator;
+
+  @Inject
+  private Users users;
+
+  @Inject
+  Configuration configuration;
+
+  final String SPRING_CONTEXT_LOCATION = "classpath:webapp/WEB-INF/spring-security.xml";
+
+  @Before
+  public void setUp() {
+    injector = Guice.createInjector(new AuthorizationTestModule());
+
+    injector.injectMembers(this);
+    injector.getInstance(GuiceJpaInitializer.class);
+    configuration.setClientSecurityType(ClientSecurityType.LDAP);
+    configuration.setLdap("c6402.ambari.apache.org:389", "posixAccount", "uid",
+        "posixGroup", "cn", "memberUid", "dc=apache,dc=org", false,
+        "uid=hdfs,ou=people,ou=dev,dc=apache,dc=org", "hdfs");
+  }
+
+  @Test
+  public void testLdapSync() throws AmbariException, InterruptedException {
+    long time = System.currentTimeMillis();
+    Set<LdapGroupDto> groups = populator.getExternalLdapGroupInfo();
+    Set<LdapUserDto> users = populator.getExternalLdapUserInfo();
+    Set<String> userNames = new HashSet<String>();
+    for (LdapUserDto user : users) {
+      userNames.add(user.getUserName());
+    }
+    Set<String> groupNames = new HashSet<String>();
+    for (LdapGroupDto group : groups) {
+      groupNames.add(group.getGroupName());
+    }
+    System.out.println("Data fetch: " + (System.currentTimeMillis() - time));
+    time = System.currentTimeMillis();
+    LdapBatchDto batchDto = populator.synchronizeLdapUsersAndGroups(userNames, groupNames);
+    batchDto = populator.synchronizeLdapUsersAndGroups(userNames, groupNames);
+    this.users.processLdapSync(batchDto);
+    System.out.println("Initial sync: " + (System.currentTimeMillis() - time));
+    time = System.currentTimeMillis();
+    batchDto = populator.synchronizeLdapUsersAndGroups(userNames, groupNames);
+    this.users.processLdapSync(batchDto);
+    System.out.println("Subsequent sync: " + (System.currentTimeMillis() - time));
+    time = System.currentTimeMillis();
+  }
+}