Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1228 lines
27 KiB

/*++
Copyright (c) 1996 Microsoft Corporation
Module Name:
fmclient.c
Abstract:
Cluster client side routines for RPC remote calls.
Author:
Rod Gamache (rodga) 8-Mar-1996
Revision History:
--*/
#include "fmp.h"
#define LOG_MODULE FMCLIENT
DWORD
FmcOnlineGroupRequest(
IN PFM_GROUP Group
)
/*++
Routine Description:
This routine requests (THE) remote system to bring the Group Online.
Arguments:
Group - The Group to bring online.
Returns:
ERROR_SUCCESS if successful.
Win32 error code on failure.
--*/
{
DWORD status;
DWORD OwnerId;
CL_ASSERT(Group->OwnerNode != NmLocalNode);
CL_ASSERT(Group->OwnerNode != NULL);
OwnerId = NmGetNodeId(Group->OwnerNode);
status = FmsOnlineGroupRequest( Session[OwnerId],
OmObjectId(Group) );
return(status);
} // FmcOnlineGroupRequest
DWORD
FmcOfflineGroupRequest(
IN PFM_GROUP Group
)
/*++
Routine Description:
This routine requests a remote system to take the Group Offline.
Arguments:
Group - The Group to take online.
Returns:
ERROR_SUCCESS if successful.
Win32 error code on failure.
--*/
{
DWORD status;
DWORD OwnerId;
CL_ASSERT(Group->OwnerNode != NmLocalNode);
CL_ASSERT(Group->OwnerNode != NULL);
OwnerId = NmGetNodeId(Group->OwnerNode);
CL_ASSERT(Session[OwnerId] != NULL);
status = FmsOfflineGroupRequest( Session[OwnerId],
OmObjectId(Group) );
return(status);
} // FmcOfflineGroupRequest
DWORD
FmcMoveGroupRequest(
IN PFM_GROUP Group,
IN PNM_NODE DestinationNode OPTIONAL
)
/*++
Routine Description:
This routine requests (THE) remote system to move the Group there.
Arguments:
Group - The Group to bring online.
DestinationNode - The node to move the Group to.
Returns:
ERROR_SUCCESS if successful.
Win32 error code on failure.
Notes:
The Group lock must be held on entry.
The Group lock is releaseed before returning.
--*/
{
DWORD status;
DWORD OwnerId;
CL_ASSERT(Group->OwnerNode != NmLocalNode);
#if 1
if ( Group ->OwnerNode == NULL ) {
ClRtlLogPrint(LOG_ERROR,
"[FM] MoveRequest for group with no owner!\n");
return(ERROR_HOST_NODE_NOT_AVAILABLE);
}
#endif
CL_ASSERT(Group->OwnerNode != NULL);
OwnerId = NmGetNodeId(Group->OwnerNode);
FmpReleaseLocalGroupLock( Group );
if (DestinationNode != NULL) {
status = FmsMoveGroupRequest( Session[OwnerId],
OmObjectId(Group ),
OmObjectId(DestinationNode));
} else {
status = FmsMoveGroupRequest( Session[OwnerId],
OmObjectId(Group ),
NULL);
}
return(status);
} // FmcMoveGroupRequest
DWORD
FmcTakeGroupRequest(
IN PNM_NODE DestinationNode,
IN LPCWSTR GroupId,
IN PRESOURCE_ENUM ResourceList
)
/*++
Routine Description:
This routine requests a remote system to move the Group there.
Arguments:
DestinationNode - The destination node
GroupId - The Id of the Group to be moved.
ResourceList - The list of the resources and their states.
Returns:
ERROR_SUCCESS if successful.
Win32 error code on failure.
--*/
{
DWORD status=ERROR_SUCCESS;
RPC_BINDING_HANDLE Binding;
PFM_GROUP group = NULL;
DWORD nodeId;
//
// If the remote session is not established, then return failure.
//
if ( gpQuoResource == NULL ) {
CsInconsistencyHalt(ERROR_INVALID_OPERATION_ON_QUORUM);
}
group = OmReferenceObjectById( ObjectTypeGroup, GroupId );
if ( group == NULL ) {
CsInconsistencyHalt(ERROR_GROUP_NOT_AVAILABLE);
}
if ( gpQuoResource->Group == group ) {
// Quorum group
// We don't need a lock on this usage, since there is only one
Binding = FmpRpcQuorumBindings[NmGetNodeId(DestinationNode)];
if ( Binding == NULL ) {
ClRtlLogPrint(LOG_ERROR,"[FM] TakeRequest and no remote binding available\n");
OmDereferenceObject( group );
return(ERROR_HOST_NODE_NOT_AVAILABLE);
}
OmDereferenceObject( group );
nodeId = NmGetNodeId(DestinationNode);
try {
NmStartRpc(nodeId);
status = FmsTakeGroupRequest( Binding,
GroupId,
ResourceList );
} finally {
NmEndRpc(nodeId);
if( status != RPC_S_OK ) {
NmDumpRpcExtErrorInfo(status);
}
}
} else {
// Non-quorum group
OmDereferenceObject( group );
Binding = FmpRpcBindings[NmGetNodeId(DestinationNode)];
if ( Binding == NULL ) {
ClRtlLogPrint(LOG_ERROR,"[FM] TakeRequest and no remote binding available\n");
return(ERROR_HOST_NODE_NOT_AVAILABLE);
}
// This is a shared binding, so serialize usage.
//
// Charlie Wickham (charlwi) - 10/30/00
//
// 185575: removing use of unique RPC binding handles hence no longer
// any need to serialize take group requests.
//
// FmpAcquireBindingLock();
//
// Chittur Subbaraman (chitturs) - 9/30/99
//
// Enclose the RPC within a "try-finally" block so that the
// lock is released regardless of whether the RPC succeeds.
// Note that the caller of FmcTakeGroupRequest encloses
// that function in a "try-except" block.
//
nodeId = NmGetNodeId(DestinationNode);
try {
NmStartRpc(nodeId);
status = FmsTakeGroupRequest( Binding,
GroupId,
ResourceList );
} finally {
NmEndRpc(nodeId);
if( status != RPC_S_OK ) {
NmDumpRpcExtErrorInfo(status);
}
// FmpReleaseBindingLock();
}
}
return(status);
} // FmcTakeGroupRequest
DWORD
FmcOnlineResourceRequest(
IN PFM_RESOURCE Resource
)
/*++
Routine Description:
This routine requests (THE) remote system to bring the Resource Online.
Arguments:
Resource - The resource to bring online.
Returns:
ERROR_SUCCESS if successful.
Win32 error code on failure.
--*/
{
DWORD status;
DWORD NodeId;
CL_ASSERT(Resource->Group->OwnerNode != NULL);
NodeId = NmGetNodeId(Resource->Group->OwnerNode);
CL_ASSERT(Session[NodeId] != NULL);
status = FmsOnlineResourceRequest( Session[NodeId],
OmObjectId(Resource) );
return(status);
} // FmcOnlineResourceRequest
DWORD
FmcOfflineResourceRequest(
IN PFM_RESOURCE Resource
)
/*++
Routine Description:
This routine requests (THE) remote system to take the Resource Offline.
Arguments:
Resource - The resource to take offline.
Returns:
ERROR_SUCCESS if successful.
Win32 error code on failure.
--*/
{
DWORD status;
DWORD NodeId;
CL_ASSERT(Resource->Group->OwnerNode != NULL);
NodeId = NmGetNodeId(Resource->Group->OwnerNode);
CL_ASSERT(Session[NodeId] != NULL);
status = FmsOfflineResourceRequest( Session[NodeId],
OmObjectId(Resource) );
return(status);
} // FmcOfflineResourceRequest
DWORD
FmcChangeResourceNode(
IN PFM_RESOURCE Resource,
IN PNM_NODE Node,
IN BOOL Add
)
/*++
Routine Description:
This routine requests the owner of the resource to perform the change
resource node operation.
Arguments:
Resource - The resource to change the resource node.
Node - The node to be added/removed from the resource list.
Add - Specifies whether to add or remove the given node.
Returns:
ERROR_SUCCESS if successful.
Win32 error code on failure.
Note:
The resource's lock must be held on entry. It is released prior to
returning.
--*/
{
DWORD status;
DWORD NodeId;
CL_ASSERT(Resource->Group->OwnerNode != NULL);
NodeId = NmGetNodeId(Resource->Group->OwnerNode);
CL_ASSERT(Session[NodeId] != NULL);
FmpReleaseLocalResourceLock( Resource );
status = FmsChangeResourceNode( Session[NodeId],
OmObjectId(Resource),
OmObjectId(Node),
Add );
return(status);
} // FmcChangeResourceNode
DWORD
FmcArbitrateResource(
IN PFM_RESOURCE Resource
)
/*++
Routine Description:
This routine requests a remote system to arbitrate a resource.
Arguments:
Resource - The resource to arbitrate.
Returns:
ERROR_SUCCESS if successful.
Win32 error code on failure.
--*/
{
DWORD status;
DWORD nodeId;
CL_ASSERT(Resource->Group->OwnerNode != NULL);
nodeId = NmGetNodeId(Resource->Group->OwnerNode);
CL_ASSERT(Session[nodeId] != NULL);
status = FmsArbitrateResource( Session[nodeId],
OmObjectId(Resource) );
return(status);
} // FmcArbitrateResource
VOID
FmcDeleteEnum(
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.
--*/
{
DWORD i;
if ( Enum == NULL ) {
return;
}
for ( i = 0; i < Enum->EntryCount; i++ ) {
MIDL_user_free(Enum->Entry[i].Id);
}
MIDL_user_free(Enum);
return;
} // FmcDeleteEnum
DWORD
FmcFailResource(
IN PFM_RESOURCE Resource
)
/*++
Routine Description:
This routine requests a remote system to fail a resource.
Arguments:
Resource - The resource to fail.
Returns:
ERROR_SUCCESS if successful.
Win32 error code on failure.
--*/
{
DWORD status;
DWORD nodeId;
CL_ASSERT(Resource->Group->OwnerNode != NULL);
nodeId = NmGetNodeId(Resource->Group->OwnerNode);
CL_ASSERT(Session[nodeId] != NULL);
status = FmsFailResource( Session[nodeId],
OmObjectId(Resource) );
return(status);
} // FmcFailResource
PFM_RESOURCE
FmcCreateResource(
IN PFM_GROUP Group,
IN LPWSTR ResourceId,
IN LPCWSTR ResourceName,
IN LPCWSTR ResourceType,
IN DWORD dwFlags
)
/*++
Routine Description:
This routine requests a remote system to create a resource. The
remote system should 'own' the group.
Arguments:
Group - The group that the resource should be created inside.
ResourceId - The id of the resource to create.
ResourceName - The name of the resource to create.
ResourceType - Resource type name
dwFlags - Flags for the resource.
Returns:
ERROR_SUCCESS if successful.
Win32 error code on failure.
Notes:
The Group lock should be held... and is released by this routine.
--*/
{
DWORD status;
DWORD nodeId;
PFM_RESOURCE resource = NULL;
DWORD dwClusterHighestVersion;
CL_ASSERT(Group->OwnerNode != NULL);
nodeId = NmGetNodeId(Group->OwnerNode);
CL_ASSERT(Session[nodeId] != NULL);
FmpReleaseLocalGroupLock( Group );
NmGetClusterOperationalVersion( &dwClusterHighestVersion,
NULL,
NULL );
if ( CLUSTER_GET_MAJOR_VERSION( dwClusterHighestVersion ) <
NT51_MAJOR_VERSION )
{
status = FmsCreateResource( Session[nodeId],
OmObjectId(Group),
ResourceId,
ResourceName );
} else
{
status = FmsCreateResource2( Session[nodeId],
OmObjectId(Group),
ResourceId,
ResourceName,
ResourceType,
dwFlags );
}
if ( status == ERROR_SUCCESS ) {
resource = OmReferenceObjectById( ObjectTypeResource,
ResourceId );
if ( resource != NULL ) {
OmDereferenceObject( resource );
}
} else {
SetLastError(status);
}
return(resource);
} // FmcCreateResource
DWORD
FmcDeleteResource(
IN PFM_RESOURCE Resource
)
/*++
Routine Description:
This routine requests a remote system to delete a resource.
Arguments:
Resource - The resource to delete.
Returns:
ERROR_SUCCESS if successful.
Win32 error code on failure.
Notes:
The Resource lock should be held... and is released by this routine.
--*/
{
DWORD status;
DWORD nodeId;
CL_ASSERT(Resource->Group->OwnerNode != NULL);
nodeId = NmGetNodeId(Resource->Group->OwnerNode);
CL_ASSERT(Session[nodeId] != NULL);
FmpReleaseLocalResourceLock( Resource );
status = FmsDeleteResource( Session[nodeId],
OmObjectId(Resource) );
return(status);
} // FmcDeleteResource
DWORD
FmcResourceControl(
IN PNM_NODE Node,
IN PFM_RESOURCE Resource,
IN DWORD ControlCode,
IN PUCHAR InBuffer,
IN DWORD InBufferSize,
OUT PUCHAR OutBuffer,
IN DWORD OutBufferSize,
OUT LPDWORD BytesReturned,
OUT LPDWORD Required
)
/*++
Routine Description:
This routine passes a resource control request to a remote system.
Arguments:
Node - the remote node to send the request to.
Resource - the resource to handle the request.
ControlCode - the control code for this request.
InBuffer - the input buffer.
InBufferSize - the size of the input buffer.
OutBuffer - the output buffer.
OutBuffer - the size of the output buffer.
BytesReturned - the length of the returned data.
Required - the number of bytes required if OutBuffer is not big enough.
Returns:
ERROR_SUCCESS if successful.
Win32 error code on failure.
--*/
{
DWORD status;
DWORD NodeId;
DWORD Dummy;
DWORD dwTmpBytesReturned;
DWORD dwTmpBytesRequired;
NodeId = NmGetNodeId(Node);
if ((NmGetNodeState(Node) != ClusterNodeUp) &&
( NmGetNodeState(Node) != ClusterNodePaused))
{
return(ERROR_HOST_NODE_NOT_AVAILABLE);
}
CL_ASSERT(Session[NodeId] != NULL);
//to take care of the output reference pointer which cannot be NULL.
if (!OutBuffer)
{
OutBuffer = (PUCHAR)&Dummy;
OutBufferSize = 0;
}
if (!BytesReturned)
BytesReturned = &dwTmpBytesReturned;
if (!Required)
Required = &dwTmpBytesRequired;
status = FmsResourceControl( Session[NodeId],
OmObjectId(Resource),
ControlCode,
InBuffer,
InBufferSize,
OutBuffer,
OutBufferSize,
BytesReturned,
Required );
return(status);
} // FmcResourceControl
DWORD
FmcResourceTypeControl(
IN PNM_NODE Node,
IN LPCWSTR ResourceTypeName,
IN DWORD ControlCode,
IN PUCHAR InBuffer,
IN DWORD InBufferSize,
OUT PUCHAR OutBuffer,
IN DWORD OutBufferSize,
OUT LPDWORD BytesReturned,
OUT LPDWORD Required
)
/*++
Routine Description:
This routine passes a resource type control request to a remote system.
Arguments:
Node - the remote node to send the request to.
ResourceTypeName - the name of the resource type to handle the request.
ControlCode - the control code for this request.
InBuffer - the input buffer.
InBufferSize - the size of the input buffer.
OutBuffer - the output buffer.
OutBuffer - the size of the output buffer.
BytesReturned - the length of the returned data.
Required - the number of bytes required if OutBuffer is not big enough.
Returns:
ERROR_SUCCESS if successful.
Win32 error code on failure.
--*/
{
DWORD status;
DWORD NodeId;
NodeId = NmGetNodeId(Node);
if (( NmGetNodeState(Node) != ClusterNodeUp ) &&
( NmGetNodeState(Node) != ClusterNodePaused )) {
return(ERROR_HOST_NODE_NOT_AVAILABLE);
}
CL_ASSERT(Session[NodeId] != NULL);
status = FmsResourceTypeControl( Session[NodeId],
ResourceTypeName,
ControlCode,
InBuffer,
InBufferSize,
OutBuffer,
OutBufferSize,
BytesReturned,
Required );
return(status);
} // FmcResourceTypeControl
DWORD
FmcGroupControl(
IN PNM_NODE Node,
IN PFM_GROUP Group,
IN DWORD ControlCode,
IN PUCHAR InBuffer,
IN DWORD InBufferSize,
OUT PUCHAR OutBuffer,
IN DWORD OutBufferSize,
OUT LPDWORD BytesReturned,
OUT LPDWORD Required
)
/*++
Routine Description:
This routine passes a resource control request to a remote system.
Arguments:
Node - the remote node to send the request to.
Group - the group to handle the request.
ControlCode - the control code for this request.
InBuffer - the input buffer.
InBufferSize - the size of the input buffer.
OutBuffer - the output buffer.
OutBuffer - the size of the output buffer.
BytesReturned - the length of the returned data.
Required - the number of bytes required if OutBuffer is not big enough.
Returns:
ERROR_SUCCESS if successful.
Win32 error code on failure.
--*/
{
DWORD status;
DWORD NodeId;
NodeId = NmGetNodeId(Node);
if (( NmGetNodeState(Node) != ClusterNodeUp ) &&
( NmGetNodeState(Node) != ClusterNodePaused )) {
return(ERROR_HOST_NODE_NOT_AVAILABLE);
}
CL_ASSERT(Session[NodeId] != NULL);
status = FmsGroupControl( Session[NodeId],
OmObjectId(Group),
ControlCode,
InBuffer,
InBufferSize,
OutBuffer,
OutBufferSize,
BytesReturned,
Required );
return(status);
} // FmcGroupControl
DWORD
FmcPrepareQuorumResChange(
IN PFM_RESOURCE Resource,
IN LPCWSTR lpszQuoLogPath,
IN DWORD dwMaxQuoLogSize
)
/*++
Routine Description:
This routine requests a the owner of a potential quorum resource
to prepare for quorum logging and registry replication.
Arguments:
Resource - The resource to on which we want to start logging.
lpszQuoLogPath - The Path where the cluster log files should be created.
dwMaxQuoLogSize - The new max Quorum Log Size.
Returns:
ERROR_SUCCESS if successful.
Win32 error code on failure.
--*/
{
DWORD status;
DWORD nodeId;
CL_ASSERT(Resource->Group->OwnerNode != NULL);
nodeId = NmGetNodeId(Resource->Group->OwnerNode);
CL_ASSERT(Session[nodeId] != NULL);
status = FmsPrepareQuorumResChange( Session[nodeId],
OmObjectId(Resource),
lpszQuoLogPath,
dwMaxQuoLogSize );
return(status);
} // FmcPrepareQuorumResChange
DWORD
FmcCompleteQuorumResChange(
IN PFM_RESOURCE pOldQuoRes,
IN LPCWSTR lpszOldQuoLogPath
)
/*++
Routine Description:
This routine requests a the owner of the previous quorum resource
to clean up after quorum resource change is complete.
Arguments:
pOldQuoRes - The resource to on which we want to start logging.
lpszOldQuoLogPath - The Path where the cluster log files should be created.
Returns:
ERROR_SUCCESS if successful.
Win32 error code on failure.
--*/
{
DWORD status;
DWORD nodeId;
CL_ASSERT(pOldQuoRes->Group->OwnerNode != NULL);
nodeId = NmGetNodeId(pOldQuoRes->Group->OwnerNode);
CL_ASSERT(Session[nodeId] != NULL);
status = FmsCompleteQuorumResChange( Session[nodeId],
OmObjectId(pOldQuoRes),
lpszOldQuoLogPath);
return(status);
} // FmcCompleteQuorumResChange
DWORD
FmcChangeResourceGroup(
IN PFM_RESOURCE pResource,
IN PFM_GROUP pNewGroup
)
/*++
Routine Description:
This routine requests the owner of the resource to move the resource
from one group to another.
Arguments:
Resource - The resource whose group is to be changed.
pNewGroup - The group to which the resource should be moved to.
Returns:
ERROR_SUCCESS if successful.
Win32 error code on failure.
Note:
The group locks for both the old and the new group must be held on entry.
They are release before making the rpc call.
--*/
{
DWORD status;
DWORD NodeId;
CL_ASSERT(pResource->Group->OwnerNode != NULL);
NodeId = NmGetNodeId(pResource->Group->OwnerNode);
CL_ASSERT(Session[NodeId] != NULL);
FmpReleaseLocalGroupLock( pResource->Group );
FmpReleaseLocalGroupLock( pNewGroup );
status = FmsChangeResourceGroup( Session[NodeId],
OmObjectId(pResource),
OmObjectId(pNewGroup));
return(status);
} // FmcChangeResourceNode
DWORD
FmcBackupClusterDatabase(
IN PFM_RESOURCE pQuoResource,
IN LPCWSTR lpszPathName
)
/*++
Routine Description:
This routine requests the owner of a potential quorum resource
to backup the quorum log and the checkpoint file to the
specified path. This function is called with the resource lock
held.
Arguments:
pQuoResource - The quorum resource.
lpszPathName - The directory path name where the files have to be
backed up. This path must be visible to the node
on which the quorum resource is online.
Returns:
ERROR_SUCCESS if successful.
Win32 error code on failure.
--*/
{
DWORD status;
DWORD nodeId;
CL_ASSERT( pQuoResource->Group->OwnerNode != NULL );
nodeId = NmGetNodeId( pQuoResource->Group->OwnerNode );
CL_ASSERT( Session[nodeId] != NULL );
//
// Chittur Subbaraman (chitturs) - 10/16/98
//
// Release the resource lock. Releasing the resource lock
// here can create a window during which this node thinks
// the other node is the owner and the other node thinks
// this node is the owner. But, unfortunately we've to treat
// this as an error case so that we don't run into deadlocks
// across multiple machines due to the lock being held while
// making the RPC.
//
FmpReleaseLocalResourceLock( pQuoResource );
status = FmsBackupClusterDatabase( Session[nodeId],
OmObjectId( pQuoResource ),
lpszPathName );
return( status );
} // FmcBackupClusterDatabase
/****
@func DWORD | FmcDeleteGroup| This makes a rpc call to the owner
of the group to handle the delete group request.
@parm IN PFM_GROUB | pGroup | The group that must be deleted.
@comm The owner node should make the GUM request to avoid deadlocks.
@rdesc Returns a result code. ERROR_SUCCESS on success.
****/
DWORD
FmcDeleteGroupRequest(
IN PFM_GROUP pGroup
)
{
DWORD dwOwnerId;
DWORD dwStatus;
dwOwnerId = NmGetNodeId(pGroup->OwnerNode);
CL_ASSERT(dwOwnerId != NmLocalNodeId);
//release the lock before making the rpc call
FmpReleaseLocalGroupLock( pGroup );
dwStatus = FmsDeleteGroupRequest( Session[dwOwnerId],
OmObjectId(pGroup)
);
return(dwStatus);
}
/****
@func DWORD | FmcAddResourceDependency | This makes an RPC to the
owner of the resource to handle the dependency addition.
@parm IN PFM_RESOURCE | pResource | The resource to add the
dependent resource.
@parm IN PFM_RESOURCE | pDependentResource | The dependent resource.
@comm The owner node should make the GUM request to avoid deadlocks.
@rdesc Returns an error code. ERROR_SUCCESS on success.
****/
DWORD
FmcAddResourceDependency(
IN PFM_RESOURCE pResource,
IN PFM_RESOURCE pDependentResource
)
{
DWORD dwOwnerId;
DWORD dwStatus;
dwOwnerId = NmGetNodeId( pResource->Group->OwnerNode );
CL_ASSERT( dwOwnerId != NmLocalNodeId );
//
// Release the lock before making the RPC call
//
FmpReleaseLocalResourceLock( pResource );
dwStatus = FmsAddResourceDependency( Session[dwOwnerId],
OmObjectId( pResource ),
OmObjectId( pDependentResource )
);
return( dwStatus );
}
/****
@func DWORD | FmcRemoveResourceDependency | This makes an RPC to the
owner of the resource to handle the dependency removal.
@parm IN PFM_RESOURCE | pResource | The resource to remove the
dependent resource from.
@parm IN PFM_RESOURCE | pDependentResource | The dependent resource.
@comm The owner node should make the GUM request to avoid deadlocks.
@rdesc Returns an error code. ERROR_SUCCESS on success.
****/
DWORD
FmcRemoveResourceDependency(
IN PFM_RESOURCE pResource,
IN PFM_RESOURCE pDependentResource
)
{
DWORD dwOwnerId;
DWORD dwStatus;
dwOwnerId = NmGetNodeId( pResource->Group->OwnerNode );
CL_ASSERT( dwOwnerId != NmLocalNodeId );
//
// Release the lock before making the RPC call
//
FmpReleaseLocalResourceLock( pResource );
dwStatus = FmsRemoveResourceDependency( Session[dwOwnerId],
OmObjectId( pResource ),
OmObjectId( pDependentResource )
);
return( dwStatus );
}