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.

767 lines
25 KiB

  1. /*++
  2. Copyright (c) 1996 Microsoft Corporation
  3. Module Name:
  4. worker.c
  5. Abstract:
  6. Failover Manager worker thread.
  7. Author:
  8. Mike Massa (mikemas) 12-Mar-1996
  9. Revision History:
  10. --*/
  11. #define UNICODE 1
  12. #include "fmp.h"
  13. #define LOG_MODULE WORKER
  14. CL_QUEUE FmpWorkQueue;
  15. //
  16. // Local Data
  17. //
  18. HANDLE FmpWorkerThreadHandle = NULL;
  19. //
  20. // Forward routines
  21. //
  22. BOOL
  23. FmpAddNodeGroupCallback(
  24. IN PVOID Context1,
  25. IN PVOID Context2,
  26. IN PVOID Object,
  27. IN LPCWSTR Name
  28. );
  29. //
  30. // Local routines
  31. //
  32. DWORD
  33. FmpWorkerThread(
  34. IN LPVOID Ignored
  35. )
  36. {
  37. DWORD status;
  38. PLIST_ENTRY entry;
  39. PWORK_ITEM workItem;
  40. DWORD running = TRUE;
  41. PFM_RESOURCE resource;
  42. PFMP_POSSIBLE_NODE possibleNode;
  43. DWORD i;
  44. ClRtlLogPrint(LOG_NOISE,"[FM] Worker thread running\n");
  45. while ( running ) {
  46. //
  47. // Check the FM work queue for work items
  48. //
  49. entry = ClRtlRemoveHeadQueue(&FmpWorkQueue);
  50. if ( entry == NULL ) {
  51. return(ERROR_SUCCESS);
  52. }
  53. workItem = CONTAINING_RECORD(entry,
  54. WORK_ITEM,
  55. ListEntry);
  56. //
  57. // FM no longer cares about node up events, make sure
  58. // we aren't getting any queued.
  59. //
  60. switch ( workItem->Event ) {
  61. case FM_EVENT_SHUTDOWN:
  62. ClRtlLogPrint(LOG_NOISE,"[FM] WorkerThread terminating...\n");
  63. running = FALSE;
  64. break;
  65. case FM_EVENT_RESOURCE_ADDED:
  66. resource = workItem->Context1;
  67. if ( resource->Monitor == NULL ) {
  68. FmpAcquireLocalResourceLock( resource );
  69. //
  70. // Chittur Subbaraman (chitturs) - 8/12/99
  71. //
  72. // Make sure the resource is not marked for delete.
  73. //
  74. if ( IS_VALID_FM_RESOURCE( resource ) )
  75. {
  76. FmpInitializeResource( resource, TRUE );
  77. }
  78. FmpReleaseLocalResourceLock( resource );
  79. }
  80. OmDereferenceObject( resource );
  81. break;
  82. case FM_EVENT_RESOURCE_DELETED:
  83. //
  84. // Tell the resource monitor to cleanup the resource.
  85. //
  86. resource = workItem->Context1;
  87. FmpAcquireLocalResourceLock( resource );
  88. // Now that no remaining resource depends on this resource and
  89. // this resource does not depend on any other resources, we can
  90. // terminate it in the resource monitor if the resource is not
  91. // already offline or failed
  92. //
  93. if ( (resource->Group->OwnerNode == NmLocalNode) &&
  94. ((resource->State != ClusterResourceOffline) &&
  95. (resource->State != ClusterResourceFailed))) {
  96. FmpRmTerminateResource(resource);
  97. }
  98. status = FmpRmCloseResource(resource);
  99. ClRtlLogPrint( LOG_NOISE,
  100. "[FM] WorkItem, delete resource <%1!ws!> status %2!u!\n",
  101. OmObjectName(resource),
  102. status );
  103. FmpReleaseLocalResourceLock( resource );
  104. OmDereferenceObject(resource);
  105. break;
  106. case FM_EVENT_GROUP_FAILED:
  107. FmpHandleGroupFailure( workItem->Context1, NULL );
  108. OmDereferenceObject( workItem->Context1 );
  109. break;
  110. case FM_EVENT_NODE_ADDED:
  111. //
  112. // We need to add this node to every resource's possible owners
  113. // list and to each group's preferred owners list.
  114. //
  115. OmEnumObjects( ObjectTypeGroup,
  116. FmpAddNodeGroupCallback,
  117. workItem->Context1,
  118. NULL );
  119. break;
  120. case FM_EVENT_NODE_EVICTED:
  121. //
  122. // Enumerate all the resources types to remove any PossibleNode references.
  123. //
  124. OmEnumObjects(ObjectTypeResType,
  125. FmpEnumResTypeNodeEvict,
  126. workItem->Context1,
  127. NULL);
  128. //
  129. // Enumerate all the resources to remove any PossibleNode references.
  130. //
  131. OmEnumObjects(ObjectTypeResource,
  132. FmpEnumResourceNodeEvict,
  133. workItem->Context1,
  134. NULL);
  135. //
  136. // Enumerate all the groups to remove any PreferredNode references
  137. //
  138. OmEnumObjects(ObjectTypeGroup,
  139. FmpEnumGroupNodeEvict,
  140. workItem->Context1,
  141. NULL);
  142. //Now dereference the object
  143. OmDereferenceObject( workItem->Context1 );
  144. break;
  145. #if 0
  146. case FM_EVENT_CLUSTER_PROPERTY_CHANGE:
  147. //this is for the cluster name change
  148. FmpClusterEventPropHandler((PFM_RESOURCE)workItem->Context1);
  149. //Now dereference the object
  150. OmDereferenceObject( workItem->Context1 );
  151. break;
  152. #endif
  153. case FM_EVENT_RESOURCE_CHANGE:
  154. // this is for a Add/Remove possible node request
  155. possibleNode = workItem->Context1;
  156. if ( possibleNode == NULL ) {
  157. break;
  158. }
  159. FmpRmResourceControl( possibleNode->Resource,
  160. possibleNode->ControlCode,
  161. (PUCHAR)OmObjectName(possibleNode->Node),
  162. ((lstrlenW(OmObjectName(possibleNode->Node)) + 1) * sizeof(WCHAR)),
  163. NULL,
  164. 0,
  165. NULL,
  166. NULL );
  167. // ignore status
  168. OmDereferenceObject( possibleNode->Resource );
  169. OmDereferenceObject( possibleNode->Node );
  170. LocalFree( possibleNode );
  171. break;
  172. case FM_EVENT_RESOURCE_PROPERTY_CHANGE:
  173. //
  174. // Generate a cluster wide event notification for this event.
  175. //
  176. ClusterWideEvent(
  177. CLUSTER_EVENT_RESOURCE_PROPERTY_CHANGE,
  178. workItem->Context1 // Resource
  179. );
  180. OmDereferenceObject( workItem->Context1 );
  181. break;
  182. case FM_EVENT_RES_RESOURCE_TRANSITION:
  183. FmpHandleResourceTransition(workItem->Context1, workItem->Context2);
  184. OmDereferenceObject(workItem->Context1);
  185. break;
  186. case FM_EVENT_RES_RESOURCE_FAILED:
  187. FmpProcessResourceEvents(workItem->Context1, ClusterResourceFailed,
  188. workItem->Context2);
  189. OmDereferenceObject( workItem->Context1 );
  190. break;
  191. case FM_EVENT_RES_RETRY_TIMER:
  192. resource= (PFM_RESOURCE)workItem->Context1;
  193. //Remove any pending watchdog timer
  194. if (resource->hTimer)
  195. {
  196. RemoveTimerActivity(resource->hTimer);
  197. resource->hTimer = NULL;
  198. }
  199. FmpAcquireLocalResourceLock(resource);
  200. // Check if this resource was deleted in the meanwhile,
  201. // or is not in failed state
  202. if( ( IS_VALID_FM_RESOURCE( resource ) ) &&
  203. ( resource->State == ClusterResourceFailed ) &&
  204. ( resource->PersistentState == ClusterResourceOnline ) )
  205. {
  206. // Check if we are the owner, if not ignore it
  207. if ( resource->Group->OwnerNode == NmLocalNode )
  208. {
  209. FmpProcessResourceEvents(resource,
  210. ClusterResourceFailed,
  211. ClusterResourceOnline);
  212. }
  213. }
  214. FmpReleaseLocalResourceLock(resource);
  215. OmDereferenceObject( workItem->Context1 );
  216. break;
  217. case FM_EVENT_INTERNAL_PROP_GROUP_STATE:
  218. FmpPropagateGroupState(workItem->Context1);
  219. OmDereferenceObject( workItem->Context1 );
  220. break;
  221. case FM_EVENT_INTERNAL_RETRY_ONLINE:
  222. {
  223. PFM_RESLIST_ONLINE_RETRY_INFO pFmOnlineRetryInfo;
  224. RemoveTimerActivity((HANDLE)workItem->Context2);
  225. pFmOnlineRetryInfo= workItem->Context1;
  226. CL_ASSERT(pFmOnlineRetryInfo);
  227. ClRtlLogPrint(LOG_NOISE,
  228. "[FM] FmpWorkerThread, retrying to online resourcelist\r\n");
  229. FmpOnlineResourceFromList(&(pFmOnlineRetryInfo->ResourceEnum),
  230. pFmOnlineRetryInfo->pGroup);
  231. //Free memory
  232. for (i =0; i< pFmOnlineRetryInfo->ResourceEnum.EntryCount; i++)
  233. LocalFree( pFmOnlineRetryInfo->ResourceEnum.Entry[i].Id );
  234. if (pFmOnlineRetryInfo->pGroup)
  235. OmDereferenceObject(pFmOnlineRetryInfo->pGroup);
  236. LocalFree(pFmOnlineRetryInfo);
  237. break;
  238. }
  239. case FM_EVENT_INTERNAL_RESOURCE_CHANGE_PARAMS:
  240. {
  241. BOOL bIsValidRes = TRUE;
  242. //
  243. // Now tell the resource monitor about the changes.
  244. //
  245. status = ERROR_SUCCESS;
  246. resource = (PFM_RESOURCE)workItem->Context1;
  247. FmpAcquireLocalResourceLock( resource );
  248. //
  249. // Chittur Subbaraman (chitturs) - 8/12/99
  250. //
  251. // Check whether the resource is marked for delete.
  252. //
  253. if ( !IS_VALID_FM_RESOURCE( resource ) )
  254. {
  255. bIsValidRes = FALSE;
  256. }
  257. FmpReleaseLocalResourceLock( resource );
  258. if( bIsValidRes )
  259. {
  260. status = FmpRmChangeResourceParams( resource );
  261. }
  262. if ( status != ERROR_SUCCESS )
  263. {
  264. ClRtlLogPrint(LOG_UNUSUAL,
  265. "[FM] FmpWorkerThread, failed to change resource "
  266. "parameters for %1!ws!, error %2!u!.\n",
  267. OmObjectId(resource),
  268. status );
  269. }
  270. OmDereferenceObject(resource);
  271. break;
  272. }
  273. case FM_EVENT_INTERNAL_ONLINE_GROUPLIST:
  274. {
  275. PGROUP_ENUM pGroupList = NULL;
  276. ClRtlLogPrint(LOG_NOISE,
  277. "[FM] FmpWorkerThread : Processing Node Down Group List\n");
  278. pGroupList = workItem->Context1;
  279. FmpOnlineGroupList(pGroupList, TRUE);
  280. FmpDeleteEnum(pGroupList);
  281. break;
  282. }
  283. case FM_EVENT_RESOURCE_NAME_CHANGE:
  284. {
  285. //
  286. // Chittur Subbaraman (chitturs) - 6/29/99
  287. //
  288. // Added this new event to handle resource name change
  289. // notifications to resource DLLs.
  290. //
  291. PFM_RES_CHANGE_NAME pResChangeName = NULL;
  292. DWORD dwStatus = ERROR_SUCCESS;
  293. pResChangeName = ( PFM_RES_CHANGE_NAME ) workItem->Context1;
  294. dwStatus = FmpRmResourceControl( pResChangeName->pResource,
  295. CLUSCTL_RESOURCE_SET_NAME,
  296. (PUCHAR) pResChangeName->szNewResourceName,
  297. ((lstrlenW(pResChangeName->szNewResourceName) + 1) * sizeof(WCHAR)),
  298. NULL,
  299. 0,
  300. NULL,
  301. NULL );
  302. ClRtlLogPrint(LOG_NOISE,
  303. "[FM] Worker thread handling FM_EVENT_RESOURCE_NAME_CHANGE event - FmpRmResourceControl returns %1!u! for resource %2!ws!\n",
  304. dwStatus,
  305. OmObjectId(pResChangeName->pResource));
  306. OmDereferenceObject( pResChangeName->pResource );
  307. LocalFree( pResChangeName );
  308. break;
  309. }
  310. default:
  311. ClRtlLogPrint(LOG_NOISE,
  312. "[FM] WorkerThread, unrecognized event %1!u!\n",
  313. workItem->Event);
  314. }
  315. //
  316. // Free the work item.
  317. //
  318. LocalFree( workItem );
  319. }
  320. return(ERROR_SUCCESS);
  321. } // FmpWorkerThread
  322. VOID
  323. FmpProcessResourceEvents(
  324. IN PFM_RESOURCE pResource,
  325. IN CLUSTER_RESOURCE_STATE NewState,
  326. IN CLUSTER_RESOURCE_STATE OldState
  327. )
  328. /*++
  329. Routine Description:
  330. Arguments:
  331. Return Value:
  332. Comments: This should not call PropagateResourceState(). FmpProcessResourceEvents
  333. acquires the group lock. The quorum resource state must be propagated without holding
  334. the group lock. FmpPropagateResourceState() must be called by FmpHandleResourceTransition.
  335. There is a slight window between when the event is received in FmpHandleResourceTransition()
  336. and when the actions corresponding to those are carried out in FmpProcessResourceEvents().
  337. In this window, another opposing action like offline/online might occur. But we dont
  338. worry about it since if there are waiting resources on this resource, those actions
  339. are not carried out.
  340. --*/
  341. {
  342. DWORD Status;
  343. BOOL bQuoChangeLockHeld = FALSE;
  344. CL_ASSERT(pResource != NULL);
  345. ChkFMState:
  346. if (!FmpFMGroupsInited)
  347. {
  348. DWORD dwRetryCount = 50;
  349. ACQUIRE_SHARED_LOCK(gQuoChangeLock);
  350. //FmFormNewClusterPhaseProcessing is in progress
  351. if (FmpFMFormPhaseProcessing)
  352. {
  353. ClRtlLogPrint(LOG_CRITICAL,
  354. "[FM] FmpProcessResourceEvents, resource notification from quorum resource during phase processing,sleep and retry\n");
  355. RELEASE_LOCK(gQuoChangeLock);
  356. Sleep(500);
  357. if (dwRetryCount--)
  358. goto ChkFMState;
  359. else
  360. {
  361. ClRtlLogPrint(LOG_CRITICAL,
  362. "[FM] FmpProcessResourceEvents, waited for too long\n");
  363. //terminate the process
  364. CL_ASSERT(FALSE);
  365. }
  366. }
  367. else
  368. {
  369. bQuoChangeLockHeld = TRUE;
  370. }
  371. //this can only come from the quorum resource
  372. CL_ASSERT(pResource->QuorumResource);
  373. }
  374. FmpAcquireLocalResourceLock( pResource );
  375. //
  376. // Chittur Subbaraman (chitturs) - 8/12/99
  377. //
  378. // First check whether the resource has been marked for deletion. If
  379. // so, don't do anything. Note that this function is called from
  380. // the worker thread AFTER FmpHandleResourceTransition has propagated
  381. // the failed state of the resource. Now, after the propagation has
  382. // occurred, a client is free to delete the resource. So, when the
  383. // worker thread makes this function call, we need to check whether
  384. // the resource is deleted and reject the call. Note that this function
  385. // holds the resource lock on the owner node of the resource and so
  386. // is serialized with the FmDeleteResource call which is also executed
  387. // on the owner node of the resource with the lock held. So, the
  388. // following check will give us a consistent result.
  389. //
  390. if ( !IS_VALID_FM_RESOURCE( pResource ) )
  391. {
  392. ClRtlLogPrint(LOG_NOISE,
  393. "[FM] FmpProcessResourceEvents: Resource %1!ws! has already been deleted, returning...\n",
  394. OmObjectId(pResource));
  395. goto FnExit;
  396. }
  397. switch(NewState){
  398. case ClusterResourceFailed:
  399. //check the old resource state
  400. if (OldState == ClusterResourceOnline)
  401. {
  402. FmpHandleResourceFailure(pResource);
  403. }
  404. else if ((OldState == ClusterResourceOffline) ||
  405. (OldState == ClusterResourceOfflinePending))
  406. {
  407. FmpTerminateResource(pResource);
  408. if ( pResource->Group->OwnerNode == NmLocalNode )
  409. {
  410. Status = FmpOfflineWaitingTree(pResource);
  411. if ( Status != ERROR_IO_PENDING)
  412. {
  413. FmpCheckForGroupCompletionEvent(pResource->Group);
  414. FmpSignalGroupWaiters( pResource->Group );
  415. }
  416. }
  417. }
  418. else if (OldState == ClusterResourceOnlinePending)
  419. {
  420. FmpHandleResourceFailure(pResource);
  421. FmpCheckForGroupCompletionEvent(pResource->Group);
  422. FmpSignalGroupWaiters( pResource->Group );
  423. }
  424. break;
  425. case ClusterResourceOnline:
  426. if (OldState == ClusterResourceOnlinePending)
  427. {
  428. Status = FmpOnlineWaitingTree( pResource );
  429. if (Status != ERROR_IO_PENDING) {
  430. FmpCheckForGroupCompletionEvent(pResource->Group);
  431. FmpSignalGroupWaiters( pResource->Group );
  432. }
  433. }
  434. break;
  435. case ClusterResourceOffline:
  436. if ((OldState == ClusterResourceOfflinePending) ||
  437. (OldState == ClusterResourceOffline))
  438. {
  439. Status = FmpOfflineWaitingTree(pResource);
  440. if ( Status != ERROR_IO_PENDING)
  441. {
  442. FmpCheckForGroupCompletionEvent(pResource->Group);
  443. FmpSignalGroupWaiters( pResource->Group );
  444. }
  445. }
  446. break;
  447. }
  448. FnExit:
  449. FmpReleaseLocalResourceLock( pResource );
  450. if (bQuoChangeLockHeld) RELEASE_LOCK(gQuoChangeLock);
  451. return;
  452. }
  453. VOID
  454. FmpPostWorkItem(
  455. IN CLUSTER_EVENT Event,
  456. IN PVOID Context1,
  457. IN ULONG_PTR Context2
  458. )
  459. /*++
  460. Routine Description:
  461. Posts a work item event to the FM work queue.
  462. Arguments:
  463. Event - The event to post.
  464. Context1 - A pointer to some context. This context should be permanent
  465. in memory - i.e. it should not be deallocated when this call
  466. returns.
  467. Context2 - A pointer to additional context. This context should be
  468. permanent in memory - i.e. it should not be deallocated when this
  469. call returns.
  470. Returns:
  471. None.
  472. --*/
  473. {
  474. PWORK_ITEM workItem;
  475. workItem = LocalAlloc(LMEM_FIXED, sizeof(WORK_ITEM));
  476. if ( workItem == NULL ) {
  477. CsInconsistencyHalt(ERROR_NOT_ENOUGH_MEMORY);
  478. } else {
  479. workItem->Event = Event;
  480. workItem->Context1 = Context1;
  481. workItem->Context2 = Context2;
  482. //
  483. // Insert work item on queue and wake up the worker thread.
  484. //
  485. ClRtlInsertTailQueue(&FmpWorkQueue, &workItem->ListEntry);
  486. }
  487. } // FmpPostEvent
  488. DWORD
  489. FmpStartWorkerThread(
  490. VOID
  491. )
  492. {
  493. DWORD threadId;
  494. DWORD Status;
  495. //
  496. // Start up our worker thread
  497. //
  498. ClRtlLogPrint(LOG_NOISE,"[FM] Starting worker thread...\n");
  499. FmpWorkerThreadHandle = CreateThread(
  500. NULL,
  501. 0,
  502. FmpWorkerThread,
  503. NULL,
  504. 0,
  505. &threadId
  506. );
  507. if (FmpWorkerThreadHandle == NULL) {
  508. ClRtlLogPrint(LOG_NOISE,
  509. "[FM] Failed to start worker thread %1!u!\n",
  510. GetLastError()
  511. );
  512. return(GetLastError());
  513. }
  514. return(ERROR_SUCCESS);
  515. } // FmpStartWorkerThread
  516. BOOL
  517. FmpAddNodeGroupCallback(
  518. IN PVOID Context1,
  519. IN PVOID Context2,
  520. IN PVOID Object,
  521. IN LPCWSTR Name
  522. )
  523. /*++
  524. Routine Description:
  525. Enumeration callback for each group in the system when a new
  526. node is added to the cluster.
  527. The algorithm used here is to enumerate all the resources in
  528. this group. For each resource in the group that does not
  529. have an explicit "PreferredOwners" setting in the registry,
  530. the node is added as a PossibleNode. Finally, if the node
  531. was added as a possiblenode for each resource in the group,
  532. the node is added to the end of the preferredowners list for
  533. the group.
  534. Arguments:
  535. Context1 - Supplies the PNM_NODE of the new node.
  536. Context2 - Not used.
  537. Object - Supplies the group object.
  538. Name - Supplies the name of the group object.
  539. Return Value:
  540. TRUE
  541. --*/
  542. {
  543. PFM_RESOURCE Resource;
  544. PFM_GROUP Group;
  545. PNM_NODE Node;
  546. HDMKEY hKey;
  547. DWORD Status;
  548. PPREFERRED_ENTRY preferredEntry;
  549. PRESOURCE_ENUM ResourceEnum;
  550. DWORD i;
  551. Group = (PFM_GROUP)Object;
  552. Node = (PNM_NODE)Context1;
  553. FmpAcquireLocalGroupLock( Group );
  554. Status = FmpGetResourceList( &ResourceEnum,
  555. Group );
  556. FmpReleaseLocalGroupLock( Group );
  557. if ( Status != ERROR_SUCCESS ) {
  558. ClRtlLogPrint(LOG_NOISE,
  559. "[FM] AddNodeGroup, failed to get resource list for group %1!ws!, status %2!u!.\n",
  560. Name,
  561. Status );
  562. return(TRUE);
  563. }
  564. //
  565. // First fix up the resource info.
  566. //
  567. for ( i = 0; i < ResourceEnum->EntryCount; i++ ) {
  568. Resource = OmReferenceObjectById( ObjectTypeResource,
  569. ResourceEnum->Entry[i].Id );
  570. if ( Resource != NULL ) {
  571. FmpAcquireLocalResourceLock( Resource );
  572. //ss: we need to hold the resource lock as well
  573. //since that is the one that the update (FmpUpdateChangeResourceNode)
  574. // to remove and add nodes obtains and we must synchronize with it
  575. FmpAcquireResourceLock();
  576. Status = FmpFixupResourceInfo( Resource );
  577. FmpReleaseResourceLock();
  578. FmpReleaseLocalResourceLock( Resource );
  579. if ( Status == ERROR_SUCCESS ) {
  580. FmpRmResourceControl( Resource,
  581. CLUSCTL_RESOURCE_INSTALL_NODE,
  582. (PUCHAR)OmObjectName(Node),
  583. ((lstrlenW(OmObjectName(Node)) + 1) * sizeof(WCHAR)),
  584. NULL,
  585. 0,
  586. NULL,
  587. NULL );
  588. // Ignore status return
  589. ClusterEvent( CLUSTER_EVENT_RESOURCE_PROPERTY_CHANGE,
  590. Resource );
  591. } else {
  592. ClRtlLogPrint(LOG_UNUSUAL,
  593. "[FM] AddNodeGroup, failed to fixup info for resource %1!ws! when node was added!\n",
  594. OmObjectName( Resource ) );
  595. }
  596. OmDereferenceObject( Resource );
  597. } else {
  598. ClRtlLogPrint(LOG_NOISE,
  599. "[FM] AddNodeGroup, failed to find resource %1!ws! in group %2!ws!.\n",
  600. ResourceEnum->Entry[i].Id,
  601. Name );
  602. }
  603. }
  604. FmpDeleteResourceEnum( ResourceEnum );
  605. //
  606. // Now fix up the group information.
  607. //
  608. FmpAcquireLocalGroupLock( Group );
  609. Status = FmpFixupGroupInfo( Group );
  610. FmpReleaseLocalGroupLock( Group );
  611. if ( Status == ERROR_SUCCESS ) {
  612. ClusterEvent( CLUSTER_EVENT_GROUP_PROPERTY_CHANGE, Group );
  613. } else {
  614. ClRtlLogPrint(LOG_UNUSUAL,
  615. "[FM] AddNodeGroup, failed to fixup info for group %1!ws! when node was added, status %2!u!.\n",
  616. OmObjectName( Group ),
  617. Status );
  618. }
  619. return(TRUE);
  620. } // FmpAddNodeGroupCallback