windows-nt/Source/XPSP1/NT/base/cluster/service/nm/node.c

4575 lines
110 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1996-1999 Microsoft Corporation
Module Name:
node.c
Abstract:
Private Node Manager routines.
Author:
Mike Massa (mikemas) 12-Mar-1996
Revision History:
--*/
#define UNICODE 1
#include "nmp.h"
/////////////////////////////////////////////////////////////////////////////
//
// Data
//
/////////////////////////////////////////////////////////////////////////////
ULONG NmMaxNodes = ClusterInvalidNodeId;
CL_NODE_ID NmMaxNodeId = ClusterInvalidNodeId;
CL_NODE_ID NmLocalNodeId = ClusterInvalidNodeId;
PNM_NODE NmLocalNode = NULL;
WCHAR NmLocalNodeIdString[CS_MAX_NODE_ID_LENGTH+1];
WCHAR NmLocalNodeName[CS_MAX_NODE_NAME_LENGTH+1];
LIST_ENTRY NmpNodeList = {NULL, NULL};
PNM_NODE * NmpIdArray = NULL;
DWORD NmpNodeCount = 0;
BOOL NmpLastNodeEvicted = FALSE;
BOOL NmLocalNodeVersionChanged = FALSE;
LIST_ENTRY * NmpIntraClusterRpcArr=NULL;
CRITICAL_SECTION NmpRPCLock;
#if DBG
DWORD NmpRpcTimer=0;
#endif // DBG
///////////////////////////////////////////////////////////////////////////
//
// Initialization/Cleanup Routines
//
///////////////////////////////////////////////////////////////////////////
VOID
NmpCleanupNodes(
VOID
)
{
PNM_NODE node;
PLIST_ENTRY entry, nextEntry;
DWORD status;
ClRtlLogPrint(LOG_NOISE,"[NM] Node cleanup starting...\n");
NmpAcquireLock();
while (!IsListEmpty(&NmpNodeList)) {
entry = NmpNodeList.Flink;
node = CONTAINING_RECORD(entry, NM_NODE, Linkage);
if (node == NmLocalNode) {
entry = node->Linkage.Flink;
if (entry == &NmpNodeList) {
break;
}
node = CONTAINING_RECORD(entry, NM_NODE, Linkage);
}
CL_ASSERT(NM_OM_INSERTED(node));
CL_ASSERT(!NM_DELETE_PENDING(node));
NmpDeleteNodeObject(node, FALSE);
}
NmpReleaseLock();
ClRtlLogPrint(LOG_NOISE,"[NM] Node cleanup complete\n");
return;
} // NmpCleanupNodes
/////////////////////////////////////////////////////////////////////////////
//
// Remote procedures called by joining nodes or on behalf of joining nodes.
//
/////////////////////////////////////////////////////////////////////////////
error_status_t
s_NmRpcEnumNodeDefinitions(
IN handle_t IDL_handle,
IN DWORD JoinSequence, OPTIONAL
IN LPWSTR JoinerNodeId, OPTIONAL
OUT PNM_NODE_ENUM * NodeEnum1
)
{
DWORD status = ERROR_CLUSTER_INCOMPATIBLE_VERSIONS;
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] Refusing node info to joining node nodeid=%1!ws!. Aborting join, obsolete interface.\n",
JoinerNodeId
);
return(status);
} // s_NmRpcEnumNodeDefinitions
error_status_t
s_NmRpcEnumNodeDefinitions2(
IN handle_t IDL_handle,
IN DWORD JoinSequence, OPTIONAL
IN LPWSTR JoinerNodeId, OPTIONAL
OUT PNM_NODE_ENUM2 * NodeEnum
)
{
DWORD status = ERROR_SUCCESS;
PNM_NODE joinerNode = NULL;
NmpAcquireLock();
if (NmpLockedEnterApi(NmStateOnline)) {
ClRtlLogPrint(LOG_NOISE,
"[NMJOIN] Supplying node information to joining node.\n"
);
if (lstrcmpW(JoinerNodeId, NmpInvalidJoinerIdString) != 0) {
joinerNode = OmReferenceObjectById(
ObjectTypeNode,
JoinerNodeId
);
if (joinerNode != NULL) {
if ( (JoinSequence == NmpJoinSequence) &&
(NmpJoinerNodeId == joinerNode->NodeId) &&
(NmpSponsorNodeId == NmLocalNodeId) &&
!NmpJoinAbortPending
)
{
CL_ASSERT(joinerNode->State == ClusterNodeJoining);
CL_ASSERT(NmpJoinerUp == FALSE);
CL_ASSERT(NmpJoinTimer != 0);
//
// Suspend the join timer while we are working on
// behalf of the joiner. This precludes an abort
// from occuring as well.
//
NmpJoinTimer = 0;
}
else {
status = ERROR_CLUSTER_JOIN_ABORTED;
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] EnumNodeDefinitions call for joining node %1!ws! failed because the join was aborted.\n",
JoinerNodeId
);
}
}
else {
status = ERROR_CLUSTER_NODE_NOT_MEMBER;
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] EnumNodeDefinitions call for joining node %1!ws! failed because the node is not a member of the cluster.\n",
JoinerNodeId
);
}
}
if (status == ERROR_SUCCESS) {
status = NmpEnumNodeObjects(NodeEnum);
if (joinerNode != NULL) {
if (status == ERROR_SUCCESS) {
//
// Restart the join timer.
//
NmpJoinTimer = NM_JOIN_TIMEOUT;
}
else {
ClRtlLogPrint(LOG_CRITICAL,
"[NMJOIN] EnumNodeDefinitions failed, status %1!u!.\n",
status
);
//
// Abort the join
//
NmpJoinAbort(status, joinerNode);
}
}
}
if (joinerNode != NULL) {
OmDereferenceObject(joinerNode);
}
NmpLockedLeaveApi();
}
else {
status = ERROR_NODE_NOT_AVAILABLE;
ClRtlLogPrint(LOG_NOISE,
"[NM] Not in valid state to process EnumNodeDefinitions request.\n"
);
}
NmpReleaseLock();
return(status);
} // s_NmRpcEnumNodeDefinitions2
error_status_t
s_NmRpcAddNode(
IN handle_t IDL_handle,
IN LPCWSTR NewNodeName,
IN DWORD NewNodeHighestVersion,
IN DWORD NewNodeLowestVersion,
IN DWORD NewNodeProductSuite
)
/*++
Routine Description:
Adds a new node to the cluster by selecting an ID and
issuing a global update.
Arguments:
IDL_handle - RPC client interface handle.
NewNodeName - A pointer to a string containing the name of the
new node.
NewNodeHighestVersion - The highest cluster version number that the
new node can support.
NewNodeLowestVersion - The lowest cluster version number that the
new node can support.
NewNodeProductSuite - The product suite identifier for the new node.
Return Value:
A Win32 status code.
Notes:
Called with NmpLock held.
--*/
{
DWORD status;
DWORD registryNodeLimit;
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] Received forwarded request to add node '%1!ws!' to the "
"cluster.\n",
NewNodeName
);
//
// Read the registry override before acquiring the NM lock.
//
status = DmQueryDword(
DmClusterParametersKey,
CLUSREG_NAME_MAX_NODES,
&registryNodeLimit,
NULL
);
if (status != ERROR_SUCCESS) {
registryNodeLimit = 0;
}
NmpAcquireLock();
if (!NmpLockedEnterApi(NmStateOnline)) {
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] This node is not in a valid state to process a "
"request to add node '%1!ws!' to the cluster.\n",
NewNodeName
);
NmpReleaseLock();
return(ERROR_NODE_NOT_AVAILABLE);
}
if (NmpLeaderNodeId == NmLocalNodeId) {\
//
// Call the internal handler.
//
status = NmpAddNode(
NewNodeName,
NewNodeHighestVersion,
NewNodeLowestVersion,
NewNodeProductSuite,
registryNodeLimit
);
}
else {
//
// This node is not the leader.
// Fail the request.
//
status = ERROR_NODE_NOT_AVAILABLE;
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] Cannot process request to add node '%1!ws!' to the "
"cluster because this node is not the leader.\n",
NewNodeName
);
}
NmpLockedLeaveApi();
NmpReleaseLock();
return(status);
} // s_NmRpcAddNode
/////////////////////////////////////////////////////////////////////////////
//
// Routines called by other cluster service components
//
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
//
// Rpc Extended error tracking.
//
/////////////////////////////////////////////////////////////////////////////
VOID NmDumpRpcExtErrorInfo(RPC_STATUS status)
{
RPC_STATUS status2;
RPC_ERROR_ENUM_HANDLE enumHandle;
status2 = RpcErrorStartEnumeration(&enumHandle);
if(status2 == RPC_S_ENTRY_NOT_FOUND) {
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] RpcExtErrorInfo: Error info not found.\n"
);
}
else if(status2 != RPC_S_OK) {
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] RpcExtErrorInfo: Couldn't get error info, status %1!u!\n",
status2
);
}
else {
RPC_EXTENDED_ERROR_INFO errorInfo;
int records;
BOOL result;
BOOL copyStrings=TRUE;
BOOL fUseFileTime=TRUE;
SYSTEMTIME *systemTimeToUse;
SYSTEMTIME systemTimeBuffer;
while(status2 == RPC_S_OK) {
errorInfo.Version = RPC_EEINFO_VERSION;
errorInfo.Flags = 0;
errorInfo.NumberOfParameters = 4;
if(fUseFileTime) {
errorInfo.Flags |= EEInfoUseFileTime;
}
status2 = RpcErrorGetNextRecord(&enumHandle, copyStrings, &errorInfo);
if(status2 == RPC_S_ENTRY_NOT_FOUND) {
break;
}
else if(status2 != RPC_S_OK) {
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] RpcExtErrorInfo: Couldn't complete enumeration, status %1!u!\n",
status2
);
break;
}
else {
int i;
if(errorInfo.ComputerName) {
ClRtlLogPrint(LOG_NOISE,
"[NM] RpcExtErrorInfo: ComputerName= %1!ws!\n",
errorInfo.ComputerName
);
}
if(copyStrings) {
result = HeapFree(GetProcessHeap(), 0, errorInfo.ComputerName);
CL_ASSERT(result);
}
ClRtlLogPrint(LOG_NOISE,
"[NM] RpcExtErrorInfo: ProcessId= %1!u!\n",
errorInfo.ProcessID
);
if(fUseFileTime) {
result = FileTimeToSystemTime(&errorInfo.u.FileTime, &systemTimeBuffer);
CL_ASSERT(result);
systemTimeToUse = &systemTimeBuffer;
}
else {
systemTimeToUse = &errorInfo.u.SystemTime;
}
ClRtlLogPrint(LOG_NOISE,
"[NM] RpcExtErrorInfo: SystemTime= %1!u!/%2!u!/%3!u! %4!u!:%5!u!:%6!u!:%7!u!\n",
systemTimeToUse->wMonth,
systemTimeToUse->wDay,
systemTimeToUse->wYear,
systemTimeToUse->wHour,
systemTimeToUse->wMinute,
systemTimeToUse->wSecond,
systemTimeToUse->wMilliseconds
);
ClRtlLogPrint(LOG_NOISE,
"[NM] RpcExtErrorInfo: GeneratingComponent= %1!u!\n",
errorInfo.GeneratingComponent
);
ClRtlLogPrint(LOG_NOISE,
"[NM] RpcExtErrorInfo: Status= 0x%1!x!\n",
errorInfo.Status
);
ClRtlLogPrint(LOG_NOISE,
"[NM] RpcExtErrorInfo: Detection Location= %1!u!\n",
(DWORD)errorInfo.DetectionLocation
);
ClRtlLogPrint(LOG_NOISE,
"[NM] RpcExtErrorInfo: Flags= 0x%1!x!\n",
errorInfo.Flags
);
ClRtlLogPrint(LOG_NOISE,
"[NM] RpcExtErrorInfo: Number of Parameters= %1!u!\n",
errorInfo.NumberOfParameters
);
for(i=0;i<errorInfo.NumberOfParameters;i++) {
switch(errorInfo.Parameters[i].ParameterType) {
case eeptAnsiString:
ClRtlLogPrint(LOG_NOISE,
"[NM] RpcExtErrorInfo: Ansi String= %1!s!\n",
errorInfo.Parameters[i].u.AnsiString
);
if(copyStrings) {
result = HeapFree(GetProcessHeap(), 0, errorInfo.Parameters[i].u.AnsiString);
CL_ASSERT(result);
}
break;
case eeptUnicodeString:
ClRtlLogPrint(LOG_NOISE,
"[NM] RpcExtErrorInfo: Unicode String= %1!S!\n",
errorInfo.Parameters[i].u.UnicodeString
);
if(copyStrings) {
result = HeapFree(GetProcessHeap(), 0, errorInfo.Parameters[i].u.UnicodeString);
CL_ASSERT(result);
}
break;
case eeptLongVal:
ClRtlLogPrint(LOG_NOISE,
"[NM] RpcExtErrorInfo: Long Val= %1!u!\n",
errorInfo.Parameters[i].u.LVal
);
break;
case eeptShortVal:
ClRtlLogPrint(LOG_NOISE,
"[NM] RpcExtErrorInfo: Short Val= %1!u!\n",
(DWORD)errorInfo.Parameters[i].u.SVal
);
break;
case eeptPointerVal:
ClRtlLogPrint(LOG_NOISE,
"[NM] RpcExtErrorInfo: Pointer Val= 0x%1!u!\n",
errorInfo.Parameters[i].u.PVal
);
break;
case eeptNone:
ClRtlLogPrint(LOG_NOISE,
"[NM] RpcExtErrorInfo: Truncated\n"
);
break;
default:
ClRtlLogPrint(LOG_NOISE,
"[NM] RpcExtErrorInfo: Invalid Type %1!u!\n",
errorInfo.Parameters[i].ParameterType
);
}
}
}
}
RpcErrorEndEnumeration(&enumHandle);
}
} //NmDumpRpcExtErrorInfo
///////////////////////////////////////////////////////////////////////////
//
// RPC Monitoring Routines
//
///////////////////////////////////////////////////////////////////////////
VOID
NmStartRpc(
DWORD NodeId
)
/*++
Routine Description:
Registers the fact that an RPC is about to be made to the specified
node by the current thread. This allows the call to be cancelled if
the target node dies.
Arguments:
NodeId - The ID of the node about to be called.
Return Value:
None
Notes:
This routine must not be called by a thread that makes concurrent
asynch RPC calls.
--*/
{
HANDLE thHandle;
PNM_INTRACLUSTER_RPC_THREAD entry;
CL_ASSERT((NodeId >= ClusterMinNodeId) && (NodeId <= NmMaxNodeId));
CL_ASSERT(NmpIntraClusterRpcArr != NULL);
thHandle = OpenThread(
THREAD_ALL_ACCESS,
FALSE,
GetCurrentThreadId()
);
if(thHandle == NULL) {
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] NmStartRpc: Failed to open handle to current thread.\n"
);
return;
}
entry = LocalAlloc(LMEM_FIXED, sizeof(NM_INTRACLUSTER_RPC_THREAD));
if(entry == NULL) {
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] NmStartRpc: Failed to allocate memory.\n"
);
CloseHandle(thHandle);
return;
}
entry->ThreadId = GetCurrentThreadId();
entry->Thread = thHandle;
entry->Cancelled = FALSE;
NmpAcquireRPCLock();
#if DBG
ClRtlLogPrint(LOG_NOISE,
"[NM] Starting RPC to node %1!u!\n",
NodeId
);
#endif
InsertHeadList(&NmpIntraClusterRpcArr[NodeId], &entry->Linkage);
NmpReleaseRPCLock();
return;
} // NmStartRpc
VOID
NmEndRpc(
DWORD NodeId
)
/*++
Routine Description:
Cancels registration of an RPC to the specified node by the current
thread.
Arguments:
NodeId - The ID of the node that was called.
Return Value:
None
Notes:
This routine must be invoked even if the RPC was cancelled.
--*/
{
DWORD threadId;
LIST_ENTRY *pEntry;
PNM_INTRACLUSTER_RPC_THREAD pRpcTh;
CL_ASSERT((NodeId >= ClusterMinNodeId) && (NodeId <= NmMaxNodeId));
CL_ASSERT(NmpIntraClusterRpcArr != NULL);
threadId = GetCurrentThreadId();
NmpAcquireRPCLock();
pEntry = NmpIntraClusterRpcArr[NodeId].Flink;
while(pEntry != &NmpIntraClusterRpcArr[NodeId]) {
pRpcTh = CONTAINING_RECORD(pEntry, NM_INTRACLUSTER_RPC_THREAD, Linkage);
if(pRpcTh->ThreadId == threadId) {
#if DBG
ClRtlLogPrint(LOG_NOISE,
"[NM] Finished RPC to node %1!u!\n",
NodeId
);
#endif
if (pRpcTh->Cancelled) {
ClRtlLogPrint(LOG_NOISE,
"[NM] RPC by this thread to node %1!u! is cancelled\n",
NodeId
);
}
RemoveEntryList(pEntry);
CloseHandle(pRpcTh->Thread);
LocalFree(pRpcTh);
NmpReleaseRPCLock();
return;
}
pEntry = pEntry->Flink;
}
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] No record of RPC by this thread to node %1!u!.\n",
NodeId
);
#if DBG
CL_ASSERT(pEntry != &NmpIntraClusterRpcArr[NodeId]);
#endif
NmpReleaseRPCLock();
return;
} // NmEndRpc
DWORD
NmPauseNode(
IN PNM_NODE Node
)
/*++
Routine Description:
Arguments:
Return Value:
--*/
{
LPCWSTR nodeId = OmObjectId(Node);
DWORD status;
ClRtlLogPrint(LOG_NOISE,
"[NM] Received request to pause node %1!ws!.\n",
nodeId
);
if (NmpEnterApi(NmStateOnline)) {
status = GumSendUpdateEx(
GumUpdateMembership,
NmUpdatePauseNode,
1,
(lstrlenW(nodeId)+1)*sizeof(WCHAR),
nodeId
);
if (status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Global update to pause node %1!ws! failed, status %2!u!\n",
nodeId,
status
);
}
NmpLeaveApi();
}
else {
status = ERROR_NODE_NOT_AVAILABLE;
ClRtlLogPrint(LOG_NOISE,
"[NM] Not in valid state to process PauseNode request.\n"
);
}
return(status);
} // NmPauseNode
DWORD
NmResumeNode(
IN PNM_NODE Node
)
/*++
Routine Description:
Arguments:
Return Value:
--*/
{
LPCWSTR nodeId = OmObjectId(Node);
DWORD status;
ClRtlLogPrint(LOG_NOISE,
"[NM] Received request to resume node %1!ws!.\n",
nodeId
);
if (NmpEnterApi(NmStateOnline)) {
status = GumSendUpdateEx(
GumUpdateMembership,
NmUpdateResumeNode,
1,
(lstrlenW(nodeId)+1)*sizeof(WCHAR),
nodeId
);
if (status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Global update to resume node %1!ws! failed, status %2!u!\n",
nodeId,
status
);
}
NmpLeaveApi();
}
else {
status = ERROR_NODE_NOT_AVAILABLE;
ClRtlLogPrint(LOG_NOISE,
"[NM] Not in valid state to process ResumeNode request.\n"
);
}
return(status);
} // NmResumeNode
DWORD
NmEvictNode(
IN PNM_NODE Node
)
/*++
Routine Description:
Arguments:
Return Value:
Notes:
The caller must be holding a reference on the node object.
--*/
{
LPCWSTR nodeId = OmObjectId(Node);
DWORD status = ERROR_SUCCESS;
LPCWSTR pcszNodeName = NULL;
ClRtlLogPrint(LOG_NOISE,
"[NM] Received request to evict node %1!ws!.\n",
nodeId
);
if (NmpEnterApi(NmStateOnline)) {
// Acquire NM lock (to ensure that the number of nodes does not change)
NmpAcquireLock();
if (NmpNodeCount != 1 ) {
NmpReleaseLock();
// We are not evicting the last node.
status = GumSendUpdateEx(
GumUpdateMembership,
NmUpdateEvictNode,
1,
(lstrlenW(nodeId)+1)*sizeof(WCHAR),
nodeId
);
if ( status != ERROR_SUCCESS ) {
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Global update to evict node %1!ws! failed, status %2!u!\n",
nodeId,
status
);
}
pcszNodeName = OmObjectName(Node);
}
else {
// We are evicting the last node. Set a flag to indicate this fact.
if ( NmpLastNodeEvicted == FALSE ) {
NmpLastNodeEvicted = TRUE;
}
else {
// We have already evicted this node. This is an error.
status = ERROR_NODE_NOT_AVAILABLE;
ClRtlLogPrint(LOG_NOISE,
"[NM] Not in valid state to process EvictNode request.\n"
);
}
NmpReleaseLock();
}
if (status == ERROR_SUCCESS) {
HRESULT cleanupStatus;
// The node was successfully evicted. Now initiate cleanup on that node.
// However, specify that cleanup is to be started only after 60000 ms (1 minute).
cleanupStatus =
ClRtlCleanupNode(
pcszNodeName, // Name of the node to be cleaned up
60000, // Amount of time (in milliseconds) to wait before starting cleanup
0 // timeout interval in milliseconds
);
if ( FAILED( cleanupStatus ) && ( cleanupStatus != RPC_S_CALLPENDING ) ){
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] Failed to initiate cleanup of evicted node %1!ws!, status 0x%2!x!\n",
nodeId,
cleanupStatus
);
status = cleanupStatus;
}
else {
ClRtlLogPrint(LOG_NOISE,
"[NM] Cleanup of evicted node %1!ws! successfully initiated.\n",
nodeId
);
CsLogEvent1(LOG_UNUSUAL, NM_NODE_EVICTED, OmObjectName(Node));
}
}
NmpLeaveApi();
}
else {
status = ERROR_NODE_NOT_AVAILABLE;
ClRtlLogPrint(LOG_NOISE,
"[NM] Not in valid state to process EvictNode request.\n"
);
}
return(status);
} // NmEvictNode
PNM_NODE
NmReferenceNodeById(
IN DWORD NodeId
)
/*++
Routine Description:
Given a node id, returns a referenced pointer to the node object.
The caller is responsible for calling OmDereferenceObject.
Arguments:
NodeId - Supplies the node id
Return Value:
A pointer to the node object if it exists
NULL if there is no such node.
--*/
{
PNM_NODE Node = NULL;
NmpAcquireLock();
if (NmpLockedEnterApi(NmStateOnlinePending)) {
CL_ASSERT(NmIsValidNodeId(NodeId));
CL_ASSERT(NmpIdArray != NULL);
Node = NmpIdArray[NodeId];
if (NmpIdArray[NodeId] != NULL) {
OmReferenceObject(Node);
}
else {
SetLastError(ERROR_CLUSTER_NODE_NOT_FOUND);
}
NmpLockedLeaveApi();
}
else {
SetLastError(ERROR_NODE_NOT_AVAILABLE);
ClRtlLogPrint(LOG_NOISE,
"[NM] Not in valid state to process ReferenceNodeById request.\n"
);
}
NmpReleaseLock();
return(Node);
} // NmReferenceNodeById
PNM_NODE
NmReferenceJoinerNode(
IN DWORD JoinSequence,
IN CL_NODE_ID JoinerNodeId
)
/*++
Routine Description:
Given a node id, returns a referenced pointer to the node object.
The caller is responsible for calling OmDereferenceObject.
Also validates the joiner's information
Arguments:
NodeId - Supplies the node id
Return Value:
A pointer to the node object if it exists
NULL if there is no such node.
Notes:
If the routine is successful, the caller must dereference the
node object by calling NmDereferenceJoiningNode.
--*/
{
PNM_NODE joinerNode = NULL;
DWORD status;
NmpAcquireLock();
if (NmpLockedEnterApi(NmStateOnline)) {
joinerNode = NmpIdArray[JoinerNodeId];
if (joinerNode != NULL) {
if ( (JoinSequence == NmpJoinSequence) &&
(NmpJoinerNodeId == JoinerNodeId)
)
{
OmReferenceObject(joinerNode);
NmpReleaseLock();
//
// Return holding an active thread reference.
//
return(joinerNode);
}
else {
status = ERROR_CLUSTER_JOIN_ABORTED;
}
}
else {
status = ERROR_CLUSTER_NODE_NOT_MEMBER;
}
NmpLockedLeaveApi();
}
else {
status = ERROR_NODE_NOT_AVAILABLE;
}
NmpReleaseLock();
if (status != ERROR_SUCCESS) {
SetLastError(status);
}
return(joinerNode);
} // NmReferenceJoinerNode
VOID
NmDereferenceJoinerNode(
PNM_NODE JoinerNode
)
{
OmDereferenceObject(JoinerNode);
NmpLeaveApi();
return;
} // NmDereferenceJoinerNode
CLUSTER_NODE_STATE
NmGetNodeState(
IN PNM_NODE Node
)
/*++
Routine Description:
Arguments:
Return Value:
Notes:
Because the caller must have a reference on the node object and the
call is so simple, there is no reason to put the call through the
EnterApi/LeaveApi dance.
--*/
{
CLUSTER_NODE_STATE state;
NmpAcquireLock();
state = Node->State;
NmpReleaseLock();
return(state);
} // NmGetNodeState
CLUSTER_NODE_STATE
NmGetExtendedNodeState(
IN PNM_NODE Node
)
/*++
Routine Description:
Arguments:
Return Value:
Notes:
Because the caller must have a reference on the node object and the
call is so simple, there is no reason to put the call through the
EnterApi/LeaveApi dance.
--*/
{
CLUSTER_NODE_STATE state;
NmpAcquireLock();
state = Node->State;
if(NM_NODE_UP(Node) ) {
//
// We need to check whether the node is really up
//
switch( Node->ExtendedState ) {
case ClusterNodeUp:
//
// The node explicitly set its extended state to UP immediately after
// ClusterJoin / ClusterForm was complete.
// We need to return either Up or Paused, depending on the node state
//
state = Node->State;
break;
case ClusterNodeDown:
//
// The node explicitly set its extended state to DOWN in the beginning of
// the shutdown process. We will report the node state as down.
//
// It is better to have ClusterNodeShuttindDown state for this situation.
//
// state = ClusterNodeDown;
// We do not want to return NodeDown, we really want NodeShuttingDown.
//
// Return UP or Paused
//
state = Node->State;
break;
default:
//
// Node is up from NM standpoint, but other components are not up yet.
//
state = ClusterNodeJoining;
}
}
NmpReleaseLock();
return(state);
} // NmGetExtendedNodeState
DWORD NmpUpdateExtendedNodeState(
IN BOOL SourceNode,
IN LPWSTR NodeId,
IN CLUSTER_NODE_STATE* ExtendedState
)
{
DWORD status = ERROR_SUCCESS;
NmpAcquireLock();
ClRtlLogPrint(LOG_NOISE,
"[NM] Received update to set extended state for node %1!ws! "
"to %2!d!\n",
NodeId,
*ExtendedState
);
if (NmpLockedEnterApi(NmStateOnline)) {
PNM_NODE node = OmReferenceObjectById(ObjectTypeNode, NodeId);
if (node != NULL) {
//
// Extended State is valid only when the node is online.
// Ignore the update otherwise.
//
if ( NM_NODE_UP(node) ) {
CLUSTER_EVENT event;
node->ExtendedState = *ExtendedState;
if (*ExtendedState == ClusterNodeUp) {
event = CLUSTER_EVENT_API_NODE_UP;
} else {
event = CLUSTER_EVENT_API_NODE_SHUTTINGDOWN;
}
ClRtlLogPrint(LOG_NOISE,
"[NM] Issuing event %1!x!.\n",
event
);
ClusterEvent(event, node);
}
OmDereferenceObject(node);
}
else {
ClRtlLogPrint(LOG_NOISE,
"[NM] Node %1!ws! is not a cluster member. Rejecting request "
"to set the node's extended state.\n",
NodeId
);
status = ERROR_NODE_NOT_AVAILABLE;
}
NmpLockedLeaveApi();
} else {
ClRtlLogPrint(LOG_NOISE,
"[NM] Not in a valid state to process request to set extended "
"state for node %1!ws!\n",
NodeId
);
status = ERROR_CLUSTER_NODE_NOT_READY;
}
NmpReleaseLock();
return status;
} // NmpUpdateExtendedNodeState
DWORD
NmSetExtendedNodeState(
IN CLUSTER_NODE_STATE State
)
{
DWORD Status;
Status = GumSendUpdateEx(
GumUpdateMembership,
NmUpdateExtendedNodeState,
2,
sizeof(NmLocalNodeIdString),
&NmLocalNodeIdString,
sizeof(CLUSTER_NODE_STATE),
&State
);
if (Status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_UNUSUAL,
"[INIT] NmUpdateExtendedNodeState node failed, status %1!d!.\n", Status);
}
return Status;
} // NmSetExtendedNodeState
DWORD
NmGetNodeId(
IN PNM_NODE Node
)
/*++
Routine Description:
Returns the given node's node ID.
Arguments:
Node - Supplies a pointer to a node object.
Return Value:
The node's node id.
Notes:
Because the caller must have a reference on the node object and the
call is so simple, there is no reason to put the call through the
EnterApi/LeaveApi dance.
--*/
{
DWORD nodeId;
//
// Since the caller has a reference on the object, and the node ID can't
// be changed, it is safe to do this without taking a lock. It is also
// necessary to prevent some deadlocks.
//
nodeId = Node->NodeId;
return(nodeId);
} // NmGetNodeId
DWORD
NmGetCurrentNumberOfNodes()
{
DWORD dwCnt = 0;
PLIST_ENTRY pListEntry;
NmpAcquireLock();
for ( pListEntry = NmpNodeList.Flink;
pListEntry != &NmpNodeList;
pListEntry = pListEntry->Flink )
{
dwCnt++;
}
NmpReleaseLock();
return(dwCnt);
}
DWORD
NmGetMaxNodeId(
)
/*++
Routine Description:
Returns the max node's node ID.
Arguments:
Node - Supplies a pointer to a node object.
Return Value:
The node's node id.
Notes:
Because the caller must have a reference on the node object and the
call is so simple, there is no reason to put the call through the
EnterApi/LeaveApi dance.
--*/
{
return(NmMaxNodeId);
} // NmGetMaxNodeId
VOID
NmpAdviseNodeFailure(
IN PNM_NODE Node,
IN DWORD ErrorCode
)
/*++
Routine Description:
Reports that a communication failure to the specified node has occurred.
A poison packet will be sent to the failed node and regroup initiated.
Arguments:
Node - Supplies a pointer to the node object for the failed node.
ErrorCode - Supplies the error code that was returned from RPC
Return Value:
None
Notes:
Called with NM lock held.
--*/
{
ClRtlLogPrint(LOG_NOISE,
"[NM] Received advice that node %1!u! has failed with "
"error %2!u!.\n",
Node->NodeId,
ErrorCode
);
if (Node->State != ClusterNodeDown) {
LPCWSTR nodeName = OmObjectName(Node);
DWORD status;
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Banishing node %1!u! from active cluster membership.\n",
Node->NodeId
);
OmReferenceObject(Node);
NmpReleaseLock();
status = MMEject(Node->NodeId);
if (status == MM_OK) {
CsLogEvent1(
LOG_UNUSUAL,
NM_EVENT_NODE_BANISHED,
nodeName
);
}
OmDereferenceObject(Node);
NmpAcquireLock();
}
return;
} // NmpAdviseNodeFailure
VOID
NmAdviseNodeFailure(
IN DWORD NodeId,
IN DWORD ErrorCode
)
/*++
Routine Description:
Reports that a communication failure to the specified node has occurred.
A poison packet will be sent to the failed node and regroup initiated.
Arguments:
NodeId - Supplies the node id of the failed node.
ErrorCode - Supplies the error code that was returned from RPC
Return Value:
None
--*/
{
NmpAcquireLock();
ClRtlLogPrint(LOG_NOISE,
"[NM] Received advice that node %1!u! has failed with error %2!u!.\n",
NodeId,
ErrorCode
);
if (NmpLockedEnterApi(NmStateOnline)) {
PNM_NODE node;
CL_ASSERT(NodeId != NmLocalNodeId);
CL_ASSERT(NmpIdArray != NULL);
node = NmpIdArray[NodeId];
NmpAdviseNodeFailure(node, ErrorCode);
NmpLockedLeaveApi();
}
else {
ClRtlLogPrint(LOG_NOISE,
"[NM] Not in valid state to process AdviseNodeFailure request.\n"
);
}
NmpReleaseLock();
return;
} // NmAdviseNodeFailure
DWORD
NmEnumNodeInterfaces(
IN PNM_NODE Node,
OUT LPDWORD InterfaceCount,
OUT PNM_INTERFACE * InterfaceList[]
)
/*++
Routine Description:
Returns the list of interfaces associated with a specified node.
Arguments:
Node - A pointer to the node object for which to enumerate interfaces.
InterfaceCount - On output, contains the number of items in InterfaceList.
InterfaceList - On output, points to an array of pointers to interface
objects. Each pointer in the array must be dereferenced
by the caller. The storage for the array must be
deallocated by the caller.
Return Value:
ERROR_SUCCESS if the routine is successful.
A Win32 error code othewise.
--*/
{
DWORD status = ERROR_SUCCESS;
NmpAcquireLock();
if (NmpLockedEnterApi(NmStateOnline)) {
if (Node->InterfaceCount > 0) {
PNM_INTERFACE * interfaceList = LocalAlloc(
LMEM_FIXED,
sizeof(PNM_INTERFACE) *
Node->InterfaceCount
);
if (interfaceList != NULL) {
PNM_INTERFACE netInterface;
PLIST_ENTRY entry;
DWORD i;
for (entry = Node->InterfaceList.Flink, i=0;
entry != &(Node->InterfaceList);
entry = entry->Flink, i++
)
{
netInterface = CONTAINING_RECORD(entry, NM_INTERFACE, NodeLinkage);
OmReferenceObject(netInterface);
interfaceList[i] = netInterface;
}
*InterfaceCount = Node->InterfaceCount;
*InterfaceList = interfaceList;
}
else {
status = ERROR_NOT_ENOUGH_MEMORY;
}
}
else {
*InterfaceCount = 0;
*InterfaceList = NULL;
}
NmpLockedLeaveApi();
}
else {
status = ERROR_NODE_NOT_AVAILABLE;
ClRtlLogPrint(LOG_NOISE,
"[NM] Not in valid state to process EnumNodeInterfaces request.\n"
);
}
NmpReleaseLock();
return(status);
} // NmEnumNodeInterfaces
DWORD
NmGetNodeHighestVersion(
IN PNM_NODE Node
)
{
return Node->HighestVersion;
}
/////////////////////////////////////////////////////////////////////////////
//
// Handlers for global updates
//
/////////////////////////////////////////////////////////////////////////////
DWORD
NmpUpdateAddNode(
IN BOOL SourceNode,
IN LPDWORD NewNodeId,
IN LPCWSTR NewNodeName,
IN LPDWORD NewNodeHighestVersion,
IN LPDWORD NewNodeLowestVersion,
IN LPDWORD NewNodeProductSuite
)
/*++
Routine Description:
GUM update handler for adding a new node to a cluster.
Arguments:
SourceNode - Specifies whether or not this is the source node for the update
NodeId - Specifies the ID of the node.
NewNodeName - A pointer to a string containing the name of the
new node.
NewNodeHighestVersion - A pointer to the highest cluster version number
that the new node can support.
NewNodeLowestVersion - A pointer to the lowest cluster version number
that the new node can support.
NewNodeProductSuite - A pointer to the product suite identifier for
the new node.
Return Value:
ERROR_SUCCESS if successful
Win32 error code otherwise.
Notes:
This routine is used to add an NT5 (or later) node to an NT5 (or
later) cluster. It will never be invoked in a mixed NT4/NT5
cluster.
--*/
{
PNM_NODE node = NULL;
NM_NODE_INFO2 nodeInfo;
HDMKEY nodeKey = NULL;
DWORD disposition;
DWORD status;
DWORD registryNodeLimit;
HLOCALXSACTION xaction = NULL;
BOOLEAN lockAcquired = FALSE;
if (!NmpEnterApi(NmStateOnline)) {
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] This node is not in a valid state to process a request "
"to add node %1!ws! to the cluster.\n",
NewNodeName
);
return(ERROR_NODE_NOT_AVAILABLE);
}
ClRtlLogPrint(LOG_NOISE,
"[NMJOIN] Received an update to add node '%1!ws!' to "
"the cluster with node ID %2!u!.\n",
NewNodeName,
*NewNodeId
);
if (*NewNodeId > NmMaxNodeId) {
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] Failed to add node %1!ws! to the cluster because the "
"specified node ID, '%2!u!' , is not valid.\n",
NewNodeName,
*NewNodeId
);
status = ERROR_INVALID_PARAMETER;
goto error_exit;
}
//
// Read the registry override before acquiring the NM lock.
//
status = DmQueryDword(
DmClusterParametersKey,
CLUSREG_NAME_MAX_NODES,
&registryNodeLimit,
NULL
);
if (status != ERROR_SUCCESS) {
registryNodeLimit = 0;
}
//
// Begin a transaction - This must be done before acquiring the
// NM lock.
//
xaction = DmBeginLocalUpdate();
if (xaction == NULL) {
status = GetLastError();
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] Failed to begin a transaction to add node %1!ws! "
"to the cluster, status %2!u!.\n",
NewNodeName,
status
);
goto error_exit;
}
NmpAcquireLock(); lockAcquired = TRUE;
//
// Verify that we do not already have the maximum number of nodes
// allowed in this cluster.
//
if (!NmpIsAddNodeAllowed(*NewNodeProductSuite, registryNodeLimit, NULL)) {
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] Cannot add node '%1!ws!' to the cluster. "
"The cluster already contains the maximum number of nodes "
"allowed by the product licenses of the current member nodes "
"and the proposed new node. \n",
NewNodeName
);
status = ERROR_LICENSE_QUOTA_EXCEEDED;
goto error_exit;
}
//
// Verify that the specified node ID is available.
//
if (NmpIdArray[*NewNodeId] != NULL) {
status = ERROR_CLUSTER_NODE_EXISTS;
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] Cannot add node '%1!ws!' to the cluster because "
"node ID '%2!u!' is already in use.\n",
NewNodeName,
*NewNodeId
);
goto error_exit;
}
//
// Try to create a key for the node in the cluster registry.
//
wsprintfW(&(nodeInfo.NodeId[0]), L"%u", *NewNodeId);
nodeKey = DmLocalCreateKey(
xaction,
DmNodesKey,
nodeInfo.NodeId,
0,
KEY_READ | KEY_WRITE,
NULL,
&disposition
);
if (nodeKey == NULL) {
status = GetLastError();
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] Failed to create registry key for new "
"node '%1!ws!' using node ID '%2!u!', status %3!u!\n",
NewNodeName,
*NewNodeId,
status
);
goto error_exit;
}
if (disposition != REG_CREATED_NEW_KEY) {
//
// The key already exists. This must be
// garbage leftover from a failed evict or oldstyle add.
// We'll just overwrite the key.
//
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] A partial definition exists for node ID '%1!u!'. "
"A node addition or eviction operation may have failed.\n",
*NewNodeId
);
}
//
// Add the rest of the node's parameters to the registry.
//
status = DmLocalSetValue(
xaction,
nodeKey,
CLUSREG_NAME_NODE_NAME,
REG_SZ,
(CONST BYTE *)NewNodeName,
NM_WCSLEN(NewNodeName)
);
if (status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] Failed to set registry value '%1!ws!', status %2!u!. "
"Cannot add node '%3!ws!' to the cluster.\n",
CLUSREG_NAME_NODE_NAME,
status,
NewNodeName
);
goto error_exit;
}
status = DmLocalSetValue(
xaction,
nodeKey,
CLUSREG_NAME_NODE_HIGHEST_VERSION,
REG_DWORD,
(CONST BYTE *)NewNodeHighestVersion,
sizeof(DWORD)
);
if (status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] Failed to set registry value '%1!ws!', status %2!u!. "
"Cannot add node '%3!ws!' to the cluster.\n",
CLUSREG_NAME_NODE_HIGHEST_VERSION,
status,
NewNodeName
);
goto error_exit;
}
status = DmLocalSetValue(
xaction,
nodeKey,
CLUSREG_NAME_NODE_LOWEST_VERSION,
REG_DWORD,
(CONST BYTE *)NewNodeLowestVersion,
sizeof(DWORD)
);
if (status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] Failed to set registry value %1!ws!, status %2!u!. "
"Cannot add node '%3!ws!' to the cluster.\n",
CLUSREG_NAME_NODE_LOWEST_VERSION,
status,
NewNodeName
);
goto error_exit;
}
status = DmLocalSetValue(
xaction,
nodeKey,
CLUSREG_NAME_NODE_PRODUCT_SUITE,
REG_DWORD,
(CONST BYTE *)NewNodeProductSuite,
sizeof(DWORD)
);
if (status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] Failed to set registry value %1!ws!, status %2!u!. "
"Cannot add node '%3!ws!' to the cluster.\n",
CLUSREG_NAME_NODE_PRODUCT_SUITE,
status,
NewNodeName
);
goto error_exit;
}
DmCloseKey(nodeKey); nodeKey = NULL;
status = NmpGetNodeDefinition(&nodeInfo);
if (status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] Failed to read definition for node %1!ws! from the "
"cluster database, status %2!u!.\n",
NewNodeName,
status
);
goto error_exit;
}
//
// If a node happens to be joining right now, flag the fact that
// it is now out of synch with the cluster config.
//
if (NmpJoinerNodeId != ClusterInvalidNodeId) {
ClRtlLogPrint(LOG_NOISE,
"[NMJOIN] Joiner (ID %1!u!) is now out of sync due to add of "
"node %2!ws!.\n",
NmpJoinerNodeId,
NewNodeName
);
NmpJoinerOutOfSynch = TRUE;
}
//
// Create the node object
//
NmpReleaseLock();
node = NmpCreateNodeObject(&nodeInfo);
ClNetFreeNodeInfo(&nodeInfo);
NmpAcquireLock();
if (node == NULL) {
status = GetLastError();
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] Failed to create object for node %1!ws!, "
"status %2!u!.\n",
NewNodeName,
status
);
goto error_exit;
}
ClusterEvent(CLUSTER_EVENT_NODE_ADDED, node);
CsLogEvent1(LOG_NOISE, NM_EVENT_NEW_NODE, NewNodeName);
//
// Remove the reference that NmpCreateNodeObject left on the node.
//
OmDereferenceObject(node);
//
// Reset the cluster version and node limit
//
NmpResetClusterVersion(FALSE);
NmpResetClusterNodeLimit();
ClRtlLogPrint(LOG_NOISE,
"[NMJOIN] Successfully added node %1!ws! to the cluster.\n",
NewNodeName
);
error_exit:
if (lockAcquired) {
NmpLockedLeaveApi();
NmpReleaseLock();
}
else {
NmpLeaveApi();
}
if (xaction != NULL) {
if (status == ERROR_SUCCESS) {
DmCommitLocalUpdate(xaction); xaction = NULL;
}
else {
DmAbortLocalUpdate(xaction);
}
}
if (nodeKey != NULL) {
DmCloseKey(nodeKey);
}
return(status);
} // NmpUpdateAddNode
DWORD
NmpUpdateCreateNode(
IN BOOL SourceNode,
IN LPDWORD NodeId
)
/*++
Routine Description:
GUM update handler for dynamically creating a new node
Arguments:
SourceNode - Specifies whether or not this is the source node for the update
NodeId - Specifies the ID of the node.
Return Value:
ERROR_SUCCESS if successful
Win32 error code otherwise.
Notes:
This handler was used by NT4 nodes. Since it is not possible to add
an NT4 node to a cluster containing an NT5 node, this handler should
never be called in an NT5 system.
--*/
{
CL_ASSERT(FALSE);
return(ERROR_CLUSTER_INCOMPATIBLE_VERSIONS);
} // NmpUpdateCreateNode
DWORD
NmpUpdatePauseNode(
IN BOOL SourceNode,
IN LPWSTR NodeId
)
/*++
Routine Description:
GUM update handler for pausing a node
Arguments:
SourceNode - Specifies whether or not this is the source node for the update
NodeId - Specifies the name of the node.
Return Value:
ERROR_SUCCESS if successful
Win32 error code otherwise.
--*/
{
DWORD status = ERROR_SUCCESS;
HLOCALXSACTION xaction = NULL;
PNM_NODE node = NULL;
BOOLEAN lockAcquired = FALSE;
if (!NmpEnterApi(NmStateOnline)) {
ClRtlLogPrint(LOG_NOISE,
"[NM] Not in valid state to process PauseNode update.\n"
);
return(ERROR_NODE_NOT_AVAILABLE);
}
ClRtlLogPrint(LOG_NOISE,
"[NM] Received update to pause node %1!ws!\n",
NodeId
);
xaction = DmBeginLocalUpdate();
if (xaction == NULL) {
status = GetLastError();
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Failed to start a transaction, status %1!u!\n",
status
);
goto error_exit;
}
node = OmReferenceObjectById(ObjectTypeNode, NodeId);
if (node == NULL) {
status = ERROR_CLUSTER_NODE_NOT_FOUND;
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] Node %1!ws! does not exist\n",
NodeId
);
goto error_exit;
}
NmpAcquireLock(); lockAcquired = TRUE;
if (node->NodeId == NmpJoinerNodeId) {
status = ERROR_CLUSTER_NODE_DOWN;
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] Cannot pause node %1!ws! because it is in the process "
"of joining the cluster.\n",
NodeId
);
goto error_exit;
}
if (node->State == ClusterNodeUp) {
//
// Update the registry to reflect the new state.
//
HDMKEY nodeKey = DmOpenKey(DmNodesKey, NodeId, KEY_WRITE);
if (nodeKey != NULL) {
DWORD isPaused = 1;
status = DmLocalSetValue(
xaction,
nodeKey,
CLUSREG_NAME_NODE_PAUSED,
REG_DWORD,
(CONST BYTE *)&isPaused,
sizeof(isPaused)
);
#ifdef CLUSTER_TESTPOINT
TESTPT(TpFailNmPauseNode) {
status = 999999;
}
#endif
if (status == ERROR_SUCCESS) {
node->State = ClusterNodePaused;
ClusterEvent(CLUSTER_EVENT_NODE_CHANGE, node);
//
// If a node happens to be joining right now, flag the
// fact that it is now out of synch with the cluster config.
//
if (NmpJoinerNodeId != ClusterInvalidNodeId) {
ClRtlLogPrint(LOG_NOISE,
"[NMJOIN] Joiner (ID %1!u!) is now out of sync due "
"to pause operation on node %2!ws!.\n",
NmpJoinerNodeId,
NodeId
);
NmpJoinerOutOfSynch = TRUE;
}
}
else {
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] Failed to set Paused value for node %1!ws!, "
"status %2!u!.\n",
NodeId,
status
);
}
DmCloseKey(nodeKey);
}
else {
status = GetLastError();
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] Failed to open key for node %1!ws!, status %2!u!.\n",
NodeId,
status
);
}
}
else if (node->State != ClusterNodePaused) {
status = ERROR_CLUSTER_NODE_DOWN;
}
error_exit:
if (lockAcquired) {
NmpLockedLeaveApi();
NmpReleaseLock();
}
else {
NmpLeaveApi();
}
if (xaction != NULL) {
if (status == ERROR_SUCCESS) {
DmCommitLocalUpdate(xaction);
}
else {
DmAbortLocalUpdate(xaction);
}
}
if (node != NULL) {
OmDereferenceObject(node);
}
return(status);
} // NmpUpdatePauseNode
DWORD
NmpUpdateResumeNode(
IN BOOL SourceNode,
IN LPWSTR NodeId
)
/*++
Routine Description:
GUM update handler for resuming a node
Arguments:
SourceNode - Specifies whether or not this is the source node for the update
NodeId - Specifies the name of the node.
Return Value:
ERROR_SUCCESS if successful
Win32 error code otherwise.
--*/
{
DWORD status = ERROR_SUCCESS;
HLOCALXSACTION xaction = NULL;
PNM_NODE node = NULL;
BOOLEAN lockAcquired = FALSE;
if (!NmpEnterApi(NmStateOnline)) {
ClRtlLogPrint(LOG_NOISE,
"[NM] Not in valid state to process ResumeNode update.\n"
);
return(ERROR_NODE_NOT_AVAILABLE);
}
ClRtlLogPrint(LOG_NOISE,
"[NM] Received update to resume node %1!ws!\n",
NodeId
);
xaction = DmBeginLocalUpdate();
if (xaction == NULL) {
status = GetLastError();
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Failed to start a transaction, status %1!u!\n",
status
);
goto error_exit;
}
node = OmReferenceObjectById(ObjectTypeNode, NodeId);
if (node == NULL) {
status = ERROR_CLUSTER_NODE_NOT_FOUND;
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] Node %1!ws! does not exist\n",
NodeId
);
goto error_exit;
}
NmpAcquireLock(); lockAcquired = TRUE;
if (node->NodeId == NmpJoinerNodeId) {
status = ERROR_CLUSTER_NODE_DOWN;
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] Cannot resume node %1!ws! because it is in the process "
"of joining the cluster.\n",
NodeId
);
goto error_exit;
}
if (node->State == ClusterNodePaused) {
//
// Update the registry to reflect the new state.
//
HDMKEY nodeKey = DmOpenKey(DmNodesKey, NodeId, KEY_WRITE);
if (nodeKey != NULL) {
status = DmLocalDeleteValue(
xaction,
nodeKey,
CLUSREG_NAME_NODE_PAUSED
);
#ifdef CLUSTER_TESTPOINT
TESTPT(TpFailNmResumeNode) {
status = 999999;
}
#endif
if (status == ERROR_SUCCESS) {
node->State = ClusterNodeUp;
ClusterEvent(CLUSTER_EVENT_NODE_CHANGE, node);
//
// If a node happens to be joining right now, flag the
// fact that it is now out of synch with the cluster config.
//
if (NmpJoinerNodeId != ClusterInvalidNodeId) {
ClRtlLogPrint(LOG_NOISE,
"[NMJOIN] Joiner (ID %1!u!) is now out of sync due "
"to resume operation on node %2!ws!.\n",
NmpJoinerNodeId,
NodeId
);
NmpJoinerOutOfSynch = TRUE;
}
}
else {
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] Failed to delete Paused value for node %1!ws!, "
"status %2!u!.\n",
NodeId,
status
);
}
DmCloseKey(nodeKey);
}
else {
status = GetLastError();
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] Failed to open key for node %1!ws!, status %2!u!.\n",
NodeId,
status
);
}
}
else {
status = ERROR_CLUSTER_NODE_NOT_PAUSED;
}
error_exit:
if (lockAcquired) {
NmpLockedLeaveApi();
NmpReleaseLock();
}
else {
NmpLeaveApi();
}
if (xaction != NULL) {
if (status == ERROR_SUCCESS) {
DmCommitLocalUpdate(xaction);
}
else {
DmAbortLocalUpdate(xaction);
}
}
if (node != NULL) {
OmDereferenceObject(node);
}
return(status);
} // NmpUpdateResumeNode
DWORD
NmpUpdateEvictNode(
IN BOOL SourceNode,
IN LPWSTR NodeId
)
/*++
Routine Description:
GUM update handler for evicting a node.
The specified node is deleted from the OM.
If the specified node is online, it is paused to prevent any other groups
from moving there.
If the specified node is the current node, it attempts to failover any
owned groups.
Arguments:
SourceNode - Specifies whether or not this is the source node for the update
NodeId - Specifies the name of the node.
Return Value:
ERROR_SUCCESS if successful
Win32 error code otherwise.
Notes:
It is very hard to make this operation abortable, so it isn't. If anything
goes wrong past a certain point, the node will halt.
Assumption: Since global updates are serialized, and local transactions
guarantee exclusive access to the registry, no other updates can be made in
parallel by the FM.
--*/
{
DWORD status = ERROR_SUCCESS;
PNM_NODE node = NULL;
HLOCALXSACTION xaction = NULL;
PNM_NETWORK network;
LPCWSTR networkId;
PNM_INTERFACE netInterface;
LPCWSTR interfaceId;
PLIST_ENTRY entry;
BOOLEAN lockAcquired = FALSE;
if (!NmpEnterApi(NmStateOnline)) {
ClRtlLogPrint(LOG_NOISE,
"[NM] Not in valid state to process EvictNode update.\n"
);
return(ERROR_NODE_NOT_AVAILABLE);
}
ClRtlLogPrint(LOG_NOISE,
"[NM] Received update to evict node %1!ws!\n",
NodeId
);
node = OmReferenceObjectById(ObjectTypeNode, NodeId);
if (node == NULL) {
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] Node %1!ws! does not exist\n",
NodeId
);
status = ERROR_CLUSTER_NODE_NOT_FOUND;
goto error_exit;
}
//
// Begin a transaction
//
xaction = DmBeginLocalUpdate();
if (xaction == NULL) {
status = GetLastError();
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Failed to start a transaction, status %1!u!\n",
status
);
goto error_exit;
}
NmpAcquireLock(); lockAcquired = TRUE;
if (NmpJoinerNodeId != ClusterInvalidNodeId) {
status = ERROR_CLUSTER_JOIN_IN_PROGRESS;
ClRtlLogPrint(LOG_NOISE,
"[NM] Cannot evict node because a join is in progress.\n"
);
goto error_exit;
}
//
// Only continue if the node is down. Evicting a node while it
// is actively participating in the cluster is way too tricky.
//
if (node->State != ClusterNodeDown) {
status = ERROR_CANT_EVICT_ACTIVE_NODE;
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Node %1!ws! cannot be evicted because it is not offline.\n",
NodeId
);
goto error_exit;
}
//
// Scrub the FM's portion of the registry of all references to this node.
//
status = NmpCleanseRegistry(NodeId, xaction);
if (status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Failed to remove all resource database references to "
"evicted node %1!ws!, status %2!u!\n",
NodeId,
status
);
goto error_exit;
}
//
// Delete the node's interfaces from the database.
//
for (entry = node->InterfaceList.Flink;
entry != &(node->InterfaceList);
entry = entry->Flink
)
{
netInterface = CONTAINING_RECORD(
entry,
NM_INTERFACE,
NodeLinkage
);
interfaceId = OmObjectId(netInterface);
network = netInterface->Network;
networkId = OmObjectId(network);
//
// Delete the interface definition from the database.
//
status = DmLocalDeleteTree(xaction, DmNetInterfacesKey, interfaceId);
if (status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Failed to delete definition for interface %1!ws!, "
"status %2!u!.\n",
interfaceId,
status
);
goto error_exit;
}
if (network->InterfaceCount == 1) {
//
// This is the last interface on the network.
// Delete the network too.
//
status = DmLocalDeleteTree(xaction, DmNetworksKey, networkId);
if (status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Failed to delete definition for network %1!ws!, "
"status %2!u!.\n",
networkId,
status
);
goto error_exit;
}
}
}
//
// Delete the node's database entry
//
status = DmLocalDeleteTree(xaction, DmNodesKey, NodeId);
#ifdef CLUSTER_TESTPOINT
TESTPT(TpFailNmEvictNodeAbort) {
status = 999999;
}
#endif
if (status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Failed to delete node's database key, status %1!u!\n",
status
);
goto error_exit;
}
//
// WARNING: From here on, operations cannot be reversed.
// If any one of them fails, this node must halt to avoid being
// inconsistent.
//
//
// Delete the interface objects associated with this node.
//
while (!IsListEmpty(&(node->InterfaceList))) {
entry = node->InterfaceList.Flink;
netInterface = CONTAINING_RECORD(
entry,
NM_INTERFACE,
NodeLinkage
);
network = netInterface->Network;
networkId = OmObjectId(network);
NmpDeleteInterfaceObject(netInterface, TRUE);
if (network->InterfaceCount == 0) {
//
// This is the last interface on the network.
// Delete the network too.
//
NmpDeleteNetworkObject(network, TRUE);
}
}
//
// Delete the node's object.
//
NmpDeleteNodeObject(node, TRUE);
//after the node is deleted, recalculate the operational version of
//the cluster
NmpResetClusterVersion(TRUE);
//calculate the operational limit on the number of nodes that
//can be a part of this cluster
NmpResetClusterNodeLimit();
NmpReleaseLock(); lockAcquired = FALSE;
//
// Call the FM so it can clean up any outstanding references to this
// node from its structures.
//
status = FmEvictNode(node);
#ifdef CLUSTER_TESTPOINT
TESTPT(TpFailNmEvictNodeHalt) {
status = 999999;
}
#endif
if (status != ERROR_SUCCESS ) {
WCHAR string[16];
wsprintfW(&(string[0]), L"%u", status);
ClRtlLogPrint(LOG_CRITICAL,
"[NM] FATAL ERROR: Failed to remove all resource references to evicted node %1!ws!, status %2!u!\n",
NodeId,
status
);
CsLogEvent3(
LOG_CRITICAL,
NM_EVENT_EVICTION_ERROR,
NmLocalNodeName,
OmObjectName(node),
string
);
CsInconsistencyHalt(status);
}
CL_ASSERT(status == ERROR_SUCCESS);
error_exit:
if (lockAcquired) {
NmpLockedLeaveApi();
NmpReleaseLock();
}
else {
NmpLeaveApi();
}
if (xaction != NULL) {
if (status == ERROR_SUCCESS) {
DmCommitLocalUpdate(xaction);
}
else {
DmAbortLocalUpdate(xaction);
}
}
if (node != NULL) {
OmDereferenceObject(node);
}
if (status != ERROR_SUCCESS) {
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Failed to evict node %1!ws!.\n",
NodeId
);
}
return(status);
} // NmpUpdateEvictNode
/////////////////////////////////////////////////////////////////////////////
//
// Database management routines
//
/////////////////////////////////////////////////////////////////////////////
DWORD
NmpGetNodeDefinition(
IN OUT PNM_NODE_INFO2 NodeInfo
)
/*++
Routine Description:
Reads information about a defined cluster node from the cluster database
and stores the information in a supplied structure.
Arguments:
NodeInfo - A pointer to the structure into which to store the node
information. The NodeId field of the structure contains
the ID of the node for which to read information.
Return Value:
ERROR_SUCCESS if the routine succeeds.
A Win32 error code otherwise.
--*/
{
DWORD status;
HDMKEY nodeKey = NULL;
DWORD valueLength;
DWORD valueType;
LPWSTR string;
WCHAR errorString[12];
nodeKey = DmOpenKey(DmNodesKey, NodeInfo->NodeId, KEY_READ);
if (nodeKey == NULL) {
status = GetLastError();
wsprintfW(&(errorString[0]), L"%u", status);
CsLogEvent2(
LOG_CRITICAL,
CS_EVENT_REG_OPEN_FAILED,
NodeInfo->NodeId,
errorString
);
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Failed to open node key, status %1!u!\n",
status
);
status = ERROR_CLUSTER_NODE_NOT_MEMBER;
goto error_exit;
}
valueLength = sizeof(NodeInfo->NodeName);
string = CLUSREG_NAME_NODE_NAME;
status = DmQueryValue(
nodeKey,
string,
&valueType,
(LPBYTE) &(NodeInfo->NodeName[0]),
&valueLength
);
if (status != ERROR_SUCCESS) {
wsprintfW(&(errorString[0]), L"%u", status);
CsLogEvent2(
LOG_CRITICAL,
CS_EVENT_REG_QUERY_FAILED,
string,
errorString
);
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Failed to read node name, status %1!u!\n",
status
);
goto error_exit;
}
if (valueType != REG_SZ) {
status = ERROR_INVALID_PARAMETER;
wsprintfW(&(errorString[0]), L"%u", status);
CsLogEvent2(
LOG_CRITICAL,
CS_EVENT_REG_QUERY_FAILED,
string,
errorString
);
goto error_exit;
}
//read the node's highest version
string = CLUSREG_NAME_NODE_HIGHEST_VERSION;
status = DmQueryDword(nodeKey, string, &NodeInfo->NodeHighestVersion,
NULL);
if (status != ERROR_SUCCESS)
{
wsprintfW(&(errorString[0]), L"%u", status);
CsLogEvent2(
LOG_CRITICAL,
CS_EVENT_REG_QUERY_FAILED,
string,
errorString
);
//this can happen on an upgrade from sp3 to nt5
//assume the node highest version is that of sp3
//the fixup function will get this fixed
NodeInfo->NodeHighestVersion = CLUSTER_MAKE_VERSION(1, 224);
}
//read the node's lowest version
string = CLUSREG_NAME_NODE_LOWEST_VERSION;
status = DmQueryDword(nodeKey, string, &NodeInfo->NodeLowestVersion,
NULL);
if (status != ERROR_SUCCESS)
{
wsprintfW(&(errorString[0]), L"%u", status);
CsLogEvent2(
LOG_CRITICAL,
CS_EVENT_REG_QUERY_FAILED,
string,
errorString
);
//this can happen on upgrade from sp3 to nt5
//if the nodelowestversion is not present assume it
//was an sp3 node(lowest version is 1.224)
NodeInfo->NodeLowestVersion = CLUSTER_MAKE_VERSION( 1, 224);
}
NodeInfo->State = ClusterNodeDown;
DmCloseKey(nodeKey);
return(ERROR_SUCCESS);
error_exit:
ClNetFreeNodeInfo(NodeInfo);
if (nodeKey != NULL) {
DmCloseKey(nodeKey);
}
return(status);
} // NmpGetNodeDefinition
DWORD
NmpGetNodeAuxInfo(
IN LPCWSTR NodeId,
IN OUT PNM_NODE_AUX_INFO pNodeAuxInfo
)
/*++
Routine Description:
Reads information about a defined cluster node from the cluster database
and stores the information in a supplied structure.
Arguments:
pNodeAuxInfo - A pointer to the structure into which to store the node
information. The NodeId field of the structure contains
the ID of the node for which to read information.
Return Value:
ERROR_SUCCESS if the routine succeeds.
A Win32 error code otherwise.
--*/
{
DWORD status;
HDMKEY nodeKey = NULL;
DWORD valueLength;
DWORD valueType;
LPWSTR string;
WCHAR errorString[12];
nodeKey = DmOpenKey(DmNodesKey, NodeId, KEY_READ);
if (nodeKey == NULL)
{
status = GetLastError();
wsprintfW(&(errorString[0]), L"%u", status);
CsLogEvent2(
LOG_CRITICAL,
CS_EVENT_REG_OPEN_FAILED,
NodeId,
errorString
);
ClRtlLogPrint(LOG_CRITICAL,
"[NM] NmpGetNodeAuxInfo : Failed to open node key, "
"status %1!u!\n",
status);
status = ERROR_CLUSTER_NODE_NOT_MEMBER;
goto error_exit;
}
//read the node's product suite
string = CLUSREG_NAME_NODE_PRODUCT_SUITE;
status = DmQueryDword(
nodeKey,
string,
(LPDWORD)&(pNodeAuxInfo->ProductSuite),
NULL
);
if (status != ERROR_SUCCESS)
{
wsprintfW(&(errorString[0]), L"%u", status);
CsLogEvent2(
LOG_NOISE,
CS_EVENT_REG_QUERY_FAILED,
string,
errorString
);
//assume it is enterprise
pNodeAuxInfo->ProductSuite = Enterprise;
}
DmCloseKey(nodeKey);
return(ERROR_SUCCESS);
error_exit:
if (nodeKey != NULL)
{
DmCloseKey(nodeKey);
}
return(status);
} // NmpGetNodeAuxInfo
DWORD
NmpEnumNodeDefinitions(
PNM_NODE_ENUM2 * NodeEnum
)
/*++
Routine Description:
Reads information about all defined cluster nodes from the cluster
database and builds an enumeration structure containing the information.
Arguments:
NodeEnum - A pointer to the variable into which to place a pointer to
the allocated node enumeration.
Return Value:
ERROR_SUCCESS if the routine succeeds.
A Win32 error code otherwise.
Notes:
This routine MUST NOT be called with the NM lock held.
--*/
{
DWORD status;
PNM_NODE_ENUM2 nodeEnum = NULL;
WCHAR nodeId[CS_MAX_NODE_ID_LENGTH];
DWORD i;
DWORD valueLength;
DWORD numNodes;
DWORD ignored;
FILETIME fileTime;
WCHAR errorString[12];
HLOCALXSACTION xaction;
BOOLEAN commitXaction = FALSE;
*NodeEnum = NULL;
//
// Begin a transaction - this must not be done while holding
// the NM lock.
//
xaction = DmBeginLocalUpdate();
if (xaction == NULL) {
status = GetLastError();
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] Failed to begin a transaction, status %1!u!.\n",
status
);
}
NmpAcquireLock();
//
// First count the number of nodes.
//
status = DmQueryInfoKey(
DmNodesKey,
&numNodes,
&ignored, // MaxSubKeyLen
&ignored, // Values
&ignored, // MaxValueNameLen
&ignored, // MaxValueLen
&ignored, // lpcbSecurityDescriptor
&fileTime
);
if (status != ERROR_SUCCESS) {
wsprintfW(&(errorString[0]), L"%u", status);
CsLogEvent1(LOG_CRITICAL, CS_EVENT_REG_OPERATION_FAILED, errorString);
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Failed to query Nodes key information, status %1!u!\n",
status
);
goto error_exit;
}
valueLength = sizeof(NM_NODE_ENUM2) +
(sizeof(NM_NODE_INFO2) * (numNodes - 1));
nodeEnum = MIDL_user_allocate(valueLength);
if (nodeEnum == NULL) {
status = ERROR_NOT_ENOUGH_MEMORY;
wsprintfW(&(errorString[0]), L"%u", status);
CsLogEvent1(LOG_CRITICAL, CS_EVENT_ALLOCATION_FAILURE, errorString);
ClRtlLogPrint(LOG_CRITICAL, "[NM] Failed to allocate memory.\n");
goto error_exit;
}
ZeroMemory(nodeEnum, valueLength);
for (i=0; i < numNodes; i++) {
valueLength = sizeof(nodeEnum->NodeList[nodeEnum->NodeCount].NodeId);
status = DmEnumKey(
DmNodesKey,
i,
&(nodeEnum->NodeList[nodeEnum->NodeCount].NodeId[0]),
&valueLength,
NULL
);
if (status != ERROR_SUCCESS) {
wsprintfW(&(errorString[0]), L"%u", status);
CsLogEvent1(
LOG_CRITICAL,
CS_EVENT_REG_OPERATION_FAILED,
errorString
);
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Failed to enumerate node key, status %1!u!\n",
status
);
goto error_exit;
}
status = NmpGetNodeDefinition(
&(nodeEnum->NodeList[nodeEnum->NodeCount])
);
if (status != ERROR_SUCCESS) {
if (status == ERROR_FILE_NOT_FOUND) {
//
// Partial node definition in the database.
// Probably from a failed AddNode operation.
//
LPWSTR nodeIdString =
nodeEnum->NodeList[nodeEnum->NodeCount].NodeId;
DWORD nodeId = wcstoul(
nodeIdString,
NULL,
10
);
//
// Delete the key and ignore it in the enum struct if it
// is safe to do so.
//
if ( (NmpIdArray[nodeId] == NULL) &&
(nodeId != NmLocalNodeId)
)
{
if (xaction != NULL) {
DWORD status2;
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Deleting partial definition for node "
"ID %1!ws!\n",
nodeIdString
);
status2 = DmLocalDeleteKey(
xaction,
DmNodesKey,
nodeIdString
);
if (status2 == ERROR_SUCCESS) {
commitXaction = TRUE;
}
}
}
continue;
}
goto error_exit;
}
nodeEnum->NodeCount++;
}
*NodeEnum = nodeEnum;
CL_ASSERT(status == ERROR_SUCCESS);
error_exit:
NmpReleaseLock();
if (xaction != NULL) {
if ((status == ERROR_SUCCESS) && commitXaction) {
DmCommitLocalUpdate(xaction);
}
else {
DmAbortLocalUpdate(xaction);
}
}
if ((status != ERROR_SUCCESS) && (nodeEnum != NULL)) {
ClNetFreeNodeEnum(nodeEnum);
}
return(status);
} // NmpEnumNodeDefinitions
/////////////////////////////////////////////////////////////////////////////
//
// Object management routines
//
/////////////////////////////////////////////////////////////////////////////
DWORD
NmpCreateNodeObjects(
IN PNM_NODE_ENUM2 NodeEnum
)
/*++
Routine Description:
Processes a node information enumeration and creates node objects.
Arguments:
NodeEnum - A pointer to a node information enumeration structure.
Return Value:
ERROR_SUCCESS if the routine completes successfully.
A Win32 error code otherwise.
--*/
{
DWORD status = ERROR_SUCCESS;
PNM_NODE_INFO2 nodeInfo;
DWORD i;
PNM_NODE node;
BOOLEAN foundLocalNode = FALSE;
for (i=0; i < NodeEnum->NodeCount; i++) {
nodeInfo = &(NodeEnum->NodeList[i]);
//
// The local node object was created during initialization.
// Skip it.
//
if (wcscmp(NmLocalNodeIdString, nodeInfo->NodeId) != 0) {
node = NmpCreateNodeObject(nodeInfo);
if (node == NULL) {
status = GetLastError();
break;
}
else {
OmDereferenceObject(node);
}
}
else {
foundLocalNode = TRUE;
}
}
if ( !foundLocalNode ) {
status = ERROR_CLUSTER_NODE_NOT_MEMBER;
}
return(status);
} // NmpCreateNodeObjects
DWORD
NmpCreateLocalNodeObject(
IN PNM_NODE_INFO2 NodeInfo
)
/*++
Routine Description:
Creates a node object for the local node given information about the node.
Arguments:
NodeInfo - A pointer to a structure containing a description of the node
to create.
Return Value:
ERROR_SUCCESS if the routine completes successfully.
A Win32 error code otherwise.
--*/
{
DWORD status;
LPWSTR string;
CL_ASSERT(NmLocalNode == NULL);
//
// Verify that the node name matches the local computername.
//
if (wcscmp(NodeInfo->NodeName, NmLocalNodeName) != 0) {
string = L"";
CsLogEvent2(
LOG_CRITICAL,
NM_EVENT_NODE_NOT_MEMBER,
NmLocalNodeName,
string
);
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Computername does not match node name in database.\n"
);
return(ERROR_INVALID_PARAMETER);
}
NmLocalNode = NmpCreateNodeObject(NodeInfo);
if (NmLocalNode == NULL) {
status = GetLastError();
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Failed to create local node (%1!ws!), status %2!u!.\n",
NodeInfo->NodeId,
status
);
return(status);
}
else {
NmLocalNode->ExtendedState = ClusterNodeJoining;
OmDereferenceObject(NmLocalNode);
}
return(ERROR_SUCCESS);
}
PNM_NODE
NmpCreateNodeObject(
IN PNM_NODE_INFO2 NodeInfo
)
/*++
Routine Description:
Creates a node object given information about the node.
Arguments:
NodeInfo - A pointer to a structure containing a description of the node
to create.
Return Value:
A pointer to the created node object if successful.
NULL if not successful. Extended error information is available
from GetLastError().
--*/
{
PNM_NODE node = NULL;
DWORD status = ERROR_SUCCESS;
BOOL created = FALSE;
DWORD eventCode = 0;
WCHAR errorString[12];
ClRtlLogPrint(LOG_NOISE,
"[NM] Creating object for node %1!ws! (%2!ws!)\n",
NodeInfo->NodeId,
NodeInfo->NodeName
);
//
// Make sure that the node doesn't already exist.
//
node = OmReferenceObjectById(ObjectTypeNode, NodeInfo->NodeId);
if (node == NULL) {
//
// Make sure that the node doesn't already exist, this time by name.
//
node = OmReferenceObjectByName(ObjectTypeNode, NodeInfo->NodeName);
}
if (node != NULL) {
OmDereferenceObject(node);
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Object already exists for node %1!ws!\n",
NodeInfo->NodeId
);
SetLastError(ERROR_OBJECT_ALREADY_EXISTS);
return(NULL);
}
node = OmCreateObject(
ObjectTypeNode,
NodeInfo->NodeId,
NodeInfo->NodeName,
&created
);
if (node == NULL) {
status = GetLastError();
wsprintfW(&(errorString[0]), L"%u", status);
CsLogEvent1(LOG_CRITICAL, CS_EVENT_ALLOCATION_FAILURE, errorString);
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Failed to create object for node %1!ws! (%2!ws!), status %3!u!\n",
NodeInfo->NodeId,
NodeInfo->NodeName,
status
);
SetLastError(status);
return(NULL);
}
CL_ASSERT(created == TRUE);
ZeroMemory(node, sizeof(NM_NODE));
node->NodeId = wcstoul(NodeInfo->NodeId, NULL, 10);
node->State = NodeInfo->State;
// A join cannot proceed if any of the current node's ExtendedState is not up. But the State might be paused.
// So don't copy the State field into ExtendedState field. (#379170)
node->ExtendedState = ClusterNodeUp;
node->HighestVersion = NodeInfo->NodeHighestVersion;
node->LowestVersion = NodeInfo->NodeLowestVersion;
//for now assume enterprise
//NmpRefresh will fixup this information later..
node->ProductSuite = Enterprise;
InitializeListHead(&(node->InterfaceList));
CL_ASSERT(NmIsValidNodeId(node->NodeId));
if (node->NodeId != NmLocalNodeId) {
status = ClusnetRegisterNode(NmClusnetHandle, node->NodeId);
if (status != ERROR_SUCCESS) {
wsprintfW(&(errorString[0]), L"%u", status);
CsLogEvent2(
LOG_CRITICAL,
NM_EVENT_CLUSNET_REGISTER_NODE_FAILED,
NodeInfo->NodeId,
errorString
);
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Failed to register node %1!ws! (%2!ws!) with the Cluster Network, status %3!u!\n",
NodeInfo->NodeId,
NodeInfo->NodeName,
status
);
goto error_exit;
}
}
//
// Put a reference on the object for the caller.
//
OmReferenceObject(node);
NmpAcquireLock();
if (NM_NODE_UP(node)) {
//
// Add this node to the up nodes set
//
BitsetAdd(NmpUpNodeSet, node->NodeId);
//
// Enable communication with this node during the
// join process.
//
ClRtlLogPrint(LOG_NOISE,
"[NM] Enabling communication for node %1!ws!\n",
NodeInfo->NodeId
);
status = ClusnetOnlineNodeComm(NmClusnetHandle, node->NodeId);
if (status != ERROR_SUCCESS) {
NmpReleaseLock();
OmDereferenceObject(node);
wsprintfW(&(errorString[0]), L"%u", status);
CsLogEvent2(
LOG_CRITICAL,
NM_EVENT_CLUSNET_ONLINE_COMM_FAILED,
NodeInfo->NodeId,
errorString
);
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Failed to enable node %1!ws! (%2!ws!) for communication, status %3!u!\n",
NodeInfo->NodeId,
NodeInfo->NodeName,
status
);
goto error_exit;
}
}
CL_ASSERT(NmpIdArray != NULL);
CL_ASSERT(NmpIdArray[node->NodeId] == NULL);
NmpIdArray[node->NodeId] = node;
InsertTailList(&NmpNodeList, &(node->Linkage));
node->Flags |= NM_FLAG_OM_INSERTED;
OmInsertObject(node);
NmpNodeCount++;
NmpReleaseLock();
return(node);
error_exit:
ClRtlLogPrint(LOG_CRITICAL,
"[NM] Failed to create object for node %1!ws!, status %2!u!.\n",
NodeInfo->NodeId,
status
);
if (eventCode != 0) {
wsprintfW(&(errorString[0]), L"%u", status);
CsLogEvent1(LOG_CRITICAL, eventCode, errorString);
}
if (node != NULL) {
NmpAcquireLock();
NmpDeleteNodeObject(node, FALSE);
NmpReleaseLock();
}
SetLastError(status);
return(NULL);
} // NmpCreateNodeObject
DWORD
NmpGetNodeObjectInfo(
IN PNM_NODE Node,
IN OUT PNM_NODE_INFO2 NodeInfo
)
/*++
Routine Description:
Reads information about a defined cluster node from the its cluster
object and stores the information in a supplied structure.
Arguments:
Node - A pointer to the node object to query.
NodeInfo - A pointer to the structure into which to store the node
information.
Return Value:
ERROR_SUCCESS if the routine succeeds.
A Win32 error code otherwise.
Notes:
Called with the NmpLock held.
--*/
{
DWORD status;
lstrcpyW(&(NodeInfo->NodeId[0]), OmObjectId(Node));
lstrcpyW(&(NodeInfo->NodeName[0]), OmObjectName(Node));
NodeInfo->State = Node->State;
NodeInfo->NodeHighestVersion = Node->HighestVersion;
NodeInfo->NodeLowestVersion = Node->LowestVersion;
return(ERROR_SUCCESS);
} // NmpGetNodeObjectInfo
VOID
NmpDeleteNodeObject(
IN PNM_NODE Node,
IN BOOLEAN IssueEvent
)
/*++
Notes:
Called with NM lock held.
--*/
{
DWORD status;
PNM_INTERFACE netInterface;
PLIST_ENTRY entry;
LPWSTR nodeId = (LPWSTR) OmObjectId(Node);
if (NM_DELETE_PENDING(Node)) {
CL_ASSERT(!NM_OM_INSERTED(Node));
return;
}
ClRtlLogPrint(LOG_NOISE,
"[NM] Deleting object for node %1!ws!.\n",
nodeId
);
Node->Flags |= NM_FLAG_DELETE_PENDING;
//
// Remove from the various object lists.
//
if (NM_OM_INSERTED(Node)) {
status = OmRemoveObject(Node);
CL_ASSERT(status == ERROR_SUCCESS);
Node->Flags &= ~NM_FLAG_OM_INSERTED;
RemoveEntryList(&(Node->Linkage));
NmpIdArray[Node->NodeId] = NULL;
CL_ASSERT(NmpNodeCount > 0);
NmpNodeCount--;
}
//
// Delete all of the interfaces on this node
//
while (!IsListEmpty(&(Node->InterfaceList))) {
entry = Node->InterfaceList.Flink;
netInterface = CONTAINING_RECORD(entry, NM_INTERFACE, NodeLinkage);
NmpDeleteInterfaceObject(netInterface, IssueEvent);
}
status = ClusnetDeregisterNode(NmClusnetHandle, Node->NodeId);
CL_ASSERT( (status == ERROR_SUCCESS) ||
(status == ERROR_NOT_READY) ||
(status == ERROR_CLUSTER_NODE_NOT_FOUND)
);
if (IssueEvent) {
ClRtlLogPrint(LOG_NOISE,
"[NM] Issuing delete event for node %1!ws!.\n",
nodeId
);
ClusterEvent(CLUSTER_EVENT_NODE_DELETED, Node);
}
OmDereferenceObject(Node);
return;
} // NmpDeleteNodeObject
BOOL
NmpDestroyNodeObject(
PNM_NODE Node
)
{
DWORD status;
ClRtlLogPrint(LOG_NOISE,
"[NM] destroying node %1!ws!\n",
OmObjectId(Node)
);
CL_ASSERT(NM_DELETE_PENDING(Node));
CL_ASSERT(!NM_OM_INSERTED(Node));
ClMsgDeleteDefaultRpcBinding(Node, Node->DefaultRpcBindingGeneration);
ClMsgDeleteRpcBinding(Node->ReportRpcBinding);
ClMsgDeleteRpcBinding(Node->IsolateRpcBinding);
return(TRUE);
} // NmpDestroyNodeObject
DWORD
NmpEnumNodeObjects(
PNM_NODE_ENUM2 * NodeEnum
)
/*++
Routine Description:
Reads information about all defined cluster nodes from the cluster
object manager and builds an enumeration structure containing
the information.
Arguments:
NodeEnum - A pointer to the variable into which to place a pointer to
the allocated node enumeration.
Return Value:
ERROR_SUCCESS if the routine succeeds.
A Win32 error code otherwise.
Notes:
Called with the NmpLock held.
--*/
{
DWORD status = ERROR_SUCCESS;
PNM_NODE_ENUM2 nodeEnum = NULL;
DWORD i;
DWORD valueLength;
PLIST_ENTRY entry;
PNM_NODE node;
*NodeEnum = NULL;
if (NmpNodeCount == 0) {
valueLength = sizeof(NM_NODE_ENUM2);
}
else {
valueLength = sizeof(NM_NODE_ENUM2) +
(sizeof(NM_NODE_INFO2) * (NmpNodeCount - 1));
}
nodeEnum = MIDL_user_allocate(valueLength);
if (nodeEnum == NULL) {
return(ERROR_NOT_ENOUGH_MEMORY);
}
ZeroMemory(nodeEnum, valueLength);
for (entry = NmpNodeList.Flink, i=0;
entry != &NmpNodeList;
entry = entry->Flink, i++
)
{
node = CONTAINING_RECORD(entry, NM_NODE, Linkage);
status = NmpGetNodeObjectInfo(
node,
&(nodeEnum->NodeList[i])
);
if (status != ERROR_SUCCESS) {
ClNetFreeNodeEnum(nodeEnum);
return(status);
}
}
nodeEnum->NodeCount = NmpNodeCount;
*NodeEnum = nodeEnum;
nodeEnum = NULL;
return(ERROR_SUCCESS);
} // NmpEnumNodeObjects
DWORD
NmpSetNodeInterfacePriority(
IN PNM_NODE Node,
IN DWORD Priority,
IN PNM_INTERFACE TargetInterface OPTIONAL,
IN DWORD TargetInterfacePriority OPTIONAL
)
/*++
Called with the NmpLock held.
--*/
{
PNM_INTERFACE netInterface;
PNM_NETWORK network;
DWORD status = ERROR_SUCCESS;
PLIST_ENTRY entry;
for (entry = Node->InterfaceList.Flink;
entry != &Node->InterfaceList;
entry = entry->Flink
)
{
netInterface = CONTAINING_RECORD( entry, NM_INTERFACE, NodeLinkage );
network = netInterface->Network;
if ( NmpIsNetworkForInternalUse(network) &&
NmpIsInterfaceRegistered(netInterface)
)
{
if ( netInterface == TargetInterface ) {
status = ClusnetSetInterfacePriority(
NmClusnetHandle,
netInterface->Node->NodeId,
netInterface->Network->ShortId,
TargetInterfacePriority
);
} else {
status = ClusnetSetInterfacePriority(
NmClusnetHandle,
netInterface->Node->NodeId,
netInterface->Network->ShortId,
Priority
);
}
}
if ( status != ERROR_SUCCESS ) {
break;
}
}
return(status);
} // NmpSetNodeInterfacePriority
/////////////////////////////////////////////////////////////////////////////
//
// Node eviction utilities
//
/////////////////////////////////////////////////////////////////////////////
DWORD
NmpCleanseRegistry(
IN LPCWSTR NodeId,
IN HLOCALXSACTION Xaction
)
/*++
Routine Description:
Removes all references to the specified node from the cluster
registry.
Arguments:
Node - Supplies the node that is being evicted.
Return Value:
ERROR_SUCCESS if successful
Win32 error code otherwise
--*/
{
NM_EVICTION_CONTEXT context;
context.NodeId = NodeId;
context.Xaction = Xaction;
context.Status = ERROR_SUCCESS;
//
// Remove this node from the possible owner list of
// each resource type.
//
OmEnumObjects(
ObjectTypeResType,
NmpCleanseResTypeCallback,
&context,
NULL
);
if (context.Status == ERROR_SUCCESS) {
//
// Remove this node from the preferred owner list of
// each group.
//
OmEnumObjects(
ObjectTypeGroup,
NmpCleanseGroupCallback,
&context,
NULL
);
}
if (context.Status == ERROR_SUCCESS) {
//
// Remove this node from the possible owner list of
// each resource.
//
OmEnumObjects(
ObjectTypeResource,
NmpCleanseResourceCallback,
&context,
NULL
);
}
return(context.Status);
} // NmpCleanseRegistry
BOOL
NmpCleanseGroupCallback(
IN PNM_EVICTION_CONTEXT Context,
IN PVOID Context2,
IN PFM_GROUP Group,
IN LPCWSTR GroupName
)
/*++
Routine Description:
Group enumeration callback for removing an evicted node from the
group's preferred owners list.
Arguments:
Context - Supplies the node ID of the evicted node and other context info.
Context2 - Not used
Group - Supplies the group.
GroupName - Supplies the group's name.
Return Value:
TRUE - to indicate that the enumeration should continue.
--*/
{
HDMKEY groupKey;
DWORD status;
//
// Open the group's key.
//
groupKey = DmOpenKey(DmGroupsKey, GroupName, KEY_READ | KEY_WRITE);
if (groupKey != NULL) {
status = DmLocalRemoveFromMultiSz(
Context->Xaction,
groupKey,
CLUSREG_NAME_GRP_PREFERRED_OWNERS,
Context->NodeId
);
if (status == ERROR_FILE_NOT_FOUND) {
status = ERROR_SUCCESS;
}
DmCloseKey(groupKey);
}
else {
status = GetLastError();
}
Context->Status = status;
if (status != ERROR_SUCCESS) {
return(FALSE);
}
else {
return(TRUE);
}
} // NmpCleanseGroupCallback
BOOL
NmpCleanseResourceCallback(
IN PNM_EVICTION_CONTEXT Context,
IN PVOID Context2,
IN PFM_RESOURCE Resource,
IN LPCWSTR ResourceName
)
/*++
Routine Description:
Group enumeration callback for removing an evicted node from the
resource's possible owner's list.
Also deletes any node-specific parameters from the resource's registry
key.
Arguments:
Context - Supplies the node ID of the evicted node and other context info.
Context2 - Not used
Resource - Supplies the resource.
ResourceName - Supplies the resource's name.
Return Value:
TRUE - to indicate that the enumeration should continue.
--*/
{
HDMKEY resourceKey;
HDMKEY paramKey;
HDMKEY subKey;
DWORD status;
//
// Open the resource's key.
//
resourceKey = DmOpenKey(
DmResourcesKey,
ResourceName,
KEY_READ | KEY_WRITE
);
if (resourceKey != NULL) {
status = DmLocalRemoveFromMultiSz(
Context->Xaction,
resourceKey,
CLUSREG_NAME_RES_POSSIBLE_OWNERS,
Context->NodeId
);
if ((status == ERROR_SUCCESS) || (status == ERROR_FILE_NOT_FOUND)) {
paramKey = DmOpenKey(
resourceKey,
CLUSREG_KEYNAME_PARAMETERS,
KEY_READ | KEY_WRITE
);
if (paramKey != NULL) {
status = DmLocalDeleteTree(
Context->Xaction,
paramKey,
Context->NodeId
);
DmCloseKey(paramKey);
}
else {
status = GetLastError();
}
}
DmCloseKey(resourceKey);
}
else {
status = GetLastError();
}
if (status == ERROR_FILE_NOT_FOUND) {
status = ERROR_SUCCESS;
}
Context->Status = status;
if (status != ERROR_SUCCESS) {
return(FALSE);
}
else {
return(TRUE);
}
} // NmpCleanseResourceCallback
BOOL
NmpCleanseResTypeCallback(
IN PNM_EVICTION_CONTEXT Context,
IN PVOID Context2,
IN PFM_RESTYPE pResType,
IN LPCWSTR pszResTypeName
)
/*++
Routine Description:
Group enumeration callback for removing an evicted node from the
resource type's possible owner's list.
Also deletes any node-specific parameters from the resource types's registry
key.
Arguments:
Context - Supplies the node ID of the evicted node and other context info.
Context2 - Not used
pResType - Supplies the resource type.
pszResTypeeName - Supplies the resource type's name.
Return Value:
TRUE - to indicate that the enumeration should continue.
--*/
{
HDMKEY hResTypeKey;
HDMKEY paramKey;
HDMKEY subKey;
DWORD status;
//
// Open the resource's key.
//
hResTypeKey = DmOpenKey(
DmResourceTypesKey,
pszResTypeName,
KEY_READ | KEY_WRITE
);
if (hResTypeKey != NULL) {
status = DmLocalRemoveFromMultiSz(
Context->Xaction,
hResTypeKey,
CLUSREG_NAME_RESTYPE_POSSIBLE_NODES,
Context->NodeId
);
if ((status == ERROR_SUCCESS) || (status == ERROR_FILE_NOT_FOUND)) {
paramKey = DmOpenKey(
hResTypeKey,
CLUSREG_KEYNAME_PARAMETERS,
KEY_READ | KEY_WRITE
);
if (paramKey != NULL) {
status = DmLocalDeleteTree(
Context->Xaction,
paramKey,
Context->NodeId
);
DmCloseKey(paramKey);
}
else {
status = GetLastError();
}
}
DmCloseKey(hResTypeKey);
}
else {
status = GetLastError();
}
if (status == ERROR_FILE_NOT_FOUND) {
status = ERROR_SUCCESS;
}
Context->Status = status;
if (status != ERROR_SUCCESS) {
return(FALSE);
}
else {
return(TRUE);
}
} // NmpCleanseResTypeCallback
/////////////////////////////////////////////////////////////////////////////
//
// Node failure handler
//
/////////////////////////////////////////////////////////////////////////////
VOID
NmpNodeFailureHandler(
CL_NODE_ID NodeId,
LPVOID NodeFailureContext
)
{
return;
}
/////////////////////////////////////////////////////////////////////////////
//
// Miscellaneous routines
//
/////////////////////////////////////////////////////////////////////////////
//SS: when the node objects are created, their product suite is
//assumed to be Enterprise(aka Advanced Server) - This is because
//the joining interface doesnt allow the joiner to provide the node
//suite type and we didnt want to muck with it at a late state in
//shipping because it affects mixed mode clusters.
//SO, we fixup the structures after NmPerformFixups is called
//and calculate the cluster node limit
DWORD NmpRefreshNodeObjects(
)
{
NM_NODE_AUX_INFO NodeAuxInfo;
PLIST_ENTRY pListEntry;
PNM_NODE pNmNode;
WCHAR szNodeId[6];
DWORD dwStatus = ERROR_SUCCESS;
NmpAcquireLock();
for ( pListEntry = NmpNodeList.Flink;
pListEntry != &NmpNodeList;
pListEntry = pListEntry->Flink )
{
pNmNode = CONTAINING_RECORD(pListEntry, NM_NODE, Linkage);
wsprintf(szNodeId, L"%u", pNmNode->NodeId);
//read the information from the registry
NmpGetNodeAuxInfo(szNodeId, &NodeAuxInfo);
//update the node structure
pNmNode->ProductSuite = NodeAuxInfo.ProductSuite;
//SS: This is ugly---we should pass in the product suits early on.
//we dont know that the versions have changed, so should we generate
//a cluster_change_node_property event?
//Also the fixup interface needs to to be richer so that the postcallback
//function knows whether it is a form fixup or a join fixup and if it
//is a join fixup, which node is joining. This could certainly optimize
//some of the fixup processing
ClusterEvent(CLUSTER_EVENT_NODE_PROPERTY_CHANGE, pNmNode);
}
NmpReleaseLock();
return(dwStatus);
}
BOOLEAN
NmpIsAddNodeAllowed(
IN DWORD NewNodeProductSuite,
IN DWORD RegistryNodeLimit,
OUT LPDWORD EffectiveNodeLimit OPTIONAL
)
/*++
Routine Description:
Determines whether a new node can be added to the cluste membership.
The membership size limit decision is based on the product suites
of the cluster and the new node. If the registry override exists,
we will use that limit instead.
Arguments:
NewNodeProductSuite - The product suite identifier for the proposed
new member node.
RegistryNodeLimit - The membership size override value stored in the
cluster database.
EffectiveNodeLimit - On output, contains the membership size limit
that was calculated for this cluster.
Return Value:
TRUE if the new node may be added to the cluster. FALSE otherwise.
Notes:
Called with NmpLock held.
--*/
{
DWORD nodeLimit;
DWORD newNodeProductLimit;
DWORD currentNodeCount;
//
// Check if we already have the maximum number of nodes allowed in
// this cluster, based on the the product suites of the cluster and
// the joiner. If the registry override exists, we will use that
// limit instead.
//
newNodeProductLimit = ClRtlGetDefaultNodeLimit(NewNodeProductSuite);
currentNodeCount = NmGetCurrentNumberOfNodes();
nodeLimit = RegistryNodeLimit;
if (nodeLimit == 0) {
//
// No override in the registry.
// Limit is minimum of cluster's limit and new node's limit
//
nodeLimit = min(CsClusterNodeLimit, newNodeProductLimit);
}
//
// The runtime limit cannot exceed the compile time limit.
//
if (nodeLimit > NmMaxNodeId) {
nodeLimit = NmMaxNodeId;
}
if (currentNodeCount >= nodeLimit) {
return(FALSE);
}
if (EffectiveNodeLimit != NULL) {
*EffectiveNodeLimit = nodeLimit;
}
return(TRUE);
} // NmpIsAddNodeAllowed
DWORD
NmpAddNode(
IN LPCWSTR NewNodeName,
IN DWORD NewNodeHighestVersion,
IN DWORD NewNodeLowestVersion,
IN DWORD NewNodeProductSuite,
IN DWORD RegistryNodeLimit
)
/*++
Routine Description:
Adds a new node to the cluster by selecting an ID and
issuing a global update.
Arguments:
NewNodeName - A pointer to a string containing the name of the
new node.
NewNodeHighestVersion - The highest cluster version number that the
new node can support.
NewNodeLowestVersion - The lowest cluster version number that the
new node can support.
NewNodeProductSuite - The product suite identifier for the new node.
Return Value:
A Win32 status code.
Notes:
Called with NmpLock held.
--*/
{
DWORD status;
DWORD nodeId;
DWORD nodeLimit;
ClRtlLogPrint(LOG_NOISE,
"[NMJOIN] Processing request to add node '%1!ws!' to "
"the cluster.\n",
NewNodeName
);
if (NmpAddNodeId != ClusterInvalidNodeId) {
//
// An add is already in progress. Return an error.
//
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] Cannot add node '%1!ws!' to the cluster because "
"another add node operation is in progress. Retry later.\n",
NewNodeName
);
return(ERROR_CLUSTER_JOIN_IN_PROGRESS);
}
if (!NmpIsAddNodeAllowed(
NewNodeProductSuite,
RegistryNodeLimit,
&nodeLimit
)
)
{
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] Cannot add node '%1!ws!' to the cluster. "
"The cluster already contains the maximum number of nodes "
"allowed by the product licenses of the current member nodes "
"and the proposed new node.\n",
NewNodeName
);
return(ERROR_LICENSE_QUOTA_EXCEEDED);
}
//
// Find a free node ID.
//
for (nodeId=ClusterMinNodeId; nodeId<=nodeLimit; nodeId++) {
if (NmpIdArray[nodeId] == NULL) {
//
// Found an available node ID.
//
NmpAddNodeId = nodeId;
ClRtlLogPrint(LOG_NOISE,
"[NMJOIN] Allocated node ID '%1!u!' for new node '%2!ws!'\n",
NmpAddNodeId,
NewNodeName
);
break;
}
}
//
// Since the license test passed, it should be impossible for us to
// find no free slots in the node table.
//
CL_ASSERT(NmpAddNodeId != ClusterInvalidNodeId);
if (NmpAddNodeId == ClusterInvalidNodeId) {
ClRtlLogPrint(LOG_UNUSUAL,
"[NMJOIN] Cannot add node '%1!ws!' to the cluster because "
"no slots are available in the node table.\n"
);
return(ERROR_LICENSE_QUOTA_EXCEEDED);
}
NmpReleaseLock();
status = GumSendUpdateEx(
GumUpdateMembership,
NmUpdateAddNode,
5,
sizeof(NmpAddNodeId),
&NmpAddNodeId,
NM_WCSLEN(NewNodeName),
NewNodeName,
sizeof(NewNodeHighestVersion),
&NewNodeHighestVersion,
sizeof(NewNodeLowestVersion),
&NewNodeLowestVersion,
sizeof(NewNodeProductSuite),
&NewNodeProductSuite
);
NmpAcquireLock();
//
// Reset the global serialization variable.
//
CL_ASSERT(NmpAddNodeId == nodeId);
NmpAddNodeId = ClusterInvalidNodeId;
return(status);
} // NmpAddNode
VOID
NmpTerminateRpcsToNode(
DWORD NodeId
)
/*++
Routine Description:
Cancels all outstanding RPCs to the specified node.
Arguments:
NodeId - The ID of the node for which calls should be cancelled.
Return Value:
None
--*/
{
LIST_ENTRY *pEntry, *pStart;
PNM_INTRACLUSTER_RPC_THREAD pRpcTh;
RPC_STATUS status;
#if DBG
BOOLEAN startTimer = FALSE;
#endif // DBG
CL_ASSERT((NodeId >= ClusterMinNodeId) && (NodeId <= NmMaxNodeId));
CL_ASSERT(NmpIntraClusterRpcArr != NULL);
NmpAcquireRPCLock();
pEntry = pStart = &NmpIntraClusterRpcArr[NodeId];
pEntry = pEntry->Flink;
while(pEntry != pStart) {
pRpcTh = CONTAINING_RECORD(pEntry, NM_INTRACLUSTER_RPC_THREAD, Linkage);
status = RpcCancelThreadEx(pRpcTh->Thread, 0);
pRpcTh->Cancelled = TRUE;
if(status != RPC_S_OK) {
ClRtlLogPrint(LOG_UNUSUAL,
"[NM] Failed to cancel RPC to node %1!u! by thread "
"x%2!x!, status %3!u!.\n",
NodeId,
pRpcTh->ThreadId,
status
);
}
else {
ClRtlLogPrint(LOG_NOISE,
"[NM] Cancelled RPC to node %1!u! by thread x%2!x!.\n",
NodeId,
pRpcTh->ThreadId
);
#if DBG
startTimer = TRUE;
#endif // DBG
}
pEntry = pEntry->Flink;
}
#if DBG
//
// Now start a timer to make sure that all cancelled RPCs return to
// their callers within a reasonable amount of time.
//
if (startTimer) {
NmpRpcTimer = NM_RPC_TIMEOUT;
}
#endif // DBG
NmpReleaseRPCLock();
return;
} // NmTerminateRpcsToNode
#if DBG
VOID
NmpRpcTimerTick(
DWORD MsTickInterval
)
/*++
Routine Description:
Decrements a timer used to ensure that all cancelled RPCs to a dead
node return to their callers within a reasonable amount of time.
Arguments:
MsTickInterval - The time, in milliseconds, that has elapsed since this
routine was last invoked.
Return Value:
None
--*/
{
DWORD ndx;
LIST_ENTRY *pEntry, *pStart;
PNM_INTRACLUSTER_RPC_THREAD pRpcTh;
if(NmpRpcTimer == 0)
return;
NmpAcquireRPCLock();
if (NmpRpcTimer > MsTickInterval) {
NmpRpcTimer -= MsTickInterval;
}
else {
BOOLEAN stopClusSvc=FALSE;
NmpRpcTimer = 0;
for(ndx=0;ndx<=NmMaxNodeId;ndx++) {
pStart = pEntry = &NmpIntraClusterRpcArr[ndx];
pEntry = pEntry->Flink;
while(pEntry != pStart) {
pRpcTh = CONTAINING_RECORD(
pEntry,
NM_INTRACLUSTER_RPC_THREAD,
Linkage
);
if(pRpcTh->Cancelled == TRUE) {
ClRtlLogPrint( LOG_CRITICAL,
"[NM] Cancelled RPC to node %1!u! by thread x%2!x! "
"is still lingering after %3!u! seconds.\n",
ndx,
pRpcTh->ThreadId,
(NM_RPC_TIMEOUT/1000)
);
stopClusSvc = TRUE;
}
pEntry = pEntry->Flink;
}
}
if(stopClusSvc) {
DebugBreak();
}
}
NmpReleaseRPCLock();
return;
} // NmpRpcTimerTick
#endif // DBG