1705 lines
48 KiB
C
1705 lines
48 KiB
C
|
/*++
|
|||
|
|
|||
|
Copyright (c) 1996 Microsoft Corporation
|
|||
|
|
|||
|
Module Name:
|
|||
|
|
|||
|
grouparb.c
|
|||
|
|
|||
|
Abstract:
|
|||
|
|
|||
|
Cluster group arbitration and sorting routines.
|
|||
|
|
|||
|
Author:
|
|||
|
|
|||
|
Rod Gamache (rodga) 8-Mar-1996
|
|||
|
|
|||
|
|
|||
|
Revision History:
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
#include "fmp.h"
|
|||
|
|
|||
|
#define LOG_MODULE GROUPARB
|
|||
|
|
|||
|
//
|
|||
|
// Global data
|
|||
|
//
|
|||
|
|
|||
|
//
|
|||
|
// Local function prototypes
|
|||
|
//
|
|||
|
|
|||
|
typedef struct FM_GROUP_ENUM_DATA {
|
|||
|
DWORD Allocated;
|
|||
|
PNM_NODE OwnerNode;
|
|||
|
BOOL QuorumGroup;
|
|||
|
} FM_GROUP_ENUM_DATA, *PFM_GROUP_ENUM_DATA;
|
|||
|
|
|||
|
|
|||
|
BOOL
|
|||
|
FmpEnumGroups(
|
|||
|
IN OUT PGROUP_ENUM *Enum,
|
|||
|
IN PFM_GROUP_ENUM_DATA EnumData,
|
|||
|
IN PFM_GROUP Group,
|
|||
|
IN LPCWSTR Name
|
|||
|
);
|
|||
|
|
|||
|
BOOL
|
|||
|
FmpEqualGroupLists(
|
|||
|
IN PGROUP_ENUM Group1,
|
|||
|
IN PGROUP_ENUM Group2
|
|||
|
);
|
|||
|
|
|||
|
int
|
|||
|
_cdecl
|
|||
|
SortCompare(
|
|||
|
IN const void * Elem1,
|
|||
|
IN const void * Elem2
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
DWORD
|
|||
|
FmpEnumSortGroups(
|
|||
|
OUT PGROUP_ENUM *ReturnEnum,
|
|||
|
IN OPTIONAL PNM_NODE OwnerNode,
|
|||
|
OUT PBOOL QuorumGroup
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Enumerates and sorts the list of Groups.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ReturnEnum - Returns the requested objects.
|
|||
|
|
|||
|
OwnerNode - If present, supplies the owner node to filter
|
|||
|
the list of groups. (i.e. if you supply this, you
|
|||
|
get a list of groups owned by that node)
|
|||
|
|
|||
|
If not present, all groups are returned.
|
|||
|
|
|||
|
QuorumGroup - Returns TRUE if the quorum resource in one of the groups
|
|||
|
returned in the ENUM.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
ERROR_SUCCESS if successful.
|
|||
|
|
|||
|
Win32 error code on error.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
DWORD status;
|
|||
|
PGROUP_ENUM groupEnum = NULL;
|
|||
|
FM_GROUP_ENUM_DATA EnumData;
|
|||
|
|
|||
|
EnumData.Allocated = ENUM_GROW_SIZE;
|
|||
|
EnumData.OwnerNode = OwnerNode;
|
|||
|
EnumData.QuorumGroup = FALSE;
|
|||
|
|
|||
|
groupEnum = LocalAlloc(LMEM_FIXED, GROUP_SIZE(ENUM_GROW_SIZE));
|
|||
|
if ( groupEnum == NULL ) {
|
|||
|
status = ERROR_NOT_ENOUGH_MEMORY;
|
|||
|
goto error_exit;
|
|||
|
}
|
|||
|
|
|||
|
groupEnum->EntryCount = 0;
|
|||
|
|
|||
|
//
|
|||
|
// Enumerate all groups, sort with Quorum Group first in the list.
|
|||
|
//
|
|||
|
|
|||
|
OmEnumObjects(ObjectTypeGroup,
|
|||
|
FmpEnumGroups,
|
|||
|
&groupEnum,
|
|||
|
&EnumData);
|
|||
|
|
|||
|
|
|||
|
*ReturnEnum = groupEnum;
|
|||
|
*QuorumGroup = EnumData.QuorumGroup;
|
|||
|
return(ERROR_SUCCESS);
|
|||
|
|
|||
|
error_exit:
|
|||
|
|
|||
|
if ( groupEnum != NULL ) {
|
|||
|
LocalFree( groupEnum );
|
|||
|
}
|
|||
|
|
|||
|
*ReturnEnum = NULL;
|
|||
|
*QuorumGroup = FALSE;
|
|||
|
return(status);
|
|||
|
|
|||
|
} // FmpEnumSortGroups
|
|||
|
|
|||
|
|
|||
|
|
|||
|
DWORD
|
|||
|
FmpGetGroupListState(
|
|||
|
PGROUP_ENUM GroupEnum
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine gets the Group state for each of the Groups in the list.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
GroupEnum - The list of Groups we now own.
|
|||
|
|
|||
|
Returns:
|
|||
|
|
|||
|
ERROR_SUCCESS if successful.
|
|||
|
|
|||
|
Win32 error code on failure.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PFM_GROUP group;
|
|||
|
DWORD i;
|
|||
|
|
|||
|
for ( i = 0; i < GroupEnum->EntryCount; i++ ) {
|
|||
|
group = OmReferenceObjectById( ObjectTypeGroup,
|
|||
|
GroupEnum->Entry[i].Id );
|
|||
|
if ( group == NULL ) {
|
|||
|
return(ERROR_GROUP_NOT_FOUND);
|
|||
|
}
|
|||
|
|
|||
|
ClRtlLogPrint( LOG_NOISE,
|
|||
|
"[FM] GetGroupListState, Group <%1!ws!> state = %2!d!\n",
|
|||
|
OmObjectName(group), group->State );
|
|||
|
if ( (group->State == ClusterGroupFailed) ||
|
|||
|
(group->State == ClusterGroupPartialOnline) ) {
|
|||
|
GroupEnum->Entry[i].State = ClusterGroupOnline;
|
|||
|
} else {
|
|||
|
GroupEnum->Entry[i].State = group->State;
|
|||
|
}
|
|||
|
|
|||
|
OmDereferenceObject( group );
|
|||
|
}
|
|||
|
|
|||
|
return(ERROR_SUCCESS);
|
|||
|
|
|||
|
} // FmpGetGroupListState
|
|||
|
|
|||
|
|
|||
|
|
|||
|
DWORD
|
|||
|
FmpOnlineGroupList(
|
|||
|
IN PGROUP_ENUM GroupEnum,
|
|||
|
IN BOOL bPrepareQuoForOnline
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Brings online all Groups in the Enum list. If the quorum group
|
|||
|
is present in the list, then it must be first.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
GroupEnum - The list of Groups to bring online.
|
|||
|
|
|||
|
bPrepareQuoForOnline - Indicates whether the quorum resource should be
|
|||
|
forced prepared for onlining
|
|||
|
Returns:
|
|||
|
|
|||
|
ERROR_SUCCESS if successful.
|
|||
|
|
|||
|
Win32 error code on failure.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PFM_GROUP group;
|
|||
|
DWORD status = ERROR_SUCCESS;
|
|||
|
int i;
|
|||
|
int iQuoGroup=-1;
|
|||
|
|
|||
|
//
|
|||
|
// see if the quorum group is present in the list.
|
|||
|
//
|
|||
|
for ( i = 0; (DWORD)i < GroupEnum->EntryCount; i++ )
|
|||
|
{
|
|||
|
|
|||
|
if (NmGetNodeId(NmLocalNode) !=
|
|||
|
NmGetNodeId(gpQuoResource->Group->OwnerNode)) {
|
|||
|
continue;
|
|||
|
}
|
|||
|
if (!lstrcmpW(OmObjectId(gpQuoResource->Group),
|
|||
|
GroupEnum->Entry[i].Id))
|
|||
|
{
|
|||
|
iQuoGroup = i;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//if quorum group was found, bring it online first. It would normally
|
|||
|
//be first in the list.
|
|||
|
//the quorum group online must return success, or invalid state
|
|||
|
//because of the online pending quorum resource.
|
|||
|
//if the quorum resource needs to be brought online, it must
|
|||
|
//be brought into online or online pending state. This is
|
|||
|
// not required in fix quorum mode.
|
|||
|
|
|||
|
if (iQuoGroup != -1)
|
|||
|
{
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpOnlineGroupList: bring quorum group online\n");
|
|||
|
status = FmpOnlineGroupFromList(GroupEnum, iQuoGroup, bPrepareQuoForOnline);
|
|||
|
if ( status != ERROR_SUCCESS && status != ERROR_IO_PENDING)
|
|||
|
{
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpOnlineGroupFromList: quorum online returned %1!u!.\n",
|
|||
|
status );
|
|||
|
CL_LOGFAILURE(status);
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
// bring the non-quorum groups online
|
|||
|
for ( i = 0; (DWORD)i < GroupEnum->EntryCount; i++ )
|
|||
|
{
|
|||
|
//quorum resource should be online now
|
|||
|
if (i != iQuoGroup)
|
|||
|
FmpOnlineGroupFromList(GroupEnum, i, bPrepareQuoForOnline);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
return(status);
|
|||
|
|
|||
|
} // FmpOnlineGroupList
|
|||
|
|
|||
|
|
|||
|
DWORD FmpOnlineGroupFromList(
|
|||
|
IN PGROUP_ENUM GroupEnum,
|
|||
|
IN DWORD Index,
|
|||
|
IN BOOL bPrepareQuoForOnline
|
|||
|
)
|
|||
|
{
|
|||
|
|
|||
|
PFM_GROUP group;
|
|||
|
DWORD status=ERROR_SUCCESS; //assume success
|
|||
|
PLIST_ENTRY listEntry;
|
|||
|
PFM_RESOURCE resource;
|
|||
|
|
|||
|
group = OmReferenceObjectById( ObjectTypeGroup,
|
|||
|
GroupEnum->Entry[Index].Id );
|
|||
|
|
|||
|
//
|
|||
|
// If we fail to find a group, then just continue.
|
|||
|
//
|
|||
|
if ( group == NULL ) {
|
|||
|
status = ERROR_GROUP_NOT_FOUND;
|
|||
|
return(status);
|
|||
|
}
|
|||
|
|
|||
|
FmpAcquireLocalGroupLock( group );
|
|||
|
|
|||
|
if (group->OwnerNode != NmLocalNode) {
|
|||
|
FmpReleaseLocalGroupLock( group );
|
|||
|
OmDereferenceObject(group);
|
|||
|
return (ERROR_HOST_NODE_NOT_RESOURCE_OWNER);
|
|||
|
}
|
|||
|
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpOnlineGroupFromList: Previous group state for %1!ws! is %2!u!\r\n",
|
|||
|
OmObjectId(group), GroupEnum->Entry[Index].State);
|
|||
|
|
|||
|
//
|
|||
|
// First make sure the group has completed initialization.
|
|||
|
//
|
|||
|
FmpCompleteInitGroup( group );
|
|||
|
|
|||
|
//
|
|||
|
// First check if the Group failed to initialize. If so,
|
|||
|
// then attempt a failover immediately.
|
|||
|
//
|
|||
|
if ( GroupEnum->Entry[Index].State == ClusterGroupPartialOnline ) {
|
|||
|
GroupEnum->Entry[Index].State = ClusterGroupOnline;
|
|||
|
}
|
|||
|
|
|||
|
if (!bPrepareQuoForOnline)
|
|||
|
{
|
|||
|
//
|
|||
|
// Normalize the state of each resource within the group.
|
|||
|
// except the quorum resource - this is because at initialization
|
|||
|
// we dont want to touch the quorum resource
|
|||
|
//
|
|||
|
for ( listEntry = group->Contains.Flink;
|
|||
|
listEntry != &(group->Contains);
|
|||
|
listEntry = listEntry->Flink ) {
|
|||
|
|
|||
|
resource = CONTAINING_RECORD(listEntry, FM_RESOURCE, ContainsLinkage);
|
|||
|
|
|||
|
if ( !resource->QuorumResource ) {
|
|||
|
// don't touch the quorum resource
|
|||
|
|
|||
|
switch ( resource->State ) {
|
|||
|
// all active resources should be brought online.
|
|||
|
case ClusterResourceOnlinePending:
|
|||
|
case ClusterResourceOfflinePending:
|
|||
|
case ClusterResourceOnline:
|
|||
|
resource->State = ClusterResourceOffline;
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
// otherwise do nothing
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
FmpSignalGroupWaiters( group );
|
|||
|
|
|||
|
if ( group->InitFailed ) {
|
|||
|
//
|
|||
|
// Bring the Group online... and then fail it!
|
|||
|
//
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpOnlineGroupFromList: group->InitFailed is true for %1!ws!\n",
|
|||
|
OmObjectId(group));
|
|||
|
|
|||
|
status = FmpOnlineGroup( group, FALSE );
|
|||
|
ClusterEvent( CLUSTER_EVENT_GROUP_FAILED, group );
|
|||
|
} else if ((group->PersistentState == ClusterGroupOnline) ||
|
|||
|
(GroupEnum->Entry[Index].State == ClusterGroupOnline) ||
|
|||
|
FmpIsAnyResourcePersistentStateOnline( group ) ) {
|
|||
|
//
|
|||
|
// Chittur Subbaraman (chitturs) - 01/07/2001
|
|||
|
//
|
|||
|
// Now bring the Group online if that is it's current state or if any one of the
|
|||
|
// resources in the group has an online persistent state. The third check is
|
|||
|
// required since it is possible for a group to have a persistent state of ClusterGroupOffline,
|
|||
|
// a state of ClusterGroupOffline and yet one or more resources in the group has a persistent
|
|||
|
// state of ClusterResourceOnline. This happens for a group in which the client never ever
|
|||
|
// calls OnlineGroup but calls OnlineResource for one or more resources in the group and you
|
|||
|
// reached this call either at the cluster service startup time or as a part of node down
|
|||
|
// processing when the source node died just after the group became ClusterGroupOffline
|
|||
|
// and before the destination node brought the appropriate resources within the group online.
|
|||
|
// In such a case, we still want to bring each resource that has a persistent state of
|
|||
|
// ClusterResourceOnline to online state. Note that it is tricky to muck with the group
|
|||
|
// persistent state in an OnlineResource call due to atomicity issues (we really need a
|
|||
|
// transaction to update both group and resource persistent states in one shot) and also
|
|||
|
// due to the fuzzy definition of group persistent state when the group has some resources
|
|||
|
// online and some offline.
|
|||
|
//
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpOnlineGroupFromList: trying to bring group %1!ws! online\n",
|
|||
|
OmObjectId(group));
|
|||
|
|
|||
|
status = FmpOnlineGroup( group, FALSE );
|
|||
|
if (status == ERROR_QUORUM_RESOURCE_ONLINE_FAILED)
|
|||
|
{
|
|||
|
PRESOURCE_ENUM pResourceEnum;
|
|||
|
// This fn is either called at startup or during
|
|||
|
// a node down event on claiming a group - so we must
|
|||
|
// try our darn best to bring resources
|
|||
|
// online after a quorum resource failure
|
|||
|
// With quorum resource failure the failure policy is
|
|||
|
// not invoked for resources so something must try to bring
|
|||
|
// these resources online. This is why we are adding this
|
|||
|
// here
|
|||
|
//
|
|||
|
// Get the list of resources in the group and their states.
|
|||
|
//
|
|||
|
status = FmpGetResourceList( &pResourceEnum, group );
|
|||
|
if ( status == ERROR_SUCCESS )
|
|||
|
{
|
|||
|
|
|||
|
//submit a timer callback to try and bring these resources
|
|||
|
//online
|
|||
|
//the worker thread will clean up the resource list
|
|||
|
FmpSubmitRetryOnline(pResourceEnum);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
FmpReleaseLocalGroupLock( group );
|
|||
|
|
|||
|
OmDereferenceObject( group );
|
|||
|
|
|||
|
return(status);
|
|||
|
|
|||
|
} // FmpOnlineGroupFromList
|
|||
|
|
|||
|
DWORD
|
|||
|
FmpOnlineResourceFromList(
|
|||
|
IN PRESOURCE_ENUM ResourceEnum
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Brings online all resources in the Enum list.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ResourceEnum - The list of resources to bring online.
|
|||
|
|
|||
|
Comments : This function is called from the worker thread. We
|
|||
|
dont assume that the resource hasnt changed groups since the
|
|||
|
work item was posted. The local resource lock is acquired and
|
|||
|
released for each resource.
|
|||
|
|
|||
|
Returns:
|
|||
|
|
|||
|
ERROR_SUCCESS if successful.
|
|||
|
|
|||
|
Win32 error code on failure.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PFM_RESOURCE resource;
|
|||
|
DWORD status;
|
|||
|
DWORD returnStatus = ERROR_SUCCESS;
|
|||
|
DWORD i;
|
|||
|
|
|||
|
if ( !FmpFMOnline ||
|
|||
|
FmpShutdown ) {
|
|||
|
return(ERROR_INVALID_STATE);
|
|||
|
}
|
|||
|
|
|||
|
// if the quorum resource is contained in here, bring it online first
|
|||
|
if (ResourceEnum->ContainsQuorum >= 0)
|
|||
|
{
|
|||
|
CL_ASSERT((DWORD)ResourceEnum->ContainsQuorum < ResourceEnum->EntryCount);
|
|||
|
|
|||
|
resource = OmReferenceObjectById( ObjectTypeResource,
|
|||
|
ResourceEnum->Entry[ResourceEnum->ContainsQuorum].Id );
|
|||
|
|
|||
|
|
|||
|
// the resource should not vanish, we are holding the group lock after all
|
|||
|
CL_ASSERT(resource != NULL);
|
|||
|
|
|||
|
//
|
|||
|
// If we fail to find a resource, then just continue
|
|||
|
//
|
|||
|
if ( resource != NULL ) {
|
|||
|
|
|||
|
//acquire the local resource lock
|
|||
|
FmpAcquireLocalResourceLock(resource);
|
|||
|
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpOnlineResourceFromList: Previous quorum resource state for %1!ws! is %2!u!\r\n",
|
|||
|
OmObjectId(resource), ResourceEnum->Entry[ResourceEnum->ContainsQuorum].State);
|
|||
|
|
|||
|
if ( (ResourceEnum->Entry[ResourceEnum->ContainsQuorum].State == ClusterResourceOnline) ||
|
|||
|
(ResourceEnum->Entry[ResourceEnum->ContainsQuorum].State == ClusterResourceFailed) ) {
|
|||
|
//
|
|||
|
// Now bring the resource online if that is it's current state.
|
|||
|
//
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpOnlineResourceFromList: trying to bring quorum resource %1!ws! online, state %2!u!\n",
|
|||
|
OmObjectId(resource),
|
|||
|
resource->State);
|
|||
|
|
|||
|
status = FmpOnlineResource( resource, FALSE );
|
|||
|
if ( status != ERROR_SUCCESS ) {
|
|||
|
returnStatus = status;
|
|||
|
}
|
|||
|
}
|
|||
|
OmDereferenceObject( resource );
|
|||
|
|
|||
|
FmpReleaseLocalResourceLock(resource);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
// SS::: TODO what happens to the persistent state of the
|
|||
|
// other resources - is it handled correctly - note that this is
|
|||
|
// called on moving a group
|
|||
|
// Will the restart policy do the right thing in terms of bringing
|
|||
|
// them online
|
|||
|
// if the quorum resource has failed, dont bother trying
|
|||
|
// to bring the rest of the resourcess online
|
|||
|
if ((returnStatus != ERROR_SUCCESS) && (returnStatus != ERROR_IO_PENDING))
|
|||
|
{
|
|||
|
FmpSubmitRetryOnline(ResourceEnum);
|
|||
|
goto FnExit;
|
|||
|
}
|
|||
|
|
|||
|
// bring online all of the other resources
|
|||
|
for ( i = 0; i < ResourceEnum->EntryCount; i++ ) {
|
|||
|
resource = OmReferenceObjectById( ObjectTypeResource,
|
|||
|
ResourceEnum->Entry[i].Id );
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// If we fail to find a resource, then just continue.
|
|||
|
//
|
|||
|
if ( resource == NULL ) {
|
|||
|
status = ERROR_RESOURCE_NOT_FOUND;
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
FmpAcquireLocalResourceLock(resource);
|
|||
|
|
|||
|
//if the resource has been marked for delete, then dont let
|
|||
|
//it be brought online
|
|||
|
if (!IS_VALID_FM_RESOURCE(resource))
|
|||
|
{
|
|||
|
FmpReleaseLocalResourceLock( resource );
|
|||
|
OmDereferenceObject(resource);
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//quorum resource has already been handled
|
|||
|
if (resource->QuorumResource)
|
|||
|
{
|
|||
|
FmpReleaseLocalResourceLock( resource );
|
|||
|
OmDereferenceObject(resource);
|
|||
|
continue;
|
|||
|
}
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpOnlineResourceFromList: Previous resource state for %1!ws! is %2!u!\r\n",
|
|||
|
OmObjectId(resource), ResourceEnum->Entry[i].State);
|
|||
|
|
|||
|
if ( (ResourceEnum->Entry[i].State == ClusterResourceOnline) ||
|
|||
|
(ResourceEnum->Entry[i].State == ClusterResourceFailed) )
|
|||
|
{
|
|||
|
//
|
|||
|
// Now bring the resource online if that is it's current state.
|
|||
|
//
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpOnlineResourceFromList: trying to bring resource %1!ws! online\n",
|
|||
|
OmObjectId(resource));
|
|||
|
|
|||
|
status = FmpOnlineResource( resource, FALSE );
|
|||
|
if ( returnStatus == ERROR_SUCCESS )
|
|||
|
{
|
|||
|
returnStatus = status;
|
|||
|
}
|
|||
|
//if this resource didnt come online because the quorum resource
|
|||
|
//didnt come online, dont bother bringing the other resources online
|
|||
|
//just a waste of time
|
|||
|
if (status == ERROR_QUORUM_RESOURCE_ONLINE_FAILED)
|
|||
|
{
|
|||
|
//submit a timer callback to try and bring these resources
|
|||
|
//online
|
|||
|
FmpReleaseLocalResourceLock( resource );
|
|||
|
OmDereferenceObject( resource );
|
|||
|
FmpSubmitRetryOnline(ResourceEnum);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
FmpReleaseLocalResourceLock( resource );
|
|||
|
OmDereferenceObject( resource );
|
|||
|
}
|
|||
|
|
|||
|
FnExit:
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpOnlineResourceFromList: Exit, status=%1!u!\r\n",
|
|||
|
returnStatus);
|
|||
|
return(returnStatus);
|
|||
|
|
|||
|
} // FmpOnlineResourceFromList
|
|||
|
|
|||
|
|
|||
|
|
|||
|
BOOL
|
|||
|
FmpEqualGroupLists(
|
|||
|
IN PGROUP_ENUM Group1,
|
|||
|
IN PGROUP_ENUM Group2
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine verifies that two group lists are equal.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Group1 - The first group to compare.
|
|||
|
Group2 - The second group to compare.
|
|||
|
|
|||
|
Returns:
|
|||
|
|
|||
|
TRUE - if the two lists are equal.
|
|||
|
FALSE - otherwise.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
DWORD i;
|
|||
|
|
|||
|
if ( (Group1 == NULL) ||
|
|||
|
(Group2 == NULL) ) {
|
|||
|
ClRtlLogPrint(LOG_NOISE,"[FM] One of the Group lists is NULL for equality check\n");
|
|||
|
return(FALSE);
|
|||
|
}
|
|||
|
|
|||
|
if ( Group1->EntryCount != Group2->EntryCount ) {
|
|||
|
ClRtlLogPrint(LOG_NOISE,"[FM] Group entry counts not equal! Left: %1!u!, Right: %2!u!.\n",
|
|||
|
Group1->EntryCount, Group2->EntryCount);
|
|||
|
return(FALSE);
|
|||
|
}
|
|||
|
|
|||
|
for ( i = 0; i < Group1->EntryCount; i++ ) {
|
|||
|
if ( lstrcmpiW(Group1->Entry[i].Id, Group2->Entry[i].Id) != 0 ) {
|
|||
|
ClRtlLogPrint(LOG_NOISE,"[FM] Group Lists do not have same names!\n");
|
|||
|
return(FALSE);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return(TRUE);
|
|||
|
|
|||
|
} // FmpEqualGroupLists
|
|||
|
|
|||
|
|
|||
|
|
|||
|
BOOL
|
|||
|
FmpEnumGroups(
|
|||
|
IN OUT PGROUP_ENUM *Enum,
|
|||
|
IN PFM_GROUP_ENUM_DATA EnumData,
|
|||
|
IN PFM_GROUP Group,
|
|||
|
IN LPCWSTR Id
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Worker callback routine for the enumeration of Groups.
|
|||
|
This routine adds the specified Group to the list that is being
|
|||
|
generated.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Enum - The Group 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 Group object being enumerated.
|
|||
|
|
|||
|
Returns:
|
|||
|
|
|||
|
TRUE - to indicate that the enumeration should continue.
|
|||
|
|
|||
|
Side Effects:
|
|||
|
|
|||
|
Makes the quorum group first in the list.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PGROUP_ENUM groupEnum;
|
|||
|
PGROUP_ENUM newEnum;
|
|||
|
DWORD newAllocated;
|
|||
|
DWORD index;
|
|||
|
LPWSTR newId;
|
|||
|
LPWSTR tmpId;
|
|||
|
DWORD status;
|
|||
|
PFM_RESOURCE quorumResource;
|
|||
|
|
|||
|
//HACKHACK::
|
|||
|
//SS: Since this is invoked from within a gum call and
|
|||
|
// the owner node is changed only within a gum call
|
|||
|
// we wont acquire locks.
|
|||
|
// there is a window if the dead node is the source of a
|
|||
|
// move and if it does a move after it is declared dead by
|
|||
|
// other nodes, the target of move and the fmpassignownerstogroup
|
|||
|
// might both land up bringing the group online on two nodes
|
|||
|
// However, if we could be guaranteed virtual synchrony, then
|
|||
|
// the target of move wouldnt accept calls from a dead node and
|
|||
|
// we wont land up in this soup. Now, it is upto the xport layer
|
|||
|
// to provide this guarantee.
|
|||
|
// For now we acquire no locks
|
|||
|
|
|||
|
//FmpAcquireLocalGroupLock( Group );
|
|||
|
|
|||
|
if ((EnumData->OwnerNode != NULL) &&
|
|||
|
(EnumData->OwnerNode != Group->OwnerNode) &&
|
|||
|
(EnumData->OwnerNode != Group->pIntendedOwner)) {
|
|||
|
//
|
|||
|
// This group does not match the owner criteria
|
|||
|
//
|
|||
|
//FmpReleaseLocalGroupLock( Group );
|
|||
|
return(TRUE);
|
|||
|
}
|
|||
|
|
|||
|
//FmpReleaseLocalGroupLock( Group );
|
|||
|
|
|||
|
groupEnum = *Enum;
|
|||
|
|
|||
|
if ( groupEnum->EntryCount >= EnumData->Allocated ) {
|
|||
|
//
|
|||
|
// Time to grow the GROUP_ENUM
|
|||
|
//
|
|||
|
|
|||
|
newAllocated = EnumData->Allocated + ENUM_GROW_SIZE;
|
|||
|
newEnum = LocalAlloc(LMEM_FIXED, GROUP_SIZE(newAllocated));
|
|||
|
if ( newEnum == NULL ) {
|
|||
|
return(FALSE);
|
|||
|
}
|
|||
|
|
|||
|
CopyMemory(newEnum, groupEnum, GROUP_SIZE(EnumData->Allocated));
|
|||
|
EnumData->Allocated = newAllocated;
|
|||
|
*Enum = newEnum;
|
|||
|
LocalFree(groupEnum);
|
|||
|
groupEnum = newEnum;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Initialize new entry
|
|||
|
//
|
|||
|
newId = LocalAlloc(LMEM_FIXED, (lstrlenW(Id)+1) * sizeof(WCHAR));
|
|||
|
if ( newId == NULL ) {
|
|||
|
CsInconsistencyHalt(ERROR_NOT_ENOUGH_MEMORY);
|
|||
|
}
|
|||
|
|
|||
|
lstrcpyW(newId, Id);
|
|||
|
|
|||
|
//
|
|||
|
// Find the quorum resource, and see if it is this group.
|
|||
|
//
|
|||
|
status = FmFindQuorumResource( &quorumResource );
|
|||
|
if ( status != ERROR_SUCCESS ) {
|
|||
|
CsInconsistencyHalt(status);
|
|||
|
}
|
|||
|
|
|||
|
groupEnum->Entry[groupEnum->EntryCount].Id = newId;
|
|||
|
if ( quorumResource->Group == Group ) {
|
|||
|
// found the quorum resource group, put it first in the list.
|
|||
|
tmpId = groupEnum->Entry[0].Id;
|
|||
|
groupEnum->Entry[0].Id = newId;
|
|||
|
groupEnum->Entry[groupEnum->EntryCount].Id = tmpId;
|
|||
|
EnumData->QuorumGroup = TRUE;
|
|||
|
}
|
|||
|
++groupEnum->EntryCount;
|
|||
|
|
|||
|
OmDereferenceObject( quorumResource );
|
|||
|
|
|||
|
return(TRUE);
|
|||
|
|
|||
|
} // FmpEnumGroups
|
|||
|
|
|||
|
|
|||
|
|
|||
|
#if 0
|
|||
|
int
|
|||
|
_cdecl
|
|||
|
SortCompare(
|
|||
|
IN const PVOID Elem1,
|
|||
|
IN const PVOID Elem2
|
|||
|
)
|
|||
|
|
|||
|
{
|
|||
|
PGROUP_ENUM_ENTRY El1 = (PGROUP_ENUM_ENTRY)Elem1;
|
|||
|
PGROUP_ENUM_ENTRY El2 = (PGROUP_ENUM_ENTRY)Elem2;
|
|||
|
|
|||
|
return(lstrcmpiW( El1->Id, El2->Id ));
|
|||
|
|
|||
|
} // SortCompare
|
|||
|
#endif
|
|||
|
|
|||
|
|
|||
|
DWORD
|
|||
|
FmpClaimAllGroups(
|
|||
|
PGROUP_ENUM MyGroups
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Takes ownership of all the groups defined in the cluster. This
|
|||
|
is used when a new cluster is being formed.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
ERROR_SUCCESS if successful
|
|||
|
|
|||
|
Win32 errorcode otherwise
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
//
|
|||
|
// Bring online any Group that needs to be online.
|
|||
|
//
|
|||
|
FmpOnlineGroupList( MyGroups, FALSE );
|
|||
|
|
|||
|
return(ERROR_SUCCESS);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
FmpDeleteEnum(
|
|||
|
IN PGROUP_ENUM Enum
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine deletes an GROUP_ENUM and associated name strings.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Enum - The GROUP_ENUM to delete. This pointer can be NULL.
|
|||
|
|
|||
|
Returns:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
Notes:
|
|||
|
|
|||
|
This routine will take a NULL input pointer and just return.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PGROUP_ENUM_ENTRY enumEntry;
|
|||
|
DWORD i;
|
|||
|
|
|||
|
if ( Enum == NULL ) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
for ( i = 0; i < Enum->EntryCount; i++ ) {
|
|||
|
enumEntry = &Enum->Entry[i];
|
|||
|
LocalFree(enumEntry->Id);
|
|||
|
}
|
|||
|
|
|||
|
LocalFree(Enum);
|
|||
|
return;
|
|||
|
|
|||
|
} // FmpDeleteEnum
|
|||
|
|
|||
|
|
|||
|
/****
|
|||
|
@func VOID | FmpPrepareGroupForOnline| This routine sets the Group
|
|||
|
up for onlining it on this node post a failure of a node
|
|||
|
or at initialization.
|
|||
|
|
|||
|
@parm IN PFM_GROUP | pGroup| A pointer to the group.
|
|||
|
|
|||
|
@comm The group lock must be held. Except when called at bootstrapping
|
|||
|
by FmBringQuorumOnline.
|
|||
|
MUST BE CALLED ONLY BY THE OWNER NODE OF THE GROUP.
|
|||
|
|
|||
|
@rdesc returns ERROR_SUCCESS if succesful else w32 error code.
|
|||
|
MUST BE CALLED ONLY BY THE OWNER NODE OF THE GROUP.
|
|||
|
****/
|
|||
|
VOID FmpPrepareGroupForOnline(
|
|||
|
IN PFM_GROUP pGroup
|
|||
|
)
|
|||
|
{
|
|||
|
PLIST_ENTRY pListEntry;
|
|||
|
PFM_RESOURCE pResource;
|
|||
|
|
|||
|
pGroup->State = ClusterGroupOffline;
|
|||
|
++pGroup->StateSequence;
|
|||
|
//
|
|||
|
// Mark offline all of the resources contained within this group.
|
|||
|
//
|
|||
|
for (pListEntry = pGroup->Contains.Flink;
|
|||
|
pListEntry != &pGroup->Contains;
|
|||
|
pListEntry = pListEntry->Flink)
|
|||
|
{
|
|||
|
pResource = CONTAINING_RECORD(pListEntry, FM_RESOURCE, ContainsLinkage);
|
|||
|
pResource->State = ClusterResourceOffline;
|
|||
|
++pResource->StateSequence;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/****
|
|||
|
@func DWORD | FmpSetGroupEnumOwner| This routine sets the Group
|
|||
|
owner for all Groups in the list.
|
|||
|
|
|||
|
@parm IN PGROUP_ENUM | pGroupEnum| The list of Groups.
|
|||
|
@parm IN PNM_NODE | pDefaultOwnerNode | A pointer to the default owner
|
|||
|
node.
|
|||
|
@parm IN PNM_NODE | pDeadNode | A pointed to the node that died. If
|
|||
|
this routine is being called other wise, this is set to NULL.
|
|||
|
@parm IN BOOL | bQuorumGroup | set to TRUE if the quorum group is
|
|||
|
on the list of groups.
|
|||
|
|
|||
|
@parm IN PFM_GROUP_NODE_LIST | pGroupNodeList | The randomized suggested preferred
|
|||
|
owner for all groups.
|
|||
|
|
|||
|
@comm If the group was in the process of moving and had an intended
|
|||
|
owner and the intended owner is not dead, the intended owner is
|
|||
|
allowed to take care of the group. Else, the first node on the
|
|||
|
preferred list that is up is chosen as the owner. If no such
|
|||
|
node exits, then the ownership is assigned to the default owner
|
|||
|
provided. This routine is called by the forming node at
|
|||
|
initialization to claimownership of all groups and by the gum
|
|||
|
update procedure FmpUpdateAssignOwnerToGroups.
|
|||
|
|
|||
|
@rdesc returns ERROR_SUCCESS if succesful else w32 error code.
|
|||
|
****/
|
|||
|
DWORD
|
|||
|
FmpSetGroupEnumOwner(
|
|||
|
IN PGROUP_ENUM pGroupEnum,
|
|||
|
IN PNM_NODE pDefaultOwnerNode,
|
|||
|
IN PNM_NODE pDeadNode,
|
|||
|
IN BOOL bQuorumGroup,
|
|||
|
IN PFM_GROUP_NODE_LIST pGroupNodeList
|
|||
|
)
|
|||
|
{
|
|||
|
PFM_GROUP pGroup;
|
|||
|
DWORD i;
|
|||
|
DWORD dwStatus = ERROR_SUCCESS;
|
|||
|
PNM_NODE pOwnerNode;
|
|||
|
|
|||
|
|
|||
|
for ( i = 0; i < pGroupEnum->EntryCount; i++ )
|
|||
|
{
|
|||
|
pGroup = OmReferenceObjectById( ObjectTypeGroup,
|
|||
|
pGroupEnum->Entry[i].Id );
|
|||
|
if ( pGroup == NULL )
|
|||
|
{
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpSetGroupEnumOwner: Group %1!ws! not found\n",
|
|||
|
pGroupEnum->Entry[i].Id);
|
|||
|
dwStatus = ERROR_GROUP_NOT_FOUND;
|
|||
|
goto FnExit;
|
|||
|
}
|
|||
|
//
|
|||
|
// SS: HACKHACK : cant get the group lock within a gum update
|
|||
|
// FmpAcquireLocalGroupLock( pGroup );
|
|||
|
//
|
|||
|
// SS: In case of a node death, see if there was an intended owner
|
|||
|
// if the intended owner is set and if the intended owner is
|
|||
|
// not the one that died then we use the normal procedure
|
|||
|
// else we let the intended owner take care of the group.
|
|||
|
|
|||
|
//
|
|||
|
// Chittur Subbaraman (chitturs) - 7/26/99
|
|||
|
//
|
|||
|
// Condition 2: Means the group was being moved and FmpTakeGroupRequest
|
|||
|
// has not taken 100% responsibility for the group.
|
|||
|
//
|
|||
|
// Condition 3: Means the source node crashed and NOT the destination node.
|
|||
|
//
|
|||
|
// Added condition 4 to cover the case in which the source node of
|
|||
|
// the move crashed AFTER setting the intended owner as the
|
|||
|
// destination node and BEFORE the FmpTakeGroupRequest has set
|
|||
|
// the group ownership to the destination node.
|
|||
|
//
|
|||
|
// If the group's owner node and the group's intended owner node are
|
|||
|
// not the same, then let this GUM handler take care of assigning
|
|||
|
// the group ownership. This means that the FmpTakeGroupRequest
|
|||
|
// has not yet set the ownership for the group to the destination
|
|||
|
// node of the move. Now, once this GUM handler sets the
|
|||
|
// ownership for the group and then resets the intended owner to
|
|||
|
// NULL, FmpTakeGroupRequest which could follow behind this GUM handler
|
|||
|
// will not succeed in setting the ownership to the local node and that
|
|||
|
// will just return doing nothing. This is TRUE only for an NT5 cluster.
|
|||
|
// For a mixed-mode cluster, all bets are off.
|
|||
|
//
|
|||
|
if ( (pDeadNode) &&
|
|||
|
(pGroup->pIntendedOwner != NULL) &&
|
|||
|
(pGroup->pIntendedOwner != pDeadNode) &&
|
|||
|
(pGroup->OwnerNode == pGroup->pIntendedOwner) )
|
|||
|
{
|
|||
|
//
|
|||
|
// Chittur Subbaraman (chitturs) - 7/27/99
|
|||
|
//
|
|||
|
// Looks like this code inside "if" will never ever be
|
|||
|
// executed. Keeping it so as to make the changes minimal.
|
|||
|
//
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpSetGroupEnumOwner: Group %1!ws! will be handled by node %2!ws!\n",
|
|||
|
OmObjectId(pGroup), OmObjectId(pGroup->pIntendedOwner));
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Find first preferred node that is UP, if we can't find any use
|
|||
|
// default OwnerNode
|
|||
|
//
|
|||
|
//
|
|||
|
// If this is the quorum group, then use the node that was selected
|
|||
|
// by the MM layer. The quorum group is the first entry in the list
|
|||
|
// and the Boolean QuorumGroup must be TRUE!
|
|||
|
//
|
|||
|
if ( (i == 0) && bQuorumGroup )
|
|||
|
{
|
|||
|
DWORD dwOwnerNodeId;
|
|||
|
|
|||
|
//for the quorum group find the node that had last
|
|||
|
//arbitrated for it.
|
|||
|
//We do this by asking MM about it.
|
|||
|
//If there was no arbitration during the last regroup
|
|||
|
//but there was one in the one before that one, the
|
|||
|
//node that arbitrated is returned.
|
|||
|
//This node should be able to online the group.
|
|||
|
//We use MMApproxArbitrationWinner instead if
|
|||
|
// MMGetArbitrationWinner() since multiple-regroups
|
|||
|
// might occur before the FM handles the node down
|
|||
|
// event for this node.
|
|||
|
MMApproxArbitrationWinner( &dwOwnerNodeId );
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpSetGroupEnumOwner:: MM suggests node %1!u! for quorum owner\r\n",
|
|||
|
dwOwnerNodeId);
|
|||
|
|
|||
|
if ( dwOwnerNodeId != MM_INVALID_NODE )
|
|||
|
{
|
|||
|
pOwnerNode = NmReferenceNodeById( dwOwnerNodeId );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
ClRtlLogPrint(LOG_CRITICAL,
|
|||
|
"[FM] FmpSetGroupEnumOwner:: MM returned MM_INVALID_NODE, chose the default target\r\n");
|
|||
|
//else just use the default target
|
|||
|
pOwnerNode = pDefaultOwnerNode;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
pOwnerNode = FmpGetPreferredNode(pGroup);
|
|||
|
if ( pOwnerNode == NULL )
|
|||
|
{
|
|||
|
pOwnerNode = pDefaultOwnerNode;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If the caller (GUM) has supplied a randomized preferred owner of the group, then
|
|||
|
// see if it can be used.
|
|||
|
//
|
|||
|
if ( pGroupNodeList != NULL )
|
|||
|
{
|
|||
|
pOwnerNode = FmpParseGroupNodeListForPreferredOwner( pGroup,
|
|||
|
pGroupNodeList,
|
|||
|
pOwnerNode );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if ( pGroup->OwnerNode != NULL )
|
|||
|
{
|
|||
|
OmDereferenceObject( pGroup->OwnerNode );
|
|||
|
}
|
|||
|
|
|||
|
OmReferenceObject( pOwnerNode );
|
|||
|
pGroup->OwnerNode = pOwnerNode;
|
|||
|
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpSetGroupEnumOwner: Group's %1!ws! new owner is node %2!ws!\n",
|
|||
|
OmObjectId(pGroup), OmObjectId(pOwnerNode));
|
|||
|
|
|||
|
//FmpReleaseLocalGroupLock( pGroup );
|
|||
|
OmDereferenceObject(pGroup);
|
|||
|
}
|
|||
|
|
|||
|
FnExit:
|
|||
|
return(dwStatus);
|
|||
|
|
|||
|
} // FmpSetGroupEnumOwner
|
|||
|
|
|||
|
|
|||
|
DWORD
|
|||
|
FmpAssignOwnersToGroups(
|
|||
|
IN PNM_NODE pDeadNode,
|
|||
|
IN PFM_GROUP pGroup,
|
|||
|
IN PFM_GROUP_NODE_LIST pGroupNodeList
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Takes ownership of all the groups defined in the cluster that
|
|||
|
are owned by another node. This is used when a node fails.
|
|||
|
|
|||
|
The current algorithm is very dumb and simple. Node with the
|
|||
|
lowest ID gets all the groups.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
pDeadNode - Supplies the node that all the groups should be taken
|
|||
|
from.
|
|||
|
|
|||
|
pGroup - Supplies the group which alone is to be claimed.
|
|||
|
|
|||
|
pGroupNodeList - The randomized suggested preferred owner for all groups.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
ERROR_SUCCESS if successful
|
|||
|
|
|||
|
Win32 errorcode otherwise
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
DWORD i;
|
|||
|
DWORD dwStatus;
|
|||
|
PGROUP_ENUM pNodeGroups = NULL;
|
|||
|
PNM_NODE pDefaultTarget = NULL;
|
|||
|
PNM_NODE pPausedTarget = NULL;
|
|||
|
BOOL bQuorumGroup;
|
|||
|
|
|||
|
//
|
|||
|
// Acquire the global group lock
|
|||
|
//
|
|||
|
FmpAcquireGroupLock();
|
|||
|
|
|||
|
//
|
|||
|
// Check if groups are initialized
|
|||
|
//
|
|||
|
if ( !FmpFMGroupsInited )
|
|||
|
{
|
|||
|
dwStatus = ERROR_SUCCESS;
|
|||
|
goto FnExit;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Find and sort all known groups
|
|||
|
//
|
|||
|
if ( pGroup == NULL )
|
|||
|
{
|
|||
|
dwStatus = FmpEnumSortGroups(&pNodeGroups, pDeadNode, &bQuorumGroup);
|
|||
|
} else
|
|||
|
{
|
|||
|
//
|
|||
|
// Chittur Subbaraman (chitturs) - 6/7/99
|
|||
|
//
|
|||
|
// This means you got here due to an RPC exception raised in
|
|||
|
// FmpTakeGroupRequest. So, see where this sole group goes.
|
|||
|
//
|
|||
|
dwStatus = FmpGetGroupInNodeGroupList(&pNodeGroups, pGroup, pDeadNode, &bQuorumGroup);
|
|||
|
}
|
|||
|
|
|||
|
if (dwStatus != ERROR_SUCCESS)
|
|||
|
{
|
|||
|
CL_ASSERT(pNodeGroups == NULL);
|
|||
|
goto FnExit;
|
|||
|
}
|
|||
|
|
|||
|
CL_ASSERT(pNodeGroups != NULL);
|
|||
|
|
|||
|
//if no nodes were owned by this node, just return
|
|||
|
if (pNodeGroups->EntryCount == 0)
|
|||
|
{
|
|||
|
FmpDeleteEnum(pNodeGroups);
|
|||
|
goto FnExit;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Find the state of the Groups.
|
|||
|
//
|
|||
|
FmpGetGroupListState( pNodeGroups );
|
|||
|
|
|||
|
//
|
|||
|
// Find the active node with the lowest ID to be the default
|
|||
|
// owner of these groups.
|
|||
|
//
|
|||
|
// If we can't find an active node then select the lowest node id for
|
|||
|
// a node that is paused.
|
|||
|
//
|
|||
|
CL_ASSERT(NmMaxNodeId != ClusterInvalidNodeId);
|
|||
|
CL_ASSERT(Session != NULL);
|
|||
|
|
|||
|
for (i=ClusterMinNodeId; i<=NmMaxNodeId; i++)
|
|||
|
{
|
|||
|
pDefaultTarget = NmReferenceNodeById(i);
|
|||
|
|
|||
|
if ( pDefaultTarget != NULL )
|
|||
|
{
|
|||
|
//if this node is up, there is no need to use a paused target
|
|||
|
if ( NmGetNodeState(pDefaultTarget) == ClusterNodeUp )
|
|||
|
{
|
|||
|
if ( pPausedTarget )
|
|||
|
{
|
|||
|
OmDereferenceObject(pPausedTarget);
|
|||
|
pPausedTarget = NULL;
|
|||
|
}
|
|||
|
//found a node, leave this loop
|
|||
|
break;
|
|||
|
}
|
|||
|
//node is not up, check if it paused
|
|||
|
//if is is paused and no other paused node has been found
|
|||
|
//set this one to be the lowest paused node
|
|||
|
if ( !pPausedTarget &&
|
|||
|
(NmGetNodeState(pDefaultTarget) == ClusterNodePaused) )
|
|||
|
{
|
|||
|
pPausedTarget = pDefaultTarget;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
OmDereferenceObject(pDefaultTarget);
|
|||
|
}
|
|||
|
pDefaultTarget = NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if ( (pDefaultTarget == NULL) && (pPausedTarget == NULL) ) {
|
|||
|
//
|
|||
|
// There are no online/paused nodes, this node must be paused,
|
|||
|
// so don't do anything.
|
|||
|
//
|
|||
|
ClRtlLogPrint(LOG_CRITICAL,
|
|||
|
"[FM] FmpAssignOwnersToGroups - no online/paused nodes remaining\n");
|
|||
|
//SS: then what are we doing here
|
|||
|
FmpDeleteEnum(pNodeGroups);
|
|||
|
goto FnExit;
|
|||
|
}
|
|||
|
|
|||
|
//if no node is up, use the lowest paused node as the default owner for
|
|||
|
//the groups
|
|||
|
if ( pDefaultTarget == NULL )
|
|||
|
{
|
|||
|
pDefaultTarget = pPausedTarget;
|
|||
|
}
|
|||
|
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpAssignOwnersToGroups - DefaultTarget is %1!ws!\n",
|
|||
|
OmObjectId(pDefaultTarget));
|
|||
|
|
|||
|
//
|
|||
|
// Chittur Subbaraman (chitturs) - 7/20/99
|
|||
|
//
|
|||
|
// Prepare the entire group list for subsequent online. You have
|
|||
|
// to do this here to have a consistent resource state view
|
|||
|
// among different nodes in the cluster since this is the GUM
|
|||
|
// handler. Also, the DM node down handler which follows this
|
|||
|
// GUM handler may think that the quorum resource is owned by
|
|||
|
// this node and its state is online while it has not been
|
|||
|
// brought online on this node. Note also the order of this
|
|||
|
// call and the call to set the group ownership. THIS ORDER
|
|||
|
// MUST BE FOLLOWED since we don't hold any groups lock here
|
|||
|
// (since we are paranoid about deadlocks) and we don't want
|
|||
|
// the FmCheckQuorumState function called as a part of the
|
|||
|
// DM node down handler to think that the group is owned by
|
|||
|
// this node and is also online on this node.
|
|||
|
//
|
|||
|
FmpPrepareGroupEnumForOnline( pNodeGroups );
|
|||
|
|
|||
|
//
|
|||
|
// Set the Group owner.
|
|||
|
//
|
|||
|
FmpSetGroupEnumOwner( pNodeGroups,
|
|||
|
pDefaultTarget,
|
|||
|
pDeadNode,
|
|||
|
bQuorumGroup,
|
|||
|
pGroupNodeList );
|
|||
|
|
|||
|
//
|
|||
|
// Chittur Subbaraman (chitturs) - 5/26/99
|
|||
|
//
|
|||
|
// Clear the intended owner fields of all the groups. This is done
|
|||
|
// since there is no guarantee that FmpTakeGroupRequest will do this.
|
|||
|
//
|
|||
|
FmpResetGroupIntendedOwner( pNodeGroups );
|
|||
|
|
|||
|
//
|
|||
|
// Chittur Subbaraman (chitturs) - 7/14/99
|
|||
|
//
|
|||
|
// Handle the online of group list containing the quorum resource with
|
|||
|
// a separate thread and let the worker thread handle group lists
|
|||
|
// not containing the quorum resource. This is necessary since it is
|
|||
|
// possible that this node can take ownership at roughly the same
|
|||
|
// time of a quorum group and a non-quorum group each resident
|
|||
|
// in a different node due to back-to-back node crashes. In such a
|
|||
|
// case, we can't order these groups for online globally with the
|
|||
|
// quorum group first in the list. So, we don't want the worker thread
|
|||
|
// to be "stuck" in FmpRmOnlineResource for the non-quorum group's
|
|||
|
// resource waiting for the quorum group to be brought online since
|
|||
|
// the quorum group online work item is queued behind the non-quorum
|
|||
|
// group online work item.
|
|||
|
//
|
|||
|
if ( bQuorumGroup )
|
|||
|
{
|
|||
|
HANDLE hThread = NULL;
|
|||
|
DWORD dwThreadId;
|
|||
|
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpAssignOwnersToGroups - Create thread to handle group list containing quorum group....\n"
|
|||
|
);
|
|||
|
|
|||
|
hThread = CreateThread( NULL,
|
|||
|
0,
|
|||
|
FmpBringQuorumGroupListOnline,
|
|||
|
pNodeGroups,
|
|||
|
0,
|
|||
|
&dwThreadId );
|
|||
|
|
|||
|
if ( hThread == NULL )
|
|||
|
{
|
|||
|
CL_UNEXPECTED_ERROR( GetLastError() );
|
|||
|
OmDereferenceObject( pDefaultTarget );
|
|||
|
goto FnExit;
|
|||
|
}
|
|||
|
|
|||
|
CloseHandle( hThread );
|
|||
|
} else
|
|||
|
{
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpAssignOwnersToGroups - Post work item to worker thread to handle group list containing non-quorum groups....\n"
|
|||
|
);
|
|||
|
FmpPostWorkItem(FM_EVENT_INTERNAL_ONLINE_GROUPLIST, pNodeGroups, 0);
|
|||
|
}
|
|||
|
|
|||
|
OmDereferenceObject(pDefaultTarget);
|
|||
|
|
|||
|
FnExit:
|
|||
|
//
|
|||
|
// Release the global group lock
|
|||
|
//
|
|||
|
FmpReleaseGroupLock();
|
|||
|
|
|||
|
|
|||
|
return(ERROR_SUCCESS);
|
|||
|
}
|
|||
|
|
|||
|
/****
|
|||
|
@func DWORD | FmpResetGroupIntendedOwner| This routine resets the
|
|||
|
intended owner for all groups in the list.
|
|||
|
|
|||
|
@parm IN PGROUP_ENUM | pGroupEnum| The list of Groups.
|
|||
|
|
|||
|
@rdesc Returns ERROR_SUCCESS.
|
|||
|
****/
|
|||
|
VOID
|
|||
|
FmpResetGroupIntendedOwner(
|
|||
|
IN PGROUP_ENUM pGroupEnum
|
|||
|
)
|
|||
|
{
|
|||
|
DWORD i;
|
|||
|
PFM_GROUP pGroup;
|
|||
|
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpResetGroupIntendedOwner: Entry.\n");
|
|||
|
|
|||
|
for ( i = 0; i < pGroupEnum->EntryCount; i++ )
|
|||
|
{
|
|||
|
pGroup = OmReferenceObjectById( ObjectTypeGroup,
|
|||
|
pGroupEnum->Entry[i].Id );
|
|||
|
if ( pGroup == NULL )
|
|||
|
{
|
|||
|
ClRtlLogPrint(LOG_UNUSUAL,
|
|||
|
"[FM] FmpResetGroupIntendedOwner: Group %1!ws! not found\n");
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
pGroup->pIntendedOwner = NULL;
|
|||
|
|
|||
|
OmDereferenceObject( pGroup );
|
|||
|
}
|
|||
|
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpResetGroupIntendedOwner: Exit.\n");
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
/****
|
|||
|
@func DWORD | FmpGetGroupInNodeGroupList | This routine checks whether
|
|||
|
the supplied group is to be included in the list to be brought
|
|||
|
online.
|
|||
|
|
|||
|
@parm OUT PGROUP_ENUM | pReturnEnum | The group list possibly
|
|||
|
containing the supplied group.
|
|||
|
|
|||
|
@parm IN PFM_GROUP | pGroup | The group which is to be brought online
|
|||
|
possibly.
|
|||
|
|
|||
|
@parm IN PNM_NODE | pDeadNode | The node which is dead.
|
|||
|
|
|||
|
@parm OUT PBOOL | pbQuorumGroup | Does the group list contain the quorum group ?
|
|||
|
|
|||
|
@rdesc Returns ERROR_SUCCESS on success OR a Win32 error code on a
|
|||
|
failure.
|
|||
|
****/
|
|||
|
DWORD
|
|||
|
FmpGetGroupInNodeGroupList(
|
|||
|
OUT PGROUP_ENUM *pReturnEnum,
|
|||
|
IN PFM_GROUP pGroup,
|
|||
|
IN PNM_NODE pDeadNode,
|
|||
|
OUT PBOOL pbQuorumGroup
|
|||
|
)
|
|||
|
{
|
|||
|
DWORD dwStatus = ERROR_SUCCESS;
|
|||
|
PGROUP_ENUM pGroupEnum = NULL;
|
|||
|
PFM_RESOURCE pQuoResource = NULL;
|
|||
|
|
|||
|
//
|
|||
|
// Chittur Subbaraman (chitturs) - 6/7/99
|
|||
|
//
|
|||
|
// This function is only called if an RPC exception is raised in
|
|||
|
// FmpTakeGroupRequest. This function will check to see whether this
|
|||
|
// group is to be brought online in this node.
|
|||
|
//
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpGetGroupInNodeGroupList: Entry for group <%1!ws!>\n",
|
|||
|
OmObjectId(pGroup));
|
|||
|
|
|||
|
*pbQuorumGroup = FALSE;
|
|||
|
|
|||
|
pGroupEnum = LocalAlloc( LPTR,
|
|||
|
sizeof( GROUP_ENUM_ENTRY ) + sizeof( GROUP_ENUM ) );
|
|||
|
|
|||
|
if ( pGroupEnum == NULL )
|
|||
|
{
|
|||
|
dwStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|||
|
goto FnExit;
|
|||
|
}
|
|||
|
|
|||
|
pGroupEnum->Entry[0].Id = pGroupEnum->Entry[1].Id = NULL;
|
|||
|
|
|||
|
pGroupEnum->EntryCount = 0;
|
|||
|
|
|||
|
//
|
|||
|
// Check whether this group was in the dead node or was in the
|
|||
|
// process of moving to the dead node.
|
|||
|
//
|
|||
|
if( ( pDeadNode != NULL ) &&
|
|||
|
( pDeadNode != pGroup->OwnerNode ) &&
|
|||
|
( pDeadNode != pGroup->pIntendedOwner ) )
|
|||
|
{
|
|||
|
//
|
|||
|
// This group does not match the owner criteria
|
|||
|
//
|
|||
|
dwStatus = ERROR_GROUP_NOT_AVAILABLE;
|
|||
|
goto FnExit;
|
|||
|
}
|
|||
|
|
|||
|
dwStatus = FmFindQuorumResource( &pQuoResource );
|
|||
|
|
|||
|
if ( dwStatus != ERROR_SUCCESS )
|
|||
|
{
|
|||
|
ClRtlLogPrint(LOG_CRITICAL,
|
|||
|
"[FM] FmpGetGroupInNodeGroupList: Cannot find quorum resource, Status = %1!u!\n",
|
|||
|
dwStatus);
|
|||
|
CsInconsistencyHalt( dwStatus );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Handle the quorum group first, if necessary. This is needed since
|
|||
|
// otherwise you may not be able to bring the other group online.
|
|||
|
//
|
|||
|
if( ( pGroup != pQuoResource->Group ) &&
|
|||
|
( ( pDeadNode == NULL ) ||
|
|||
|
( pDeadNode == pQuoResource->Group->OwnerNode ) ||
|
|||
|
( pDeadNode == pQuoResource->Group->pIntendedOwner ) ) )
|
|||
|
{
|
|||
|
//
|
|||
|
// The quorum group matches the owner criteria. Include it first
|
|||
|
// in the list.
|
|||
|
//
|
|||
|
pGroupEnum->Entry[pGroupEnum->EntryCount].Id =
|
|||
|
LocalAlloc( LMEM_FIXED, ( lstrlenW(OmObjectId(pQuoResource->Group)) + 1 ) * sizeof( WCHAR ) );
|
|||
|
|
|||
|
if ( pGroupEnum->Entry[pGroupEnum->EntryCount].Id == NULL )
|
|||
|
{
|
|||
|
dwStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|||
|
goto FnExit;
|
|||
|
}
|
|||
|
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpGetGroupInNodeGroupList: Dead node contains quorum group also, including it...\n");
|
|||
|
lstrcpyW( pGroupEnum->Entry[pGroupEnum->EntryCount].Id, OmObjectId( pQuoResource->Group ) );
|
|||
|
pGroupEnum->EntryCount++;
|
|||
|
*pbQuorumGroup = TRUE;
|
|||
|
} else if ( pGroup == pQuoResource->Group )
|
|||
|
{
|
|||
|
*pbQuorumGroup = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
pGroupEnum->Entry[pGroupEnum->EntryCount].Id =
|
|||
|
LocalAlloc( LMEM_FIXED, ( lstrlenW(OmObjectId(pGroup)) + 1 ) * sizeof( WCHAR ) );
|
|||
|
|
|||
|
if ( pGroupEnum->Entry[pGroupEnum->EntryCount].Id == NULL )
|
|||
|
{
|
|||
|
dwStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|||
|
goto FnExit;
|
|||
|
}
|
|||
|
|
|||
|
lstrcpyW( pGroupEnum->Entry[pGroupEnum->EntryCount].Id, OmObjectId( pGroup ) );
|
|||
|
|
|||
|
pGroupEnum->EntryCount++;
|
|||
|
|
|||
|
*pReturnEnum = pGroupEnum;
|
|||
|
|
|||
|
OmDereferenceObject( pQuoResource );
|
|||
|
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpGetGroupInNodeGroupList: Exit with SUCCESS.\n");
|
|||
|
|
|||
|
return( ERROR_SUCCESS );
|
|||
|
|
|||
|
FnExit:
|
|||
|
if ( pGroupEnum != NULL )
|
|||
|
{
|
|||
|
FmpDeleteEnum( pGroupEnum );
|
|||
|
}
|
|||
|
|
|||
|
if ( pQuoResource != NULL )
|
|||
|
{
|
|||
|
OmDereferenceObject( pQuoResource );
|
|||
|
}
|
|||
|
|
|||
|
*pReturnEnum = NULL;
|
|||
|
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpGetGroupInNodeGroupList: Exit, Status = %1!u!\n",
|
|||
|
dwStatus);
|
|||
|
|
|||
|
return( dwStatus );
|
|||
|
}
|
|||
|
|
|||
|
/****
|
|||
|
@func VOID | FmpPrepareGroupEnumForOnline | Prepare a list of
|
|||
|
groups for online.
|
|||
|
|
|||
|
@parm IN PGROUP_ENUM | pGroupEnum | The group list.
|
|||
|
|
|||
|
@rdesc None.
|
|||
|
****/
|
|||
|
VOID
|
|||
|
FmpPrepareGroupEnumForOnline(
|
|||
|
IN PGROUP_ENUM pGroupEnum
|
|||
|
)
|
|||
|
{
|
|||
|
PFM_GROUP pGroup = NULL;
|
|||
|
DWORD i;
|
|||
|
|
|||
|
//
|
|||
|
// Chittur Subbaraman (chitturs) - 6/21/99
|
|||
|
//
|
|||
|
// Prepare an entire group list for online.
|
|||
|
//
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpPrepareGroupEnumForOnline - Entry...\n");
|
|||
|
|
|||
|
for ( i=0; i<pGroupEnum->EntryCount; i++ )
|
|||
|
{
|
|||
|
pGroup = OmReferenceObjectById( ObjectTypeGroup,
|
|||
|
pGroupEnum->Entry[i].Id );
|
|||
|
|
|||
|
//
|
|||
|
// If we fail to find a group, then just continue.
|
|||
|
//
|
|||
|
if ( pGroup == NULL )
|
|||
|
{
|
|||
|
ClRtlLogPrint(LOG_UNUSUAL,
|
|||
|
"[FM] FmpPrepareGroupEnumForOnline - Group %1!ws! cannot be found !\n",
|
|||
|
pGroupEnum->Entry[i].Id);
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpPrepareGroupEnumForOnline - Preparing group <%1!ws!> for online...\n",
|
|||
|
pGroupEnum->Entry[i].Id);
|
|||
|
|
|||
|
FmpPrepareGroupForOnline( pGroup );
|
|||
|
}
|
|||
|
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpPrepareGroupEnumForOnline - Exit...\n");
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
/****
|
|||
|
@func DWORD | FmpBringQuorumGroupListOnline | Bring a list of groups
|
|||
|
containing the quorum group online.
|
|||
|
|
|||
|
@parm IN LPVOID | pContext | A pointer to the group list to be brought
|
|||
|
online.
|
|||
|
|
|||
|
@rdesc Returns ERROR_SUCCESS.
|
|||
|
****/
|
|||
|
DWORD
|
|||
|
FmpBringQuorumGroupListOnline(
|
|||
|
IN LPVOID pContext
|
|||
|
)
|
|||
|
{
|
|||
|
PGROUP_ENUM pGroupList = NULL;
|
|||
|
|
|||
|
//
|
|||
|
// Chittur Subbaraman (chitturs) - 7/14/99
|
|||
|
//
|
|||
|
// This function tries to bring a list of groups containing the quorum
|
|||
|
// group online. Note that if the group's owner turns out to be some
|
|||
|
// other node, this function will not online the group.
|
|||
|
//
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpBringQuorumGroupListOnline - Entry: Trying to online group list containing quorum group....\n"
|
|||
|
);
|
|||
|
|
|||
|
pGroupList = pContext;
|
|||
|
|
|||
|
CL_ASSERT( pGroupList != NULL );
|
|||
|
|
|||
|
FmpOnlineGroupList( pGroupList, TRUE );
|
|||
|
|
|||
|
FmpDeleteEnum( pGroupList );
|
|||
|
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpBringQuorumGroupListOnline - Exit ....\n"
|
|||
|
);
|
|||
|
|
|||
|
return( ERROR_SUCCESS );
|
|||
|
}
|
|||
|
|
|||
|
/****
|
|||
|
@func BOOL | FmpIsAnyResourcePersistentStateOnline | Is the persistent state of any
|
|||
|
resource in the group online ?
|
|||
|
|
|||
|
@parm IN PFM_GROUP | pGroup | The group which is to be checked.
|
|||
|
|
|||
|
@rdesc TRUE if at least one resource's persistent state is ClusterResourceOnline, FALSE otherwise.
|
|||
|
****/
|
|||
|
BOOL
|
|||
|
FmpIsAnyResourcePersistentStateOnline(
|
|||
|
IN PFM_GROUP pGroup
|
|||
|
)
|
|||
|
{
|
|||
|
PFM_RESOURCE pResource;
|
|||
|
PLIST_ENTRY pListEntry;
|
|||
|
|
|||
|
if ( CsNoQuorum ) return FALSE;
|
|||
|
|
|||
|
for ( pListEntry = pGroup->Contains.Flink;
|
|||
|
pListEntry != &( pGroup->Contains );
|
|||
|
pListEntry = pListEntry->Flink )
|
|||
|
{
|
|||
|
pResource = CONTAINING_RECORD( pListEntry,
|
|||
|
FM_RESOURCE,
|
|||
|
ContainsLinkage );
|
|||
|
|
|||
|
if ( pResource->PersistentState == ClusterResourceOnline )
|
|||
|
{
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpIsAnyResourcePersistentStateOnline: Persistent state of resource %1!ws! in group %2!ws! is online...\r\n",
|
|||
|
OmObjectId(pResource),
|
|||
|
OmObjectId(pGroup));
|
|||
|
return ( TRUE );
|
|||
|
}
|
|||
|
} // for
|
|||
|
|
|||
|
ClRtlLogPrint(LOG_NOISE,
|
|||
|
"[FM] FmpIsAnyResourcePersistentStateOnline: No resource in group %1!ws! has persistent state online...\r\n",
|
|||
|
OmObjectId(pGroup));
|
|||
|
|
|||
|
return( FALSE );
|
|||
|
} // FmpIsAnyResourcePersistentStateOnline
|
|||
|
|
|||
|
|
|||
|
|