You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2595 lines
74 KiB
2595 lines
74 KiB
/*++
|
|
|
|
Copyright (c) 1996-1997 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
fmreg.c
|
|
|
|
Abstract:
|
|
|
|
Object Manager registry query routines for the Failover Manager
|
|
component of the NT Cluster Service.
|
|
|
|
Author:
|
|
|
|
Rod Gamache (rodga) 14-Mar-1996
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
#include "fmp.h"
|
|
#include <stdlib.h>
|
|
#include <search.h>
|
|
|
|
#define LOG_MODULE FMREG
|
|
|
|
//
|
|
// Global data initialized in this module
|
|
//
|
|
ULONG FmpUnknownCount = 0;
|
|
|
|
//
|
|
// Local functions
|
|
//
|
|
|
|
VOID
|
|
FmpGroupChangeCallback(
|
|
IN DWORD_PTR Context1,
|
|
IN DWORD_PTR Context2,
|
|
IN DWORD CompletionFilter,
|
|
IN LPCWSTR RelativeName
|
|
);
|
|
|
|
VOID
|
|
FmpResourceChangeCallback(
|
|
IN DWORD_PTR Context1,
|
|
IN DWORD_PTR Context2,
|
|
IN DWORD CompletionFilter,
|
|
IN LPCWSTR RelativeName
|
|
);
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Configuration Database Access Routines
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
DWORD
|
|
FmpRegEnumerateKey(
|
|
IN HDMKEY ListKey,
|
|
IN DWORD Index,
|
|
OUT LPWSTR *Name,
|
|
IN OUT LPDWORD NameMaxSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Arguments:
|
|
|
|
Returns:
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD status;
|
|
FILETIME fileTime;
|
|
|
|
|
|
status = DmEnumKey( ListKey,
|
|
Index,
|
|
*Name,
|
|
NameMaxSize,
|
|
NULL );
|
|
|
|
if ( status == ERROR_SUCCESS ) {
|
|
return(ERROR_SUCCESS);
|
|
}
|
|
|
|
if ( status == ERROR_MORE_DATA ) {
|
|
PWCHAR nameString = NULL;
|
|
DWORD maxSubkeyNameSize = 0;
|
|
DWORD temp = 0;
|
|
|
|
//
|
|
// The name string isn't big enough. Reallocate it.
|
|
//
|
|
|
|
//
|
|
// Find out the length of the longest subkey name.
|
|
//
|
|
status = DmQueryInfoKey( ListKey,
|
|
&temp,
|
|
&maxSubkeyNameSize,
|
|
&temp,
|
|
&temp,
|
|
&temp,
|
|
NULL,
|
|
&fileTime );
|
|
|
|
if ( (status != ERROR_SUCCESS) &&
|
|
(status != ERROR_MORE_DATA) ) {
|
|
ClRtlLogPrint(LOG_NOISE,"[FM] DmQueryInfoKey returned status %1!u!\n",
|
|
status);
|
|
return(status);
|
|
}
|
|
|
|
CL_ASSERT(maxSubkeyNameSize != 0);
|
|
|
|
//
|
|
// The returned subkey name size does not include the terminating null.
|
|
// It is also an ANSI string count.
|
|
//
|
|
maxSubkeyNameSize *= sizeof(WCHAR);
|
|
maxSubkeyNameSize += sizeof(UNICODE_NULL);
|
|
|
|
nameString = LocalAlloc( LMEM_FIXED,
|
|
maxSubkeyNameSize );
|
|
|
|
if ( nameString == NULL ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Unable to allocate key name buffer of size %1!u!\n",
|
|
maxSubkeyNameSize
|
|
);
|
|
return(ERROR_NOT_ENOUGH_MEMORY);
|
|
}
|
|
|
|
LocalFree(*Name);
|
|
*Name = nameString;
|
|
*NameMaxSize = maxSubkeyNameSize;
|
|
|
|
status = DmEnumKey( ListKey,
|
|
Index,
|
|
*Name,
|
|
NameMaxSize,
|
|
NULL );
|
|
|
|
CL_ASSERT(status != ERROR_MORE_DATA);
|
|
CL_ASSERT(status != ERROR_NO_MORE_ITEMS);
|
|
}
|
|
|
|
return(status);
|
|
|
|
} // FmpRegEnumerateKey
|
|
|
|
|
|
VOID
|
|
FmpPruneGroupOwners(
|
|
IN PFM_GROUP Group
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Prunes the entire preferred group list based on the possible
|
|
nodes of each resource in the group.
|
|
|
|
Arguments:
|
|
|
|
Group - Supplies the group object to be pruned
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY ListEntry;
|
|
PFM_RESOURCE Resource;
|
|
|
|
ListEntry = Group->Contains.Flink;
|
|
while (ListEntry != &Group->Contains) {
|
|
Resource = CONTAINING_RECORD(ListEntry,
|
|
FM_RESOURCE,
|
|
ContainsLinkage);
|
|
FmpPrunePreferredList(Resource);
|
|
ListEntry = ListEntry->Flink;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
FmpPrunePreferredList(
|
|
IN PFM_RESOURCE Resource
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Prune out nodes from the preferred owners list, if the resource cannot
|
|
run on that node.
|
|
|
|
Arguments:
|
|
|
|
Resource - Pointer to the resource object with a possible owners list.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFM_GROUP group;
|
|
PLIST_ENTRY listEntry;
|
|
PLIST_ENTRY entry;
|
|
PPREFERRED_ENTRY preferredEntry;
|
|
PPOSSIBLE_ENTRY possibleEntry;
|
|
DWORD orderedEntry = 0;
|
|
|
|
group = Resource->Group;
|
|
|
|
//
|
|
// For each entry in the Preferred list, it must exist in the possible
|
|
// list.
|
|
//
|
|
|
|
for ( listEntry = group->PreferredOwners.Flink;
|
|
listEntry != &(group->PreferredOwners);
|
|
) {
|
|
|
|
preferredEntry = CONTAINING_RECORD( listEntry,
|
|
PREFERRED_ENTRY,
|
|
PreferredLinkage );
|
|
//
|
|
// Scan the Possible owners list in the resource to make sure that
|
|
// the group can run on all of the preferred owners.
|
|
//
|
|
for ( entry = Resource->PossibleOwners.Flink;
|
|
entry != &(Resource->PossibleOwners);
|
|
entry = entry->Flink ) {
|
|
possibleEntry = CONTAINING_RECORD( entry,
|
|
POSSIBLE_ENTRY,
|
|
PossibleLinkage );
|
|
if ( preferredEntry->PreferredNode == possibleEntry->PossibleNode ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
listEntry = listEntry->Flink;
|
|
|
|
//
|
|
// If we got to the end of the possible owners list and didn't find
|
|
// an entry, then remove the current preferred entry.
|
|
//
|
|
if ( entry == &(Resource->PossibleOwners) ) {
|
|
ClRtlLogPrint( LOG_NOISE,
|
|
"[FM] Removing preferred node %1!ws! because of resource %2!ws!\n",
|
|
OmObjectId(preferredEntry->PreferredNode),
|
|
OmObjectId(Resource));
|
|
|
|
//
|
|
// If this was an ordered entry, then decrement count.
|
|
//
|
|
if ( orderedEntry < group->OrderedOwners ) {
|
|
--group->OrderedOwners;
|
|
}
|
|
RemoveEntryList( &preferredEntry->PreferredLinkage );
|
|
OmDereferenceObject(preferredEntry->PreferredNode);
|
|
LocalFree(preferredEntry);
|
|
if ( IsListEmpty( &group->PreferredOwners ) ) {
|
|
ClRtlLogPrint( LOG_ERROR,
|
|
"[FM] Preferred owners list is now empty! No place to run group %1!ws!\n",
|
|
OmObjectId(group));
|
|
}
|
|
} else {
|
|
orderedEntry++;
|
|
}
|
|
}
|
|
|
|
} // FmpPrunePreferredList
|
|
|
|
|
|
|
|
BOOL
|
|
FmpAddNodeToPrefList(
|
|
IN PNM_NODE Node,
|
|
IN PFM_GROUP Group
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Node enumeration callback for including all remaining nodes
|
|
in a group's preferred owners list.
|
|
|
|
Arguments:
|
|
|
|
Group - a pointer to the group object to add this node as a preferred owner.
|
|
|
|
Context2 - Not used
|
|
|
|
Node - Supplies the node.
|
|
|
|
Name - Supplies the node's name.
|
|
|
|
Return Value:
|
|
|
|
TRUE - to indicate that the enumeration should continue.
|
|
FALSE - to indicate that the enumeration should not continue.
|
|
|
|
--*/
|
|
|
|
{
|
|
//if it is already in the list FmpSetPrefferedEntry returns ERROR_SUCCESS
|
|
if ( FmpSetPreferredEntry( Group, Node ) != ERROR_SUCCESS ) {
|
|
return(FALSE);
|
|
}
|
|
|
|
return(TRUE);
|
|
|
|
} // FmpAddNodeToPrefList
|
|
|
|
BOOL
|
|
FmpAddNodeToListCb(
|
|
IN OUT PNM_NODE_ENUM2 *ppNmNodeEnum,
|
|
IN LPDWORD pdwAllocatedEntries,
|
|
IN PNM_NODE pNode,
|
|
IN LPCWSTR Id
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Worker callback routine for the enumeration of nodes.
|
|
This routine adds the specified node to the list that is being
|
|
generated.
|
|
|
|
Arguments:
|
|
|
|
ppNmNodeEnum - The node Enumeration list. Can be an output if a new list is
|
|
allocated.
|
|
|
|
EnumData - Supplies the current enumeration data structure.
|
|
|
|
Group - The Group object being enumerated.
|
|
|
|
Id - The Id of the node object being enumerated.
|
|
|
|
Returns:
|
|
|
|
TRUE - to indicate that the enumeration should continue.
|
|
|
|
Side Effects:
|
|
|
|
Makes the quorum group first in the list.
|
|
|
|
--*/
|
|
|
|
{
|
|
PNM_NODE_ENUM2 pNmNodeEnum;
|
|
PNM_NODE_ENUM2 pNewNmNodeEnum;
|
|
DWORD dwNewAllocated;
|
|
DWORD dwStatus;
|
|
|
|
|
|
|
|
pNmNodeEnum = *ppNmNodeEnum;
|
|
|
|
if ( pNmNodeEnum->NodeCount >= *pdwAllocatedEntries )
|
|
{
|
|
//
|
|
// Time to grow the GROUP_ENUM
|
|
//
|
|
|
|
dwNewAllocated = *pdwAllocatedEntries + ENUM_GROW_SIZE;
|
|
pNewNmNodeEnum = LocalAlloc(LMEM_FIXED, NODE_SIZE(dwNewAllocated));
|
|
if ( pNewNmNodeEnum == NULL )
|
|
{
|
|
dwStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
CL_UNEXPECTED_ERROR(dwStatus);
|
|
return(FALSE);
|
|
}
|
|
|
|
CopyMemory(pNewNmNodeEnum, pNmNodeEnum, NODE_SIZE(*pdwAllocatedEntries));
|
|
*pdwAllocatedEntries = dwNewAllocated;
|
|
*ppNmNodeEnum = pNewNmNodeEnum;
|
|
LocalFree(pNmNodeEnum);
|
|
pNmNodeEnum = pNewNmNodeEnum;
|
|
}
|
|
|
|
//
|
|
// Dont copy more than the size
|
|
//
|
|
lstrcpyn( pNmNodeEnum->NodeList[pNmNodeEnum->NodeCount].NodeId,
|
|
Id,
|
|
RTL_NUMBER_OF ( pNmNodeEnum->NodeList[pNmNodeEnum->NodeCount].NodeId ) );
|
|
|
|
++pNmNodeEnum->NodeCount;
|
|
|
|
return(TRUE);
|
|
|
|
} // FmpAddNodeToListCb
|
|
|
|
int
|
|
__cdecl
|
|
SortNodesInAscending(
|
|
const PVOID Elem1,
|
|
const PVOID Elem2
|
|
)
|
|
{
|
|
PNM_NODE_INFO2 El1 = (PNM_NODE_INFO2)Elem1;
|
|
PNM_NODE_INFO2 El2 = (PNM_NODE_INFO2)Elem2;
|
|
|
|
return(lstrcmpiW( El1->NodeId, El2->NodeId ));
|
|
|
|
}// SortNodesInAsceding
|
|
|
|
|
|
DWORD
|
|
FmpEnumNodesById(
|
|
IN DWORD dwOptions,
|
|
OUT PNM_NODE_ENUM2 *ppNodeEnum
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Enumerates and sorts the list of Groups.
|
|
|
|
Arguments:
|
|
|
|
*ppNodeEnum - Returns the requested objects.
|
|
|
|
dwOptions -
|
|
|
|
Return Value:
|
|
|
|
ERROR_SUCCESS if successful.
|
|
|
|
Win32 error code on error.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD dwStatus;
|
|
PNM_NODE_ENUM2 pNmNodeEnum = NULL;
|
|
DWORD dwAllocatedEntries;
|
|
|
|
//
|
|
// initialize output params to NULL
|
|
//
|
|
*ppNodeEnum = NULL;
|
|
|
|
dwAllocatedEntries = ENUM_GROW_SIZE;
|
|
|
|
pNmNodeEnum = LocalAlloc( LMEM_FIXED, NODE_SIZE(ENUM_GROW_SIZE) );
|
|
if ( pNmNodeEnum == NULL ) {
|
|
dwStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto error_exit;
|
|
}
|
|
|
|
pNmNodeEnum->NodeCount = 0;
|
|
|
|
//
|
|
// Enumerate all nodes
|
|
//
|
|
|
|
OmEnumObjects( ObjectTypeNode,
|
|
FmpAddNodeToListCb,
|
|
&pNmNodeEnum,
|
|
&dwAllocatedEntries );
|
|
|
|
CL_ASSERT( pNmNodeEnum->NodeCount != 0 );
|
|
//
|
|
// Sort the groups by their collating sequence number.
|
|
//
|
|
|
|
qsort( (PVOID)(&pNmNodeEnum->NodeList[0]),
|
|
(size_t)pNmNodeEnum->NodeCount,
|
|
sizeof(NM_NODE_INFO2),
|
|
(int (__cdecl *)(const void*, const void*)) SortNodesInAscending
|
|
);
|
|
|
|
*ppNodeEnum = pNmNodeEnum;
|
|
return( ERROR_SUCCESS );
|
|
|
|
error_exit:
|
|
if ( pNmNodeEnum != NULL ) {
|
|
LocalFree( pNmNodeEnum );
|
|
}
|
|
|
|
return( dwStatus );
|
|
|
|
} // FmpEnumNodesById
|
|
|
|
|
|
BOOL
|
|
FmpEnumAddAllOwners(
|
|
IN PFM_RESOURCE Resource,
|
|
IN PVOID Context2,
|
|
IN PNM_NODE Node,
|
|
IN LPCWSTR Name
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Node enumeration callback for adding all nodes to a resource's
|
|
list of possible nodes.
|
|
|
|
Arguments:
|
|
|
|
Resource - a pointer to the resource object to add this node as a possible owner.
|
|
|
|
Context2 - Not used
|
|
|
|
Node - Supplies the node.
|
|
|
|
Name - Supplies the node's name.
|
|
|
|
Return Value:
|
|
|
|
TRUE - to indicate that the enumeration should continue.
|
|
FALSE - to indicate that the enumeration should not continue.
|
|
|
|
--*/
|
|
|
|
{
|
|
if ( !Resource->PossibleList ) {
|
|
FmpAddPossibleEntry(Resource, Node);
|
|
}
|
|
return(TRUE);
|
|
|
|
} // FmpEnumAddAllOwners
|
|
|
|
|
|
DWORD
|
|
FmpQueryGroupNodes(
|
|
IN PFM_GROUP Group,
|
|
IN HDMKEY hGroupKey
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Rebuilds and orders the list of preferred nodes associated with
|
|
a group.
|
|
|
|
Arguments:
|
|
|
|
Group - Supplies the group whose list of preferred nodes should
|
|
be rebuilt.
|
|
|
|
hGroupKey - Supplies a handle to the group's registry key
|
|
|
|
Return Value:
|
|
|
|
ERROR_SUCCESS if successful
|
|
|
|
Win32 error code otherwise
|
|
|
|
--*/
|
|
|
|
{
|
|
LPWSTR preferredOwnersString = NULL;
|
|
DWORD preferredOwnersStringSize = 0;
|
|
DWORD preferredOwnersStringMaxSize = 0;
|
|
DWORD mszStringIndex;
|
|
PPREFERRED_ENTRY preferredEntry;
|
|
DWORD status;
|
|
PLIST_ENTRY listEntry;
|
|
PNM_NODE_ENUM2 pNmNodeEnum = NULL;
|
|
PNM_NODE pNmNode;
|
|
DWORD i;
|
|
//
|
|
// First, delete the old list.
|
|
//
|
|
while ( !IsListEmpty(&Group->PreferredOwners) ) {
|
|
listEntry = Group->PreferredOwners.Flink;
|
|
preferredEntry = CONTAINING_RECORD( listEntry,
|
|
PREFERRED_ENTRY,
|
|
PreferredLinkage );
|
|
RemoveEntryList( &preferredEntry->PreferredLinkage );
|
|
OmDereferenceObject( preferredEntry->PreferredNode );
|
|
LocalFree( preferredEntry );
|
|
}
|
|
Group->OrderedOwners = 0;
|
|
|
|
CL_ASSERT ( IsListEmpty(&Group->PreferredOwners) );
|
|
|
|
status = DmQueryMultiSz( hGroupKey,
|
|
CLUSREG_NAME_GRP_PREFERRED_OWNERS,
|
|
&preferredOwnersString,
|
|
&preferredOwnersStringMaxSize,
|
|
&preferredOwnersStringSize );
|
|
|
|
if ( status == NO_ERROR ) {
|
|
|
|
//
|
|
// Now Create the Preferred Owners list.
|
|
//
|
|
|
|
for ( mszStringIndex = 0; ; mszStringIndex++ ) {
|
|
LPCWSTR nameString;
|
|
PNM_NODE preferredNode;
|
|
|
|
nameString = ClRtlMultiSzEnum( preferredOwnersString,
|
|
preferredOwnersStringSize/sizeof(WCHAR),
|
|
mszStringIndex );
|
|
|
|
if ( nameString == NULL ) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Create the Preferred Owners List entry
|
|
//
|
|
|
|
preferredEntry = LocalAlloc( LMEM_FIXED, sizeof(PREFERRED_ENTRY) );
|
|
|
|
if ( preferredEntry == NULL ) {
|
|
status = ERROR_NOT_ENOUGH_MEMORY;
|
|
return(status);
|
|
}
|
|
|
|
//
|
|
// Create the preferred owners. This will implicitly create
|
|
// additional reference required for the preferred owner nodes.
|
|
//
|
|
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Group %1!ws! preferred owner %2!ws!.\n",
|
|
OmObjectId(Group),
|
|
nameString);
|
|
|
|
preferredNode = OmReferenceObjectById( ObjectTypeNode,
|
|
nameString );
|
|
|
|
if ( preferredNode == NULL ) {
|
|
LocalFree(preferredEntry);
|
|
status = GetLastError();
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Failed to find node %1!ws! for Group %2!ws!\n",
|
|
nameString,
|
|
OmObjectId(Group));
|
|
} else {
|
|
Group->OrderedOwners++;
|
|
preferredEntry->PreferredNode = preferredNode;
|
|
InsertTailList( &Group->PreferredOwners,
|
|
&preferredEntry->PreferredLinkage );
|
|
}
|
|
|
|
}
|
|
LocalFree( preferredOwnersString );
|
|
}
|
|
|
|
//
|
|
// We now include all remaining nodes in the preferred owners list.
|
|
//
|
|
// Every node must maintain the same ordering for the preferred list
|
|
// for the multi-node cluster to work
|
|
//
|
|
status = FmpEnumNodesById( 0, &pNmNodeEnum );
|
|
|
|
if ( status != ERROR_SUCCESS )
|
|
{
|
|
CL_UNEXPECTED_ERROR( status );
|
|
ClRtlLogPrint(LOG_UNUSUAL,
|
|
"[FM] FmpQueryGroupNodes: FmpEnumNodesById failed, status = %1!u!\r\n",
|
|
status);
|
|
// return error
|
|
}
|
|
|
|
for ( i=0; i<pNmNodeEnum->NodeCount; i++ )
|
|
{
|
|
pNmNode = OmReferenceObjectById( ObjectTypeNode,
|
|
pNmNodeEnum->NodeList[i].NodeId );
|
|
CL_ASSERT( pNmNode != NULL );
|
|
FmpAddNodeToPrefList( pNmNode, Group );
|
|
OmDereferenceObject( pNmNode );
|
|
}
|
|
|
|
//
|
|
// Now prune out all the unreachable nodes.
|
|
//
|
|
FmpPruneGroupOwners( Group );
|
|
|
|
//
|
|
// Chittur Subbaraman (chitturs) - 12/11/98
|
|
//
|
|
// Free the memory allocated for pNmNodeEnum.
|
|
// (Fix memory leak)
|
|
//
|
|
LocalFree( pNmNodeEnum );
|
|
|
|
return( ERROR_SUCCESS );
|
|
|
|
} // FmpQueryGroupNodes
|
|
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
FmpQueryGroupInfo(
|
|
IN PVOID Object,
|
|
IN BOOL Initialize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Queries Group info from the registry when creating a Group Object.
|
|
|
|
Arguments:
|
|
|
|
Object - A pointer to the Group object being created.
|
|
|
|
Initialize - TRUE if the resource objects should be initialized. FALSE
|
|
otherwise.
|
|
|
|
Return Value:
|
|
|
|
ERROR_SUCCESS if successful.
|
|
|
|
Win32 error code otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFM_GROUP Group = (PFM_GROUP)Object;
|
|
PFM_RESOURCE Resource;
|
|
DWORD status;
|
|
LPWSTR containsString = NULL;
|
|
DWORD containsStringSize = 0;
|
|
DWORD containsStringMaxSize = 0;
|
|
DWORD temp;
|
|
DWORD mszStringIndex;
|
|
DWORD failoverThreshold = CLUSTER_GROUP_DEFAULT_FAILOVER_THRESHOLD;
|
|
DWORD failoverPeriod = CLUSTER_GROUP_DEFAULT_FAILOVER_PERIOD;
|
|
DWORD autoFailbackType = CLUSTER_GROUP_DEFAULT_AUTO_FAILBACK_TYPE;
|
|
DWORD zero = 0;
|
|
PLIST_ENTRY listEntry;
|
|
HDMKEY groupKey;
|
|
DWORD groupNameStringMaxSize = 0;
|
|
DWORD groupNameStringSize = 0;
|
|
LPWSTR groupName;
|
|
PPREFERRED_ENTRY preferredEntry;
|
|
DWORD dwBufferSize = 0;
|
|
DWORD dwStringSize;
|
|
|
|
|
|
//
|
|
// Initialize the Group object from the registry info.
|
|
//
|
|
if ( Group->Initialized ) {
|
|
return(ERROR_SUCCESS);
|
|
}
|
|
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Initializing group %1!ws! from the registry.\n",
|
|
OmObjectId(Group));
|
|
|
|
//
|
|
// Open the group key.
|
|
//
|
|
groupKey = DmOpenKey( DmGroupsKey,
|
|
OmObjectId(Group),
|
|
MAXIMUM_ALLOWED );
|
|
|
|
if ( groupKey == NULL ) {
|
|
status = GetLastError();
|
|
ClRtlLogPrint(LOG_UNUSUAL,
|
|
"[FM] Unable to open group key %1!ws!, %2!u!\n",
|
|
OmObjectId(Group),
|
|
status);
|
|
|
|
return(status);
|
|
}
|
|
|
|
//
|
|
// Read the required group values. The strings will be allocated
|
|
// by the DmQuery* functions.
|
|
//
|
|
|
|
//
|
|
// Get the Name.
|
|
//
|
|
status = DmQuerySz( groupKey,
|
|
CLUSREG_NAME_GRP_NAME,
|
|
&groupName,
|
|
&groupNameStringMaxSize,
|
|
&groupNameStringSize );
|
|
|
|
if (status != ERROR_SUCCESS) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Unable to read name for Group %1!ws!\n",
|
|
OmObjectId(Group));
|
|
goto error_exit;
|
|
}
|
|
|
|
status = OmSetObjectName( Group, groupName );
|
|
|
|
if ( status != ERROR_SUCCESS ) {
|
|
ClRtlLogPrint(LOG_ERROR,
|
|
"[FM] Unable to set name %1!ws! for group %2!ws!, error %3!u!.\n",
|
|
groupName,
|
|
OmObjectId(Group),
|
|
status );
|
|
LocalFree(groupName);
|
|
goto error_exit;
|
|
}
|
|
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Name for Group %1!ws! is '%2!ws!'.\n",
|
|
OmObjectId(Group),
|
|
groupName);
|
|
|
|
LocalFree(groupName);
|
|
//
|
|
// Get the PersistentState.
|
|
//
|
|
status = DmQueryDword( groupKey,
|
|
CLUSREG_NAME_GRP_PERSISTENT_STATE,
|
|
&temp,
|
|
&zero );
|
|
|
|
//
|
|
// If the group state is non-zero then we go online.
|
|
//
|
|
if ( temp ) {
|
|
Group->PersistentState = ClusterGroupOnline;
|
|
} else {
|
|
Group->PersistentState = ClusterGroupOffline;
|
|
}
|
|
|
|
//
|
|
// Get the OPTIONAL PreferredOwners list.
|
|
// *** NOTE *** This MUST be done before processing the contains list!
|
|
//
|
|
status = FmpQueryGroupNodes(Group, groupKey);
|
|
if (status != ERROR_SUCCESS) {
|
|
ClRtlLogPrint(LOG_UNUSUAL,"[FM] Error %1!d! creating preferred owners list\n",status);
|
|
goto error_exit;
|
|
}
|
|
|
|
|
|
//
|
|
// Get the Contains string.
|
|
//
|
|
status = DmQueryMultiSz( groupKey,
|
|
CLUSREG_NAME_GRP_CONTAINS,
|
|
&containsString,
|
|
&containsStringMaxSize,
|
|
&containsStringSize );
|
|
|
|
if ( status != NO_ERROR ) {
|
|
if ( status != ERROR_FILE_NOT_FOUND ) {
|
|
ClRtlLogPrint(LOG_UNUSUAL,
|
|
"[FM] Unable to read Contains for group %1!ws!\n",
|
|
OmObjectId(Group));
|
|
}
|
|
} else {
|
|
//
|
|
// Now Create the Contains list.
|
|
//
|
|
|
|
for ( mszStringIndex = 0; ; mszStringIndex++ ) {
|
|
LPCWSTR nameString;
|
|
PFM_RESOURCE containedResource;
|
|
|
|
nameString = ClRtlMultiSzEnum( containsString,
|
|
containsStringSize/sizeof(WCHAR),
|
|
mszStringIndex );
|
|
|
|
if ( nameString == NULL ) {
|
|
break;
|
|
}
|
|
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Group %1!ws! contains Resource %2!ws!.\n",
|
|
OmObjectId(Group),
|
|
nameString);
|
|
|
|
//
|
|
// Try to create the object.
|
|
//
|
|
FmpAcquireResourceLock();
|
|
FmpAcquireLocalGroupLock( Group );
|
|
|
|
containedResource = FmpCreateResource( Group,
|
|
nameString,
|
|
NULL,
|
|
Initialize );
|
|
FmpReleaseLocalGroupLock( Group );
|
|
FmpReleaseResourceLock();
|
|
|
|
//
|
|
// Check if we got a resource.
|
|
//
|
|
if ( containedResource == NULL ) {
|
|
//
|
|
// This group claims to contain a non-existent resource.
|
|
// Log an error, but keep going. This should not tank the
|
|
// whole group. Also, let the arbitration code know about
|
|
// the failure of a resource.
|
|
//
|
|
Group->InitFailed = TRUE;
|
|
ClRtlLogPrint(LOG_UNUSUAL,
|
|
"[FM] Failed to find resource %1!ws! for Group %2!ws!\n",
|
|
nameString,
|
|
OmObjectId(Group));
|
|
}
|
|
}
|
|
LocalFree(containsString);
|
|
|
|
}
|
|
|
|
//
|
|
// Get the AutoFailbackType.
|
|
//
|
|
|
|
status = DmQueryDword( groupKey,
|
|
CLUSREG_NAME_GRP_FAILBACK_TYPE,
|
|
&temp,
|
|
&autoFailbackType );
|
|
|
|
//
|
|
// Verify that AutoFailbackType is okay.
|
|
//
|
|
|
|
if ( temp >= FailbackMaximum ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Illegal value for AutoFailbackType on %1!ws!, setting to default\n",
|
|
OmObjectId(Group));
|
|
temp = autoFailbackType;
|
|
}
|
|
|
|
Group->FailbackType = (UCHAR)temp;
|
|
|
|
//
|
|
// Get the FailbackWindowStart.
|
|
//
|
|
status = DmQueryDword( groupKey,
|
|
CLUSREG_NAME_GRP_FAILBACK_WIN_START,
|
|
&temp,
|
|
&zero );
|
|
|
|
//
|
|
// Verify that FailbackWindowStart is okay.
|
|
//
|
|
if ( temp > 24 ) {
|
|
if ( temp != CLUSTER_GROUP_DEFAULT_FAILBACK_WINDOW_START ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Illegal value for FailbackWindowStart on %1!ws!,setting to default\n",
|
|
OmObjectId(Group));
|
|
temp = zero;
|
|
}
|
|
}
|
|
Group->FailbackWindowStart = (UCHAR)temp;
|
|
|
|
//
|
|
// Get the FailbackWindowEnd.
|
|
//
|
|
status = DmQueryDword( groupKey,
|
|
CLUSREG_NAME_GRP_FAILBACK_WIN_END,
|
|
&temp,
|
|
&zero );
|
|
|
|
//
|
|
// Verify that FailbackWindowEnd is okay.
|
|
//
|
|
|
|
if ( temp > 24 ) {
|
|
if ( temp != CLUSTER_GROUP_DEFAULT_FAILBACK_WINDOW_END ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Illegal value for FailbackWindowEnd on %1!ws!, setting to default\n",
|
|
OmObjectId(Group));
|
|
temp = zero;
|
|
}
|
|
}
|
|
Group->FailbackWindowEnd = (UCHAR)temp;
|
|
|
|
//
|
|
// Get the FailoverPeriod.
|
|
//
|
|
status = DmQueryDword( groupKey,
|
|
CLUSREG_NAME_GRP_FAILOVER_PERIOD,
|
|
&temp,
|
|
&failoverPeriod );
|
|
|
|
//
|
|
// Verify that FailoverPeriod is okay. Take any value up to UCHAR max.
|
|
// In theory we could take any value... but in practice we have to convert
|
|
// this time to milliseconds (currently). That means that 1193 hours can
|
|
// fit in a DWORD - so that is the maximum we can take. (We are limited
|
|
// because we use GetTickCount, which returns a DWORD in milliseconds.)
|
|
//
|
|
|
|
if ( temp > CLUSTER_GROUP_MAXIMUM_FAILOVER_PERIOD ) { // Keep it positive?
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Illegal value for FailolverPeriod on %1!ws!. Max is 1193\n",
|
|
OmObjectId(Group));
|
|
temp = failoverPeriod;
|
|
}
|
|
|
|
Group->FailoverPeriod = (UCHAR)temp;
|
|
|
|
|
|
//
|
|
// Get the FailoverThreshold.
|
|
//
|
|
status = DmQueryDword( groupKey,
|
|
CLUSREG_NAME_GRP_FAILOVER_THRESHOLD,
|
|
&(Group->FailoverThreshold),
|
|
&failoverThreshold );
|
|
|
|
//
|
|
// Verify that FailoverThreshold is okay. Take any value.
|
|
//
|
|
|
|
//
|
|
// Get the AntiAffinityClassName property if present.
|
|
//
|
|
status = DmQueryMultiSz( groupKey,
|
|
CLUSREG_NAME_GRP_ANTI_AFFINITY_CLASS_NAME,
|
|
&Group->lpszAntiAffinityClassName,
|
|
&dwBufferSize,
|
|
&dwStringSize );
|
|
|
|
//
|
|
// Handle the case in which the string is empty.
|
|
//
|
|
if ( ( status == ERROR_SUCCESS ) &&
|
|
( Group->lpszAntiAffinityClassName != NULL ) &&
|
|
( Group->lpszAntiAffinityClassName[0] == L'\0' ) )
|
|
{
|
|
LocalFree( Group->lpszAntiAffinityClassName );
|
|
Group->lpszAntiAffinityClassName = NULL;
|
|
}
|
|
|
|
//
|
|
// We're done. We should only get here if Group->Initialized is FALSE.
|
|
//
|
|
CL_ASSERT( Group->Initialized == FALSE );
|
|
Group->Initialized = TRUE;
|
|
Group->RegistryKey = groupKey;
|
|
|
|
//
|
|
// Now register for any changes to the resource key.
|
|
//
|
|
|
|
status = DmNotifyChangeKey(
|
|
groupKey,
|
|
(DWORD) CLUSTER_CHANGE_ALL,
|
|
FALSE, // Only watch the top of the tree
|
|
&Group->DmRundownList,
|
|
FmpGroupChangeCallback,
|
|
(DWORD_PTR)Group,
|
|
0 );
|
|
|
|
if ( status != ERROR_SUCCESS) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Error registering for DM change notify on group %1!ws!, error %2!u!.\n",
|
|
OmObjectId(Group),
|
|
status);
|
|
goto error_exit;
|
|
}
|
|
|
|
return(ERROR_SUCCESS);
|
|
|
|
|
|
error_exit:
|
|
|
|
Group->Initialized = FALSE;
|
|
Group->RegistryKey = NULL;
|
|
|
|
DmCloseKey(groupKey);
|
|
|
|
//
|
|
// Cleanup any contained resources
|
|
//
|
|
while ( !IsListEmpty(&Group->Contains) ) {
|
|
listEntry = RemoveHeadList(&Group->Contains);
|
|
Resource = CONTAINING_RECORD(listEntry, FM_RESOURCE, ContainsLinkage);
|
|
OmDereferenceObject(Resource);
|
|
}
|
|
|
|
//
|
|
// Cleanup any preferred nodes
|
|
//
|
|
while ( !IsListEmpty(&Group->PreferredOwners) ) {
|
|
listEntry = RemoveHeadList(&Group->PreferredOwners);
|
|
preferredEntry = CONTAINING_RECORD(listEntry, PREFERRED_ENTRY, PreferredLinkage);
|
|
OmDereferenceObject(preferredEntry->PreferredNode);
|
|
LocalFree(preferredEntry);
|
|
}
|
|
|
|
return(status);
|
|
|
|
} // FmpQueryGroupInfo
|
|
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
FmpFixupGroupInfo(
|
|
IN PFM_GROUP Group
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Re-queries Group info from the registry to fixup items that may have
|
|
changed since the quorum resource (and the Group that it is in) was
|
|
first created.
|
|
|
|
This routine exists because we may have created the quorum resource
|
|
(and its Group) early in the 'life' of the cluster, before all the node
|
|
objects (for example) were created. We then would have failed generating
|
|
the list of possible owners for the resource. This in turn would have
|
|
caused some entries from the preferred list to get pruned. We need to
|
|
redo this operation again here.
|
|
|
|
Arguments:
|
|
|
|
Group - A pointer to the Group object to fix up.
|
|
|
|
Return Value:
|
|
|
|
ERROR_SUCCESS if successful.
|
|
|
|
Win32 error code otherwise.
|
|
|
|
Notes:
|
|
|
|
It is assumed that the quorum resource fixup already has happened.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD status;
|
|
|
|
status = FmpQueryGroupNodes(Group, Group->RegistryKey);
|
|
|
|
return(status);
|
|
|
|
} // FmpFixupGroupInfo
|
|
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
FmpQueryResourceInfo(
|
|
IN PVOID Object,
|
|
IN BOOL Initialize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Queries Resource info from the registry when creating a Resource Object.
|
|
|
|
Arguments:
|
|
|
|
Object - A pointer to the Resource object being created.
|
|
|
|
Initialize - TRUE if the resource should be fully initialized.
|
|
FALSE otherwise.
|
|
|
|
Return Value:
|
|
|
|
ERROR_SUCCESS if successful.
|
|
|
|
Win32 error code otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFM_RESOURCE Resource = (PFM_RESOURCE)Object;
|
|
DWORD status;
|
|
DWORD dllNameStringSize = 0;
|
|
DWORD dllNameStringMaxSize = 0;
|
|
LPWSTR resourceTypeString = NULL;
|
|
DWORD resourceTypeStringMaxSize = 0;
|
|
DWORD resourceTypeStringSize = 0;
|
|
DWORD dependenciesStringMaxSize = 0;
|
|
DWORD restartThreshold = CLUSTER_RESOURCE_DEFAULT_RESTART_THRESHOLD;
|
|
DWORD restartPeriod = CLUSTER_RESOURCE_DEFAULT_RESTART_PERIOD;
|
|
DWORD pendingTimeout = CLUSTER_RESOURCE_DEFAULT_PENDING_TIMEOUT;
|
|
DWORD RetryPeriodOnFailure = CLUSTER_RESOURCE_DEFAULT_RETRY_PERIOD_ON_FAILURE;
|
|
DWORD defaultRestartAction = RestartGroup;
|
|
DWORD DefaultExFlags = 0;
|
|
DWORD zero = 0;
|
|
DWORD temp;
|
|
DWORD separateMonitor;
|
|
HDMKEY resourceKey;
|
|
DWORD resourceNameStringMaxSize = 0;
|
|
DWORD resourceNameStringSize = 0;
|
|
LPWSTR resourceName = NULL;
|
|
LPWSTR possibleOwnersString = NULL;
|
|
DWORD possibleOwnersStringSize = 0;
|
|
DWORD possibleOwnersStringMaxSize = 0;
|
|
DWORD mszStringIndex;
|
|
PPOSSIBLE_ENTRY possibleEntry;
|
|
PLIST_ENTRY listEntry;
|
|
WCHAR unknownName[] = L"_Unknown9999";
|
|
DWORD nameSize = 0;
|
|
DWORD stringSize;
|
|
|
|
//if the key is non null, this resource has already been initialized
|
|
if (Resource->RegistryKey != NULL)
|
|
return(ERROR_SUCCESS);
|
|
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Initializing resource %1!ws! from the registry.\n",
|
|
OmObjectId(Resource));
|
|
|
|
//
|
|
// Begin initializing the resource from the registry.
|
|
//
|
|
//
|
|
// Open the resource key.
|
|
//
|
|
resourceKey = DmOpenKey( DmResourcesKey,
|
|
OmObjectId(Resource),
|
|
MAXIMUM_ALLOWED );
|
|
|
|
if ( resourceKey == NULL ) {
|
|
status = GetLastError();
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Unable to open resource key %1!ws!, %2!u!\n",
|
|
OmObjectId(Resource),
|
|
status);
|
|
return(ERROR_INVALID_NAME);
|
|
}
|
|
|
|
//
|
|
// Read the required resource values. The strings will be allocated
|
|
// by the DmQuery* functions.
|
|
//
|
|
|
|
//
|
|
// Get the Name.
|
|
//
|
|
status = DmQuerySz( resourceKey,
|
|
CLUSREG_NAME_RES_NAME,
|
|
&resourceName,
|
|
&resourceNameStringMaxSize,
|
|
&resourceNameStringSize );
|
|
|
|
if (status != ERROR_SUCCESS) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Unable to read name for resource %1!ws!\n",
|
|
OmObjectId(Resource));
|
|
if ( OmObjectName( Resource ) == NULL ) {
|
|
wsprintf( unknownName,
|
|
L"_Unknown%u",
|
|
InterlockedIncrement( &FmpUnknownCount ));
|
|
status = OmSetObjectName( Resource, unknownName );
|
|
} else {
|
|
status = ERROR_SUCCESS;
|
|
}
|
|
} else {
|
|
status = OmSetObjectName( Resource, resourceName );
|
|
}
|
|
if ( status != ERROR_SUCCESS ) {
|
|
ClRtlLogPrint(LOG_ERROR,
|
|
"[FM] Unable to set name %1!ws! for resource %2!ws!, error %3!u!.\n",
|
|
resourceName,
|
|
OmObjectId(Resource),
|
|
status );
|
|
LocalFree(resourceName);
|
|
status = ERROR_INVALID_NAME;
|
|
goto error_exit;
|
|
}
|
|
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Name for Resource %1!ws! is '%2!ws!'.\n",
|
|
OmObjectId(Resource),
|
|
resourceName);
|
|
|
|
LocalFree(resourceName);
|
|
|
|
//
|
|
// Get the dependencies list.
|
|
//
|
|
|
|
status = DmQueryMultiSz( resourceKey,
|
|
CLUSREG_NAME_RES_DEPENDS_ON,
|
|
&(Resource->Dependencies),
|
|
&dependenciesStringMaxSize,
|
|
&(Resource->DependenciesSize) );
|
|
|
|
if (status != NO_ERROR) {
|
|
if ( status != ERROR_FILE_NOT_FOUND ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Unable to read Dependencies for resource %1!ws!\n",
|
|
OmObjectId(Resource));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get the OPTIONAL PossibleOwners list.
|
|
//
|
|
// We do this here, because we must have a possible owners list for the
|
|
// CluAdmin to start the resource.
|
|
//
|
|
|
|
status = DmQueryMultiSz( resourceKey,
|
|
CLUSREG_NAME_RES_POSSIBLE_OWNERS,
|
|
&possibleOwnersString,
|
|
&possibleOwnersStringMaxSize,
|
|
&possibleOwnersStringSize );
|
|
|
|
if ( status == NO_ERROR ) {
|
|
|
|
//
|
|
// Now Create the Possible Owners list.
|
|
//
|
|
|
|
for ( mszStringIndex = 0; ; mszStringIndex++ ) {
|
|
LPCWSTR nameString;
|
|
PNM_NODE possibleNode;
|
|
|
|
nameString = ClRtlMultiSzEnum( possibleOwnersString,
|
|
possibleOwnersStringSize/sizeof(WCHAR),
|
|
mszStringIndex );
|
|
|
|
if ( nameString == NULL ) {
|
|
break;
|
|
}
|
|
possibleNode = OmReferenceObjectById( ObjectTypeNode,
|
|
nameString );
|
|
|
|
if ( possibleNode == NULL ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Warning, failed to find node %1!ws! for Resource %2!ws!\n",
|
|
nameString,
|
|
OmObjectId(Resource));
|
|
} else {
|
|
Resource->PossibleList = TRUE;
|
|
status = FmpAddPossibleEntry(Resource, possibleNode);
|
|
OmDereferenceObject(possibleNode);
|
|
if (status != ERROR_SUCCESS) {
|
|
goto error_exit;
|
|
}
|
|
}
|
|
}
|
|
LocalFree(possibleOwnersString);
|
|
|
|
//
|
|
// Now prune out unusable nodes from the preferred owners list.
|
|
//
|
|
FmpPrunePreferredList( Resource );
|
|
|
|
} else {
|
|
//
|
|
// No possible owners value was specified. Add all the nodes
|
|
// to the possible owners list. Note there is no point in pruning
|
|
// the preferred list after this since this resource can run
|
|
// anywhere.
|
|
//
|
|
OmEnumObjects( ObjectTypeNode,
|
|
FmpEnumAddAllOwners,
|
|
Resource,
|
|
NULL );
|
|
}
|
|
|
|
//
|
|
// Get the resource type.
|
|
//
|
|
status = DmQuerySz( resourceKey,
|
|
CLUSREG_NAME_RES_TYPE,
|
|
&resourceTypeString,
|
|
&resourceTypeStringMaxSize,
|
|
&resourceTypeStringSize );
|
|
|
|
if (status != ERROR_SUCCESS) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Unable to read ResourceType for resource %1!ws!\n",
|
|
OmObjectId(Resource));
|
|
goto error_exit;
|
|
}
|
|
|
|
//
|
|
// Open (and reference) the resource type.
|
|
//
|
|
if (Resource->Type == NULL)
|
|
{
|
|
Resource->Type = OmReferenceObjectById( ObjectTypeResType,
|
|
resourceTypeString );
|
|
}
|
|
if (Resource->Type == NULL) {
|
|
|
|
PFM_RESTYPE pResType;
|
|
//
|
|
// If we can't find a resource type, then try to create it.
|
|
//
|
|
pResType = FmpCreateResType(resourceTypeString );
|
|
|
|
if (pResType == NULL) {
|
|
status = ERROR_INVALID_PARAMETER;
|
|
LocalFree(resourceTypeString);
|
|
goto error_exit;
|
|
}
|
|
|
|
//bump the ref count before saving a pointer to it in the
|
|
//resource structure.
|
|
OmReferenceObject(pResType);
|
|
Resource->Type = pResType;
|
|
}
|
|
|
|
LocalFree(resourceTypeString);
|
|
|
|
if ( !Initialize ) {
|
|
//
|
|
// We're not supposed to fully initialize the resource. This is
|
|
// when we're early in the init process. We need to keep the registry
|
|
// key closed when leaving.
|
|
//
|
|
DmCloseKey(resourceKey);
|
|
return(ERROR_SUCCESS);
|
|
}
|
|
|
|
|
|
//
|
|
// Get the IsAlive poll interval
|
|
//
|
|
CL_ASSERT( Resource->Type->IsAlivePollInterval != 0 );
|
|
status = DmQueryDword( resourceKey,
|
|
CLUSREG_NAME_RES_IS_ALIVE,
|
|
&Resource->IsAlivePollInterval,
|
|
&Resource->Type->IsAlivePollInterval );
|
|
|
|
if ( status != NO_ERROR ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Unable to read IsAlivePollInterval for resource %1!ws!. Error %2!u!\n",
|
|
OmObjectId(Resource),
|
|
status);
|
|
|
|
goto error_exit;
|
|
}
|
|
|
|
if ( Resource->IsAlivePollInterval == CLUSTER_RESOURCE_USE_DEFAULT_POLL_INTERVAL ) {
|
|
Resource->IsAlivePollInterval = Resource->Type->IsAlivePollInterval;
|
|
}
|
|
|
|
//
|
|
// Get the LooksAlive poll interval
|
|
//
|
|
CL_ASSERT( Resource->Type->LooksAlivePollInterval != 0 );
|
|
status = DmQueryDword( resourceKey,
|
|
CLUSREG_NAME_RES_LOOKS_ALIVE,
|
|
&Resource->LooksAlivePollInterval,
|
|
&Resource->Type->LooksAlivePollInterval );
|
|
|
|
if ( status != NO_ERROR ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Unable to read LooksAlivePollInterval for resource %1!ws!. Error %2!u!\n",
|
|
OmObjectId(Resource),
|
|
status);
|
|
goto error_exit;
|
|
}
|
|
|
|
if ( Resource->LooksAlivePollInterval == CLUSTER_RESOURCE_USE_DEFAULT_POLL_INTERVAL ) {
|
|
Resource->LooksAlivePollInterval = Resource->Type->LooksAlivePollInterval;
|
|
}
|
|
|
|
//
|
|
// Get the current persistent state of the resource.
|
|
//
|
|
status = DmQueryDword( resourceKey,
|
|
CLUSREG_NAME_RES_PERSISTENT_STATE,
|
|
&temp,
|
|
NULL );
|
|
|
|
//
|
|
// Save the current resource state.
|
|
//
|
|
|
|
if ( ( status == ERROR_FILE_NOT_FOUND ) ||
|
|
( ( status == ERROR_SUCCESS ) && ( temp == CLUSTER_RESOURCE_DEFAULT_PERSISTENT_STATE ) ) ) {
|
|
switch ( Resource->Group->PersistentState ) {
|
|
case ClusterGroupOnline:
|
|
Resource->PersistentState = ClusterResourceOnline;
|
|
break;
|
|
case ClusterGroupOffline:
|
|
Resource->PersistentState = ClusterResourceOffline;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else if ( status != NO_ERROR ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Unable to read PersistentState for resource %1!ws!. Error %2!u!\n",
|
|
OmObjectId(Resource),
|
|
status);
|
|
goto error_exit;
|
|
} else if ( temp ) {
|
|
Resource->PersistentState = ClusterResourceOnline;
|
|
} else {
|
|
Resource->PersistentState = ClusterResourceOffline;
|
|
}
|
|
|
|
//
|
|
// Determine the monitor to run this in.
|
|
//
|
|
status = DmQueryDword( resourceKey,
|
|
CLUSREG_NAME_RES_SEPARATE_MONITOR,
|
|
&separateMonitor,
|
|
&zero );
|
|
if ( separateMonitor ) {
|
|
Resource->Flags |= RESOURCE_SEPARATE_MONITOR;
|
|
}
|
|
|
|
//
|
|
// Get the RestartThreshold.
|
|
//
|
|
|
|
status = DmQueryDword( resourceKey,
|
|
CLUSREG_NAME_RES_RESTART_THRESHOLD,
|
|
&Resource->RestartThreshold,
|
|
&restartThreshold );
|
|
|
|
// Verify the RestartThreshold. Take any value.
|
|
|
|
//
|
|
// Get the RestartPeriod.
|
|
//
|
|
|
|
status = DmQueryDword( resourceKey,
|
|
CLUSREG_NAME_RES_RESTART_PERIOD,
|
|
&Resource->RestartPeriod,
|
|
&restartPeriod );
|
|
|
|
// Verify the RestartPeriod. Take any value.
|
|
|
|
//
|
|
// Get the RestartAction.
|
|
//
|
|
|
|
status = DmQueryDword( resourceKey,
|
|
CLUSREG_NAME_RES_RESTART_ACTION,
|
|
&Resource->RestartAction,
|
|
&defaultRestartAction );
|
|
|
|
// Verify the RestartAction.
|
|
|
|
if ( Resource->RestartAction >= RestartMaximum ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Illegal RestartAction for resource %1!ws!\n",
|
|
OmObjectId(Resource));
|
|
goto error_exit;
|
|
}
|
|
|
|
status = DmQueryDword( resourceKey,
|
|
CLUSREG_NAME_RES_RETRY_PERIOD_ON_FAILURE,
|
|
&Resource->RetryPeriodOnFailure,
|
|
&RetryPeriodOnFailure );
|
|
|
|
// make sure that RetryPeriodOnFailure >= RestartPeriod
|
|
if (Resource->RetryPeriodOnFailure < Resource->RestartPeriod)
|
|
{
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Specified RetryPeriodOnFailure value is less than RestartPeriod value - setting RetryPeriodOnFailure equal to RestartPeriod \n");
|
|
Resource->RetryPeriodOnFailure = Resource->RestartPeriod;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Get the extrinsic Flags
|
|
//
|
|
DefaultExFlags = 0;
|
|
status = DmQueryDword( resourceKey,
|
|
CLUSREG_NAME_FLAGS,
|
|
&Resource->ExFlags,
|
|
&DefaultExFlags );
|
|
|
|
if ( status != NO_ERROR ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Unable to read Extrinsic Flags for resource %1!ws!. Error %2!u!\n",
|
|
OmObjectId(Resource),
|
|
status);
|
|
|
|
goto error_exit;
|
|
}
|
|
|
|
//
|
|
// Get the PendingTimeout value.
|
|
//
|
|
|
|
status = DmQueryDword( resourceKey,
|
|
CLUSREG_NAME_RES_PENDING_TIMEOUT,
|
|
&Resource->PendingTimeout,
|
|
&pendingTimeout );
|
|
|
|
// Verify the PendingTimeout. Take any value.
|
|
|
|
//
|
|
// Now register for any changes to the resource key.
|
|
//
|
|
|
|
if (IsListEmpty(&Resource->DmRundownList))
|
|
{
|
|
status = DmNotifyChangeKey(
|
|
resourceKey,
|
|
(DWORD) CLUSTER_CHANGE_ALL,
|
|
FALSE, // Only watch the top of the tree
|
|
&Resource->DmRundownList,
|
|
FmpResourceChangeCallback,
|
|
(DWORD_PTR)Resource,
|
|
0 );
|
|
|
|
if ( status != ERROR_SUCCESS) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Error registering for DM change notify on resource %1!ws!, error %2!u!.\n",
|
|
OmObjectId(Resource),
|
|
status);
|
|
goto error_exit;
|
|
}
|
|
}
|
|
//
|
|
// Get the DebugPrefix string... this is on the resource type.
|
|
//
|
|
status = DmQuerySz( resourceKey,
|
|
CLUSREG_NAME_RES_DEBUG_PREFIX,
|
|
&Resource->DebugPrefix,
|
|
&nameSize,
|
|
&stringSize );
|
|
|
|
//
|
|
// Finally save the resource key for registry updates of the
|
|
// PersistentState.
|
|
//
|
|
Resource->RegistryKey = resourceKey;
|
|
|
|
return(ERROR_SUCCESS);
|
|
|
|
|
|
error_exit:
|
|
|
|
DmCloseKey(resourceKey);
|
|
|
|
if ( Resource->Type != NULL ) {
|
|
OmDereferenceObject(Resource->Type);
|
|
}
|
|
|
|
//
|
|
// Cleanup any dependencies
|
|
//
|
|
if ( Resource->Dependencies != NULL ) {
|
|
LocalFree(Resource->Dependencies);
|
|
Resource->Dependencies = NULL;
|
|
}
|
|
|
|
//
|
|
// Cleanup any possible nodes
|
|
//
|
|
while ( !IsListEmpty(&Resource->PossibleOwners) ) {
|
|
listEntry = RemoveHeadList(&Resource->PossibleOwners);
|
|
possibleEntry = CONTAINING_RECORD(listEntry, POSSIBLE_ENTRY, PossibleLinkage);
|
|
OmDereferenceObject(possibleEntry->PossibleNode);
|
|
LocalFree(possibleEntry);
|
|
}
|
|
|
|
return(status);
|
|
|
|
} // FmpQueryResourceInfo
|
|
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
FmpFixupResourceInfo(
|
|
IN PFM_RESOURCE Resource
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Re-queries Resource info from the registry to fixup items that may have
|
|
changed since the quorum resource was first created.
|
|
|
|
This routine exists because we may have created the quorum resource early
|
|
in the 'life' of the cluster, before all the node objects (for example)
|
|
were created. We then would have failed generating the list of possible
|
|
owners for the resource. In FmpQueryResourceInfo, we treat failures to
|
|
find node objects as non-fatal errors, which we will now cleanup.
|
|
|
|
Arguments:
|
|
|
|
Resource - A pointer to the Resource object to fix up.
|
|
|
|
Return Value:
|
|
|
|
ERROR_SUCCESS if successful.
|
|
|
|
Win32 error code otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
LPWSTR possibleOwnersString = NULL;
|
|
DWORD possibleOwnersStringSize = 0;
|
|
DWORD possibleOwnersStringMaxSize = 0;
|
|
DWORD mszStringIndex;
|
|
DWORD status;
|
|
|
|
|
|
if ( Resource->RegistryKey == NULL ) {
|
|
return(ERROR_NOT_READY);
|
|
}
|
|
|
|
//
|
|
// Get the OPTIONAL PossibleOwners list.
|
|
//
|
|
|
|
status = DmQueryMultiSz( Resource->RegistryKey,
|
|
CLUSREG_NAME_RES_POSSIBLE_OWNERS,
|
|
&possibleOwnersString,
|
|
&possibleOwnersStringMaxSize,
|
|
&possibleOwnersStringSize );
|
|
|
|
if ( status == NO_ERROR ) {
|
|
|
|
//
|
|
// Now Create the Possible Owners list.
|
|
//
|
|
|
|
for ( mszStringIndex = 0; ; mszStringIndex++ ) {
|
|
LPCWSTR nameString;
|
|
PNM_NODE possibleNode;
|
|
|
|
nameString = ClRtlMultiSzEnum( possibleOwnersString,
|
|
possibleOwnersStringSize/sizeof(WCHAR),
|
|
mszStringIndex );
|
|
|
|
if ( nameString == NULL ) {
|
|
break;
|
|
}
|
|
possibleNode = OmReferenceObjectById( ObjectTypeNode,
|
|
nameString );
|
|
|
|
if ( possibleNode == NULL ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Warning, failed to find node %1!ws! for Resource %2!ws!\n",
|
|
nameString,
|
|
OmObjectId(Resource));
|
|
} else {
|
|
Resource->PossibleList = TRUE;
|
|
status = FmpAddPossibleEntry(Resource, possibleNode);
|
|
OmDereferenceObject(possibleNode);
|
|
if (status != ERROR_SUCCESS) {
|
|
return(status);
|
|
}
|
|
}
|
|
}
|
|
LocalFree(possibleOwnersString);
|
|
|
|
//
|
|
// Now prune out unusable nodes from the preferred owners list.
|
|
//
|
|
FmpPrunePreferredList( Resource );
|
|
|
|
} else {
|
|
//
|
|
// No possible owners value was specified. Add all the nodes
|
|
// to the possible owners list. Note there is no point in pruning
|
|
// the preferred list after this since this resource can run
|
|
// anywhere.
|
|
//
|
|
OmEnumObjects( ObjectTypeNode,
|
|
FmpEnumAddAllOwners,
|
|
Resource,
|
|
NULL );
|
|
|
|
}
|
|
|
|
return(ERROR_SUCCESS);
|
|
|
|
} // FmpFixupQuorumResourceInfo
|
|
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
FmpQueryResTypeInfo(
|
|
IN PVOID Object
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Queries Resource Type info from the registry when creating a ResType Object.
|
|
|
|
Arguments:
|
|
|
|
Object - A pointer to the Resource Type object being created.
|
|
|
|
Return Value:
|
|
|
|
ERROR_SUCCESS if successful.
|
|
|
|
Win32 error code otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFM_RESTYPE resType = (PFM_RESTYPE)Object;
|
|
DWORD status;
|
|
DWORD dwSize = 0;
|
|
DWORD stringSize;
|
|
HDMKEY resTypeKey;
|
|
DWORD temp;
|
|
LPWSTR pmszPossibleNodes = NULL;
|
|
|
|
//
|
|
// Open the resource type key.
|
|
//
|
|
resTypeKey = DmOpenKey( DmResourceTypesKey,
|
|
OmObjectId(resType),
|
|
MAXIMUM_ALLOWED );
|
|
|
|
if ( resTypeKey == NULL ) {
|
|
status = GetLastError();
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Unable to open resource type key %1!ws!, %2!u!\n",
|
|
OmObjectId(resType),
|
|
status);
|
|
|
|
return(status);
|
|
}
|
|
|
|
//
|
|
// Read the required resource type DLL name. The strings will be allocated
|
|
// by the DmQuery* functions.
|
|
//
|
|
|
|
status = DmQuerySz( resTypeKey,
|
|
CLUSREG_NAME_RESTYPE_DLL_NAME,
|
|
&resType->DllName,
|
|
&dwSize,
|
|
&stringSize );
|
|
if ( status != NO_ERROR ) {
|
|
if ( status == ERROR_FILE_NOT_FOUND ) {
|
|
ClRtlLogPrint(LOG_CRITICAL,
|
|
"[FM] The DllName value for the %1!ws! resource type does not exist. "
|
|
"Resources of this type will not be monitored.\n",
|
|
OmObjectId(resType));
|
|
}
|
|
else {
|
|
ClRtlLogPrint(LOG_CRITICAL,
|
|
"[FM] The DllName value for the %1!ws! resource type could not be read "
|
|
"from the registry. Resources of this type will not be monitored. "
|
|
"The error was %2!d!.\n",
|
|
OmObjectId(resType),
|
|
status);
|
|
}
|
|
|
|
goto error_exit;
|
|
}
|
|
|
|
|
|
//
|
|
// Get the optional LooksAlive poll interval
|
|
//
|
|
status = DmQueryDword( resTypeKey,
|
|
CLUSREG_NAME_RESTYPE_LOOKS_ALIVE,
|
|
&resType->LooksAlivePollInterval,
|
|
NULL );
|
|
|
|
if ( status != NO_ERROR ) {
|
|
if ( status == ERROR_FILE_NOT_FOUND ) {
|
|
resType->LooksAlivePollInterval = CLUSTER_RESTYPE_DEFAULT_LOOKS_ALIVE;
|
|
} else {
|
|
ClRtlLogPrint(LOG_CRITICAL,
|
|
"[FM] The LooksAlive poll interval for the %1!ws! resource type could "
|
|
"not be read from the registry. Resources of this type will not be "
|
|
"monitored. The error was %2!d!.\n",
|
|
OmObjectId(resType),
|
|
status);
|
|
goto error_exit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get the optional IsAlive poll interval
|
|
//
|
|
status = DmQueryDword( resTypeKey,
|
|
CLUSREG_NAME_RESTYPE_IS_ALIVE,
|
|
&resType->IsAlivePollInterval,
|
|
NULL );
|
|
|
|
if ( status != NO_ERROR ) {
|
|
if ( status == ERROR_FILE_NOT_FOUND ) {
|
|
resType->IsAlivePollInterval = CLUSTER_RESTYPE_DEFAULT_IS_ALIVE;
|
|
} else {
|
|
ClRtlLogPrint(LOG_CRITICAL,
|
|
"[FM] The IsAlive poll interval for the %1!ws! resource type "
|
|
"could not be read from the registry. Resources of this type "
|
|
"will not be monitored. The error was %2!d!.\n",
|
|
OmObjectId(resType),
|
|
status);
|
|
goto error_exit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get the optional DebugPrefix string... this is on the resource type.
|
|
//
|
|
dwSize = 0;
|
|
status = DmQuerySz( resTypeKey,
|
|
CLUSREG_NAME_RESTYPE_DEBUG_PREFIX,
|
|
&resType->DebugPrefix,
|
|
&dwSize,
|
|
&stringSize );
|
|
|
|
//
|
|
// Get the optional DebugControlFunctions registry value
|
|
//
|
|
resType->Flags &= ~RESTYPE_DEBUG_CONTROL_FUNC;
|
|
temp = 0;
|
|
status = DmQueryDword( resTypeKey,
|
|
CLUSREG_NAME_RESTYPE_DEBUG_CTRLFUNC,
|
|
&temp,
|
|
NULL );
|
|
|
|
if ( status != NO_ERROR ) {
|
|
if ( status != ERROR_FILE_NOT_FOUND ) {
|
|
ClRtlLogPrint(LOG_CRITICAL,
|
|
"[FM] The Debug control functions for the %1!ws! resource type "
|
|
"could not be read from the registry. Resources of this type "
|
|
"will not be monitored. The error was %2!d!.\n",
|
|
OmObjectId(resType),
|
|
status);
|
|
goto error_exit;
|
|
}
|
|
}
|
|
|
|
if ( temp ) {
|
|
resType->Flags |= RESTYPE_DEBUG_CONTROL_FUNC;
|
|
}
|
|
|
|
|
|
//ss: bug make sure you free the old memory
|
|
InitializeListHead(&(resType->PossibleNodeList));
|
|
|
|
//
|
|
// Get the Possible Nodes
|
|
//
|
|
dwSize = 0;
|
|
status = DmQueryMultiSz( resTypeKey,
|
|
CLUSREG_NAME_RESTYPE_POSSIBLE_NODES,
|
|
&pmszPossibleNodes,
|
|
&dwSize,
|
|
&stringSize);
|
|
|
|
|
|
if ( status != NO_ERROR )
|
|
{
|
|
//if the possible node list is not found this is ok
|
|
if ( status != ERROR_FILE_NOT_FOUND )
|
|
{
|
|
ClRtlLogPrint(LOG_CRITICAL,
|
|
"[FM] The Possible nodes list for the %1!ws! resource type "
|
|
"could not be read from the registry. Resources of this type "
|
|
"will not be monitored. The error was %2!d!.\n",
|
|
OmObjectId(resType),
|
|
status);
|
|
goto error_exit;
|
|
}
|
|
}
|
|
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] FmpQueryResTypeInfo: Calling FmpAddPossibleNodeToList for restype %1!ws!\r\n",
|
|
OmObjectId(resType));
|
|
|
|
status = FmpAddPossibleNodeToList(pmszPossibleNodes, stringSize,
|
|
&resType->PossibleNodeList);
|
|
if ( status != ERROR_SUCCESS )
|
|
{
|
|
ClRtlLogPrint(LOG_CRITICAL,
|
|
"[FM] FmpCreateResType: FmpAddPossibleNodeToList() failed, status=%1!u!\r\n",
|
|
status);
|
|
goto error_exit;
|
|
}
|
|
|
|
|
|
error_exit:
|
|
if (pmszPossibleNodes) LocalFree(pmszPossibleNodes);
|
|
DmCloseKey(resTypeKey);
|
|
|
|
return(status);
|
|
|
|
} // FmpQueryResTypeInfo
|
|
|
|
|
|
|
|
VOID
|
|
FmpGroupChangeCallback(
|
|
IN DWORD_PTR Context1,
|
|
IN DWORD_PTR Context2,
|
|
IN DWORD CompletionFilter,
|
|
IN LPCWSTR RelativeName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine basically flushes our cached data for the given group.
|
|
|
|
Arguments:
|
|
|
|
Context1 - A pointer to the Group object that was modified.
|
|
|
|
Context2 - Not used.
|
|
|
|
CompletionFilter - Not used.
|
|
|
|
RelativeName - The registry path relative to the entry that was modified.
|
|
Not used.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFM_GROUP Group = (PFM_GROUP)Context1;
|
|
HDMKEY groupKey;
|
|
DWORD status;
|
|
DWORD temp;
|
|
BOOL notify = FALSE;
|
|
DWORD dwBufferSize = 0;
|
|
DWORD dwStringSize;
|
|
|
|
groupKey = Group->RegistryKey;
|
|
if ( groupKey == NULL ) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Re-fetch all of the data for the group.
|
|
//
|
|
// Name changes are managed elsewhere.
|
|
// The Contains list is managed elsewhere.
|
|
//
|
|
|
|
//
|
|
// Get the OPTIONAL PreferredOwners list.
|
|
// *** NOTE *** This MUST be done before processing the contains list!
|
|
//
|
|
status = FmpQueryGroupNodes(Group, groupKey);
|
|
if (status != ERROR_SUCCESS) {
|
|
ClRtlLogPrint(LOG_UNUSUAL,"[FM] Error %1!d! refreshing preferred owners list\n",status);
|
|
}
|
|
|
|
//
|
|
// Get the AutoFailbackType.
|
|
//
|
|
temp = Group->FailbackType;
|
|
status = DmQueryDword( groupKey,
|
|
CLUSREG_NAME_GRP_FAILBACK_TYPE,
|
|
&temp,
|
|
&temp );
|
|
|
|
//
|
|
// Verify that AutoFailbackType is okay.
|
|
//
|
|
|
|
if ( temp >= FailbackMaximum ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Illegal refresh value for AutoFailbackType on %1!ws!\n",
|
|
OmObjectId(Group));
|
|
} else {
|
|
if ( (UCHAR)temp != Group->FailbackType ) {
|
|
notify = TRUE;
|
|
}
|
|
Group->FailbackType = (UCHAR)temp;
|
|
}
|
|
|
|
//
|
|
// Get the FailbackWindowStart.
|
|
//
|
|
temp = Group->FailbackWindowStart;
|
|
status = DmQueryDword( groupKey,
|
|
CLUSREG_NAME_GRP_FAILBACK_WIN_START,
|
|
&temp,
|
|
&temp );
|
|
|
|
//
|
|
// Verify that FailbackWindowStart is okay.
|
|
//
|
|
|
|
if ( temp > 24 ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Illegal refresh value for FailbackWindowStart on %1!ws!\n",
|
|
OmObjectId(Group));
|
|
} else {
|
|
if ( (UCHAR)temp != Group->FailbackWindowStart ) {
|
|
notify = TRUE;
|
|
}
|
|
Group->FailbackWindowStart = (UCHAR)temp;
|
|
}
|
|
|
|
//
|
|
// Get the FailbackWindowEnd.
|
|
//
|
|
temp = Group->FailbackWindowEnd;
|
|
status = DmQueryDword( groupKey,
|
|
CLUSREG_NAME_GRP_FAILBACK_WIN_END,
|
|
&temp,
|
|
&temp );
|
|
|
|
//
|
|
// Verify that FailbackWindowEnd is okay.
|
|
//
|
|
|
|
if ( temp > 24 ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Illegal refresh value for FailbackWindowEnd on %1!ws!\n",
|
|
OmObjectId(Group));
|
|
} else {
|
|
if ( (UCHAR)temp != Group->FailbackWindowEnd ) {
|
|
notify = TRUE;
|
|
}
|
|
Group->FailbackWindowEnd = (UCHAR)temp;
|
|
}
|
|
|
|
//
|
|
// Get the FailoverPeriod.
|
|
//
|
|
temp = Group->FailoverPeriod;
|
|
status = DmQueryDword( groupKey,
|
|
CLUSREG_NAME_GRP_FAILOVER_PERIOD,
|
|
&temp,
|
|
&temp );
|
|
|
|
//
|
|
// Verify that FailoverPeriod is okay. Take any value up to UCHAR max.
|
|
// In theory we could take any value... but in practice we have to convert
|
|
// this time to milliseconds (currently). That means that 1193 hours can
|
|
// fit in a DWORD - so that is the maximum we can take. (We are limited
|
|
// because we use GetTickCount, which returns a DWORD in milliseconds.)
|
|
//
|
|
|
|
if ( temp > (1193) ) { // we dont bother Keeping it positive?
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Illegal refresh value for FailolverPeriod on %1!ws!. Max is 596\n",
|
|
OmObjectId(Group));
|
|
} else {
|
|
if ( (UCHAR)temp != Group->FailoverPeriod ) {
|
|
notify = TRUE;
|
|
}
|
|
Group->FailoverPeriod = (UCHAR)temp;
|
|
}
|
|
|
|
//
|
|
// Get the FailoverThreshold.
|
|
//
|
|
status = DmQueryDword( groupKey,
|
|
CLUSREG_NAME_GRP_FAILOVER_THRESHOLD,
|
|
&(Group->FailoverThreshold),
|
|
&(Group->FailoverThreshold) );
|
|
|
|
//
|
|
// Verify that FailoverThreshold is okay. Take any value.
|
|
//
|
|
|
|
//
|
|
// Get the current persistent state of the group.
|
|
//
|
|
if ( Group->PersistentState == ClusterGroupOnline ) {
|
|
temp = 1;
|
|
} else {
|
|
temp = 0;
|
|
}
|
|
status = DmQueryDword( groupKey,
|
|
CLUSREG_NAME_GRP_PERSISTENT_STATE,
|
|
&temp,
|
|
&temp );
|
|
//
|
|
// If the group state is non-zero then we go online.
|
|
//
|
|
// Don't bother with change notifications... they should happen elsewhere.
|
|
//
|
|
if ( temp ) {
|
|
if ( ClusterGroupOnline != Group->PersistentState ) {
|
|
//notify = TRUE;
|
|
}
|
|
Group->PersistentState = ClusterGroupOnline;
|
|
} else {
|
|
if ( ClusterGroupOffline != Group->PersistentState ) {
|
|
//notify = TRUE;
|
|
}
|
|
Group->PersistentState = ClusterGroupOffline;
|
|
}
|
|
|
|
//
|
|
// Get the AntiAffinityClassName property if present.
|
|
//
|
|
LocalFree( Group->lpszAntiAffinityClassName );
|
|
Group->lpszAntiAffinityClassName = NULL;
|
|
status = DmQueryMultiSz( groupKey,
|
|
CLUSREG_NAME_GRP_ANTI_AFFINITY_CLASS_NAME,
|
|
&Group->lpszAntiAffinityClassName,
|
|
&dwBufferSize,
|
|
&dwStringSize );
|
|
|
|
//
|
|
// Handle the case in which the string is empty.
|
|
//
|
|
if ( ( status == ERROR_SUCCESS ) &&
|
|
( Group->lpszAntiAffinityClassName != NULL ) &&
|
|
( Group->lpszAntiAffinityClassName[0] == L'\0' ) )
|
|
{
|
|
LocalFree( Group->lpszAntiAffinityClassName );
|
|
Group->lpszAntiAffinityClassName = NULL;
|
|
}
|
|
|
|
// We're done!
|
|
if ( !FmpShutdown &&
|
|
notify ) {
|
|
ClusterEvent( CLUSTER_EVENT_GROUP_PROPERTY_CHANGE, Group );
|
|
}
|
|
|
|
return;
|
|
|
|
} // FmpGroupChangeCallback
|
|
|
|
|
|
|
|
VOID
|
|
FmpResourceChangeCallback(
|
|
IN DWORD_PTR Context1,
|
|
IN DWORD_PTR Context2,
|
|
IN DWORD CompletionFilter,
|
|
IN LPCWSTR RelativeName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine basically flushes our cached data for the given resource.
|
|
|
|
Arguments:
|
|
|
|
Context1 - A pointer to the resource object that was modified.
|
|
|
|
Context2 - Not used.
|
|
|
|
CompletionFilter - Not used.
|
|
|
|
RelativeName - The registry path relative to the entry that was modified.
|
|
Not used.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFM_RESOURCE Resource = (PFM_RESOURCE)Context1;
|
|
HDMKEY resourceKey;
|
|
DWORD status;
|
|
DWORD separateMonitor;
|
|
DWORD zero = 0;
|
|
DWORD temp;
|
|
BOOL notify = FALSE;
|
|
DWORD dwDefault;
|
|
|
|
resourceKey = Resource->RegistryKey;
|
|
if ( resourceKey == NULL ) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Re-fetch all of the data for the resource.
|
|
//
|
|
// Name changes are managed elsewhere.
|
|
// The dependency list is managed elsewhere.
|
|
//
|
|
// We can't change the resource type here!
|
|
// We can't stop the resource to start it in a separate monitor either.
|
|
//
|
|
|
|
//
|
|
// Get the IsAlive poll interval
|
|
//
|
|
temp = Resource->IsAlivePollInterval;
|
|
dwDefault = CLUSTER_RESOURCE_DEFAULT_IS_ALIVE;
|
|
status = DmQueryDword( resourceKey,
|
|
CLUSREG_NAME_RES_IS_ALIVE,
|
|
&Resource->IsAlivePollInterval,
|
|
&dwDefault );
|
|
|
|
if ( status != NO_ERROR ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Unable to refresh IsAlivePollInterval for resource %1!ws!. Error %2!u!\n",
|
|
OmObjectId(Resource),
|
|
status);
|
|
|
|
} else {
|
|
CL_ASSERT( Resource->Type->IsAlivePollInterval != 0 );
|
|
if ( temp != Resource->IsAlivePollInterval ) {
|
|
notify = TRUE;
|
|
}
|
|
if ( Resource->IsAlivePollInterval == CLUSTER_RESOURCE_USE_DEFAULT_POLL_INTERVAL ) {
|
|
Resource->IsAlivePollInterval = Resource->Type->IsAlivePollInterval;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get the LooksAlive poll interval
|
|
//
|
|
temp = Resource->LooksAlivePollInterval;
|
|
dwDefault = CLUSTER_RESOURCE_DEFAULT_LOOKS_ALIVE;
|
|
status = DmQueryDword( resourceKey,
|
|
CLUSREG_NAME_RES_LOOKS_ALIVE,
|
|
&Resource->LooksAlivePollInterval,
|
|
&dwDefault );
|
|
|
|
if ( status != NO_ERROR ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Unable to refresh LooksAlivePollInterval for resource %1!ws!. Error %2!u!\n",
|
|
OmObjectId(Resource),
|
|
status);
|
|
} else {
|
|
CL_ASSERT( Resource->Type->IsAlivePollInterval != 0 );
|
|
if ( temp != Resource->LooksAlivePollInterval ) {
|
|
notify = TRUE;
|
|
}
|
|
if ( Resource->LooksAlivePollInterval == CLUSTER_RESOURCE_USE_DEFAULT_POLL_INTERVAL ) {
|
|
Resource->LooksAlivePollInterval = Resource->Type->LooksAlivePollInterval;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get the RestartThreshold.
|
|
//
|
|
temp = Resource->RestartThreshold;
|
|
dwDefault = CLUSTER_RESOURCE_DEFAULT_RESTART_THRESHOLD;
|
|
status = DmQueryDword( resourceKey,
|
|
CLUSREG_NAME_RES_RESTART_THRESHOLD,
|
|
&Resource->RestartThreshold,
|
|
&dwDefault);
|
|
|
|
// Verify the RestartThreshold. Take any value.
|
|
if ( (status == NO_ERROR) &&
|
|
(temp != Resource->RestartThreshold) ) {
|
|
notify = TRUE;
|
|
}
|
|
|
|
//
|
|
// Get the RestartPeriod.
|
|
//
|
|
temp = Resource->RestartPeriod;
|
|
dwDefault = CLUSTER_RESOURCE_DEFAULT_RESTART_PERIOD;
|
|
status = DmQueryDword( resourceKey,
|
|
CLUSREG_NAME_RES_RESTART_PERIOD,
|
|
&Resource->RestartPeriod,
|
|
&dwDefault );
|
|
|
|
if ( (status == NO_ERROR) &&
|
|
(temp != Resource->RestartPeriod) ) {
|
|
notify = TRUE;
|
|
}
|
|
|
|
// Verify the RestartPeriod. Take any value.
|
|
|
|
//
|
|
// Get the RestartAction.
|
|
//
|
|
temp = Resource->RestartAction;
|
|
dwDefault = CLUSTER_RESOURCE_DEFAULT_RESTART_ACTION;
|
|
status = DmQueryDword( resourceKey,
|
|
CLUSREG_NAME_RES_RESTART_ACTION,
|
|
&Resource->RestartAction,
|
|
&dwDefault);
|
|
|
|
// Verify the RestartAction.
|
|
|
|
if ( status == NO_ERROR ) {
|
|
if ( temp != Resource->RestartAction ) {
|
|
notify = TRUE;
|
|
}
|
|
if ( Resource->RestartAction >= RestartMaximum ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Illegal RestartAction refresh for resource %1!ws!\n",
|
|
OmObjectId(Resource));
|
|
}
|
|
}
|
|
|
|
temp = Resource->RetryPeriodOnFailure;
|
|
dwDefault = CLUSTER_RESOURCE_DEFAULT_RETRY_PERIOD_ON_FAILURE;
|
|
status = DmQueryDword( resourceKey,
|
|
CLUSREG_NAME_RES_RETRY_PERIOD_ON_FAILURE,
|
|
&Resource->RetryPeriodOnFailure,
|
|
&dwDefault );
|
|
|
|
// make sure that RetryPeriodOnFailure >= RestartPeriod
|
|
if (Resource->RetryPeriodOnFailure < Resource->RestartPeriod)
|
|
{
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Specified RetryPeriodOnFailure value is less than RestartPeriod value - setting RetryPeriodOnFailure equal to RestartPeriod \n");
|
|
Resource->RetryPeriodOnFailure = Resource->RestartPeriod;
|
|
|
|
}
|
|
if( temp != Resource->RetryPeriodOnFailure)
|
|
notify = TRUE;
|
|
|
|
//
|
|
// Get the PendingTimeout value.
|
|
//
|
|
temp = Resource->PendingTimeout;
|
|
dwDefault = CLUSTER_RESOURCE_DEFAULT_PENDING_TIMEOUT;
|
|
status = DmQueryDword( resourceKey,
|
|
CLUSREG_NAME_RES_PENDING_TIMEOUT,
|
|
&Resource->PendingTimeout,
|
|
&dwDefault);
|
|
|
|
// Verify the PendingTimeout. Take any value.
|
|
|
|
if ( (status == NO_ERROR) &&
|
|
(temp != Resource->PendingTimeout) ) {
|
|
notify = TRUE;
|
|
}
|
|
|
|
|
|
//
|
|
// Get the current persistent state of the resource.
|
|
//
|
|
// Don't bother with change notifications... they should happen elsewhere.
|
|
//
|
|
status = DmQueryDword( resourceKey,
|
|
CLUSREG_NAME_RES_PERSISTENT_STATE,
|
|
&temp,
|
|
NULL );
|
|
|
|
//
|
|
// Save the current resource state.
|
|
//
|
|
|
|
if ( ( status == ERROR_FILE_NOT_FOUND ) ||
|
|
( ( status == ERROR_SUCCESS ) && ( temp == CLUSTER_RESOURCE_DEFAULT_PERSISTENT_STATE ) ) ) {
|
|
switch ( Resource->Group->PersistentState ) {
|
|
case ClusterGroupOnline:
|
|
Resource->PersistentState = ClusterResourceOnline;
|
|
break;
|
|
case ClusterGroupOffline:
|
|
Resource->PersistentState = ClusterResourceOffline;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else if ( status != NO_ERROR ) {
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Unable to read PersistentState for resource %1!ws!. Error %2!u!\n",
|
|
OmObjectId(Resource),
|
|
status);
|
|
return;
|
|
} else if ( temp ) {
|
|
Resource->PersistentState = ClusterResourceOnline;
|
|
} else {
|
|
Resource->PersistentState = ClusterResourceOffline;
|
|
}
|
|
|
|
if ( !FmpShutdown &&
|
|
notify ) {
|
|
//
|
|
// Comments from sunitas: Tell the resource monitor about the
|
|
// changes but do this from the worker thread. Originally, this
|
|
// used to be a post notification to the FmpRmWorkerThread
|
|
// which posts resmon notifications to clussvc.
|
|
//
|
|
OmReferenceObject(Resource);
|
|
FmpPostWorkItem(FM_EVENT_INTERNAL_RESOURCE_CHANGE_PARAMS,
|
|
Resource,
|
|
0);
|
|
}
|
|
|
|
return;
|
|
|
|
} // FmpResourceChangeCallback
|
|
|
|
|
|
|
|
DWORD
|
|
FmpChangeResourceMonitor(
|
|
IN PFM_RESOURCE Resource,
|
|
IN DWORD SeparateMonitor
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine switches the resource from one resource monitor to another.
|
|
|
|
Arguments:
|
|
|
|
Resource - pointer to the resource that was modified.
|
|
|
|
SeparateMonitor - flag to indicate whether to run in a separate monitor;
|
|
|
|
Return Value:
|
|
|
|
ERROR_SUCCESS if successful.
|
|
A Win32 error code on failure.
|
|
|
|
Notes:
|
|
|
|
The resource should be offline.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD status = ERROR_SUCCESS;
|
|
DWORD separateMonitor;
|
|
DWORD zero = 0;
|
|
|
|
if ( Resource->RegistryKey == NULL ) {
|
|
return(ERROR_INVALID_STATE);
|
|
}
|
|
|
|
if ( (Resource->State != ClusterResourceOffline) &&
|
|
(Resource->State != ClusterResourceFailed) ) {
|
|
return(ERROR_INVALID_STATE);
|
|
}
|
|
|
|
//
|
|
// Determine the monitor to run this in. This is only updated from
|
|
// the node that owns the resource.
|
|
//
|
|
if ( (!SeparateMonitor &&
|
|
(Resource->Flags & RESOURCE_SEPARATE_MONITOR)) ||
|
|
(SeparateMonitor &&
|
|
((Resource->Flags & RESOURCE_SEPARATE_MONITOR) == 0)) ) {
|
|
|
|
//
|
|
// The separate monitor flag has changed... tell ResMon to close
|
|
// the resource and then create it again.
|
|
//
|
|
ClRtlLogPrint(LOG_NOISE,
|
|
"[FM] Changing Separate Resource Monitor state\n");
|
|
|
|
status = FmpRmCloseResource( Resource );
|
|
if ( status == ERROR_SUCCESS ) {
|
|
if ( Resource->Flags & RESOURCE_SEPARATE_MONITOR ) {
|
|
Resource->Flags &= ~RESOURCE_SEPARATE_MONITOR;
|
|
} else {
|
|
Resource->Flags |= RESOURCE_SEPARATE_MONITOR;
|
|
}
|
|
status = FmpRmCreateResource( Resource );
|
|
if ( status != ERROR_SUCCESS ) {
|
|
ClRtlLogPrint(LOG_UNUSUAL,
|
|
"[FM] Separate resource monitor changed for '%1!ws!', but failed to re-open the resource, error %2!u!.\n",
|
|
OmObjectId(Resource),
|
|
status );
|
|
}
|
|
} else {
|
|
ClRtlLogPrint(LOG_UNUSUAL,
|
|
"[FM] Separate resource monitor changed for '%1!ws!', but failed to close the resource, error %2!u!.\n",
|
|
OmObjectId(Resource),
|
|
status );
|
|
return(status);
|
|
}
|
|
}
|
|
|
|
return(status);
|
|
|
|
} // FmpChangeResourceMonitor
|
|
|
|
|