Showing posts with label Active Directory. Show all posts
Showing posts with label Active Directory. Show all posts

27/03/2014

SizeLimit, PageSize i dokumentacja

Home

W poście wspomniałem o właściwościach DirectorySearcher.SizeLimit oraz DirectorySearcher.PageSize, których poprawne ustawienie zapewnia, że z bazy danych AD można pobrać więcej obiektów niż ustawiony na serwerze limit. Tym razem chciałbym sprecyzować do czego służą obie właściwości bo moim zdaniem dokumentacja nie jest precyzyjna, co potwierdza zresztą spora liczba pytań w Internecie na ten temat.

Otóż SizeLimit określa maksymalną liczbę obiektów jaka może zostać zwrócona w wyniku wyszukiwania (zero oznacza brak limitu) przy czym limit ustawiony na serwerze ma priorytet. PageSize służy natomiast do skonfigurowania stronicowania i wartość różna od zera je włącza. Na przykład wartość 100 oznacza, że obiekty będą zwracane w paczkach po 100, przy czym .NET jest na tyle miły, że samemu obsługuje doczytywanie kolejnych stron.

Teraz spójrzmy na przykład. W bazie danych AD znajdowało się 1580 obiektów spełniających kryteria wyszukiwania, a limit ustawiony na serwerze wynosił 1500. Poniższa tabelka pokazuje ile obiektów zwróci zapytanie w zależności od ustawień.

SizeLimitPageSizeLiczba obiektów w ADLiczba zwróconych obiektówUwagi
0015801500Brak stronicowania + domyślny limit z serwera
10001580100Brak stronicowania + limit określony przez nas
010015801580Stronicowanie włączone
20010015801580Stronicowanie włączone + limit określony przez nas
1002001580100Stronicowanie włączone + limit określony przez nas

Dwa ostatnie scenariusze są trochę zagadkowe. W obu przypadkach obie właściwości są różne od zera, ale liczba zwróconych obiektów jest inna tj. jeśli SizeLimit > PageSize to z AD pobrano wszystkie dostępne obiekty, a w przeciwnym wypadku tyle ile wynosił SizeLimit. Przypuszczam, że DirectorySearcher działa tak, że pobiera dane póki nie zostanie przekroczony limit. W pierwszym przypadku przy pobieraniu kolejnych stron liczba pobieranych obiektów nie przekracza limitu, a więc udało się odczytać wszystko. W drugim wypadku już przy pobraniu pierwszej strony liczba obiektów przekroczyła limit i dalsze pobieranie zostało zakończone. Pewnie można było to zaimplementować inaczej, ale cóż zrobić i po prostu warto o tym wiedzieć.

18/03/2014

Jak napisać szybki program pobierający dane z AD

Home

Post ten dotyczy tematu efektywnego pobierania danych z Active Directory. Załóżmy, że chcemy pobrać listę użytkowników przy czym interesują nas tylko niektóre właściwości, które ich opisują. Pokażę trzy niewiele różniące się z pozoru sposoby odczytania potrzebnych nam danych. Pozornie ponieważ te trzy podejścia znacząco różnią się wydajnością. W celu zademonstrowania różnic napisałem prostą klasę ADTester. Zawiera ona tylko jedną metodę Run w parametrach, której określamy tryb działania oraz liczbę obiektów do pobrania. Różnica pomiędzy trybami jest następująca:
  • W trybie szybkim (Mode.Fast) dla każdego obiektu z bazy danych Active Directory pobrane zostają tylko wybrane 4 właściwości, a to dzięki zastosowaniu właściwości DirectorySearcher.PropertiesToLoad
  • W trybie normalnym (Mode.Normal) dla każdego obiektu z bazy danych Active Directory pobrane zostają wszystkie dostępne dla danego obiektu właściwości.
  • W trybie wolnym (Mode.Slow) zamiast użyć danych zawartych w obiektach SearchResult korzystam z metody SearchResult.GetDirectoryEntry.
Poniższa tabela pokazuje czas działania programu w ms w zależności od trybu działania oraz liczby obiektów do pobrania:

maxNumberOfObjectsMode.FastMode.NormalMode.Slow
1003634637507
2004076611600
500797221239323
10001353306075935

W najlepszym przypadku tryb prosty jest 2.7 szybszy niż tryb normalny i prawie 60 razy szybszy niż tryb wolny. Różnica jest wręcz powalająca, a wnioski nasuwają się same.
  • Jeśli z góry wiemy jakie właściwości nas interesują to używajmy właściwości DirectorySearcher.PropertiesToLoad.
  • Korzystajmy z danych zwróconych przez klasę DirectorySearcher w postaci obiektów SearchResult.
  • Tylko jeśli to absolutnie konieczne korzystajmy z metody SearchResult.GetDirectoryEntry. Taka potrzeba zachodzi na przykład wtedy jeśli chcemy zmodyfikować dane w AD.
Na koniec jeszcze jedna uwaga. Wyniki czasu działania programu będą się różnić w zależności od tego gdzie znajduje się serwer Active Directory. W moim przypadku znajdował się on poza krajem. Na koniec zamieszczam kod programu do własnych testów:
public class ADTester
{
    public enum Mode { Fast, Normal, Slow }

    public void Run(Mode mode, int maxNumberOfObjects)
    {
        var ldapPath = "YOUR_LDAP_PATH";

        using (var root = new DirectoryEntry(ldapPath))
        {
            using (var searcher = new DirectorySearcher(root)
                    {
                        Filter = "(&(objectClass=user))", SearchScope = SearchScope.Subtree, SizeLimit = maxNumberOfObjects
                    })
            {
                if (mode == Mode.Fast)
                    searcher.PropertiesToLoad.AddRange(new[]{ "displayName","name", "pwdLastSet","userAccountControl" });

                using (SearchResultCollection searchResult = searcher.FindAll())
                {
                    foreach (SearchResult user in searchResult)
                    {
                        if (mode != Mode.Slow)
                        {
                            var displayName = user.Properties["displayName"];
                            ...
                        }
                        else
                        {
                            var entry = user.GetDirectoryEntry();
                            var displayName = entry.Properties["displayName"];
                            ...
                        }
                    }
                }
            }
        }
    }
}

27/01/2014

Ja vs Active Directory

Home

Od niedawna mam okazję pracować, przy pomocy API .NET'owego, z usługami katalogowymi w wydaniu Microsoft'u, czyli z Active Directory. Dla mnie nowa rzecz, więc z zapałem dziecka, które dostało nową zabawkę, zabrałem się do pracy i, jak to często bywa w takich sytuacjach, od razu napotkałem problemy właściwe dla początkujących. W ten sposób dowiedziałem się wielu rzeczy, które dla wyjadaczy mogą być oczywiste, ale dla mnie były nowością. Zebrałem więc je do kupy i tak powstała poniższa lista ku pamięci.

Niektóre z tych rzeczy to drobnostki i jeśli o nich zapomnimy nic wielkiego się nie stanie. Niektóre są jednak bardzo ważne i ich pominięcie może sprowadzić na nas większe kłopoty. Tego rodzaju rzeczy oznaczyłem jako [Ważne]

Projektowanie schematu
  • [Ważne] Przy projektowaniu schematu AD (definicja klas, atrybutów) trzeba być bardzo ostrożnym. AD nie wspiera usuwania klas lub atrybutów, a wiele rzeczy można zdefiniować tylko przy ich tworzeniu, na przykład atrybuty obowiązkowe dla danej klasy. AD wspiera natomiast tzw. dezaktywację. Ma to podobne skutki jak usunięcie, ale i tak wprowadzanie zmian do już używanego schematu AD może być bardzo kłopotliwe.
  • [Ważne] AD to nie C# i nie możesz sobie tak po prostu dodać nowego atrybutu do klasy i oczekiwać, że wszystkie jej instancje zostaną z automatu uaktualnione. Mam tutaj na myśli atrybut mustContain, który określa atrybuty obowiązkowe. Niestety, ale można go podać tylko przy tworzeniu danej klasy. Więcej ograniczeń znajdziecie tutaj.
  • Zamiast atrybutu mustContain można jednak użyć atrybutu mayContain, który określa atrybuty potencjalne.
  • Pisząc skrypt ldif definiujący nowy atrybut nie wystarczy użyć atrybutu attributeSyntax. Potrzebny jest jeszcze atrybut oMSyntax.
Limit 8KB
  • [Ważne] Ponownie AD to nie C#. Obiekty w AD nie mogą mieć więcej niż 8KB, a co za tym idzie, nie mogą zawierać dowolnej liczby atrybutów i wartości. Należy o tym pamiętać szczególnie, jeśli używamy atrybutów wielowartościowych. Ja analizowałem błąd, w którym nie można było dodać nowych wartości do atrybutu właśnie z tego powodu. Przekroczenie limitu 8KB dla danego obiektu skutkuje błędem Administrative limit for this request was exceeded
  • Aby było śmieszniej, nie można łatwo wyznaczyć maksymalnej dopuszczalnej liczby wartości, jakie zmieszczą się w danym atrybucie. Zależy to na przykład od tego, ile atrybutów ma dany obiekt. Ten artykuł zawiera fajną dyskusję praktycznych limitów na liczbę wartości w obiekcie. Sekcja Maximum Database Record Size
Atrybuty powiązane
  • Pewnym rozwiązaniem problemu ograniczonej wielkości obiektów są tzw. atrybuty powiązane (ang. linked attributes), w których zawartość jednego atrybutu zależy od zawartości innych obiektów. Problemem jest natomiast to, że taki wyliczany atrybut musi być typu distinquished name, a więc nie obsłużymy w ten sposób np.: listy liczb całkowitych.
  • [Ważne] Jeśli atrybuty mają być powiązane to muszą być takie od początku.
  • Działanie atrybutów powiązanych może nie być intuicyjne, a więc załączam poglądowy schemat. Jak widać atrybuty połączone to para atrybutów tzw. forward link oraz backward link. Backward link przechowuje wskazania na obiekty, które wskazują go w atrybucie forward link.


Stronicowanie
  • AD ma ograniczenie na liczbę obiektów, jakie można pobrać z bazy w jednym zapytaniu. .NET pozwala to jednak bardzo łatwo obejść dzięki stronicowaniu. Należy ustawić właściwość DirectorySearcher.SizeLimit na 0, a właściwość DirectorySearcher.PageSize na wartość inną niż 0. Nic więcej nie trzeba robić. Więcej szczegółów tutaj.
  • [Ważne] Domyślnie .NET nie używa stronicowania. Jeśli o ty zapomnimy, może dojść do sytuacji, kiedy nasz program nagle przestanie działać, bo nie będzie pobierał wszystkich oczekiwanych obiektów z AD.
    Inne
    • [Ważne] Klasy z przestrzeni nazw System.DirectoryServices, takie jak DirectorySearcher, DirectoryEntry, SearchResultCollection implementują interfejs IDisposable. Piszę o tym ponieważ nawet przykłady na MSDN nie biorą tego pod uwagę!
    • Skrypt ldif, służy do wprowadzania zmian do AD i składa się z sekwencji operacji dodania, usunięcia lub zmodyfikowania czegoś. W przypadku operacji modyfikacji (changetype: add) na końcu powinien znaleźć się znak „-“.
    • Uwaga na spacje w atrybucie cn. Mój skrypt ldif dodający do schematu nowy atrybut wywalił się, bo wartość dla atrybutu cn zawierała na końcu spacje.
    • AD ma różne limity ograniczające, na przykład maksymalna liczbę wartości, jakie można pobrać, maksymalną wielkość strony itp. Pełną listę można znaleźć tutaj.
    Lista ta z pewnością nie jest kompletna. Jeśli macie jakieś sugestie to chętnie ją rozszerzę.