To Do: 1. Resolve vi problem. 551410 NLB:nlbmgr: Can't create new cluster via nlbmgr uitility 571762 NLB does not properly filter dedicated IP address traffic 585280 NLBMgr fails when cluster instance has nlb was bound but not cfg 5. See if we can call RegWriteParams BEFORE binding. 2. 535616 memory leak running remove from view 587508 Wmiprvse.exe is using too much memory cause exception 599165 nlbmgr:remember host properties when update is pending 591265 VERIFIER STOP 0000000A: unexpected exception raised in DLL entry 3. HostID analysis -- look for duplicates, missing ones and partitions 1. Queue calls to handle-engine-event and handle them in the main proc. - checkin, but make it disabled... - remove ALL (or almost ALL) calls to ProcesMsgQueue. Remove most occurrances of calls to ProcessMsgQueue in engine.cpp -- replace by calls in document.cpp for log and handleengineevent. 4. Make GetConfig, UpdateCfg, etc, properly fill out the WBEM_NOT_FOUND value on error. 5. Fake: Implement fHidden flag in NIC, set it in debugger in between operations, and make sure mgr behaves propertly when guids disappear (kerry problem). Make sure not-found interfaces are DELETED. 7. Make sure error is logged if operation doesn't succeed properly. -- on operation timeout, do a final read of properties and report any misconfig. 11. Change MsgBox to a Dlg -- in reporting problems, changes, etc. 12. Error messages -- make them more actionable. 13. Add list of changes to log. 14. Investigate nlbmgr's resource usage. ------------------- - Error checking: CfgUtilAnalyze is failing because ded-ip matches cluster-ip -- but the UI simply reports invalid cluster specification. We need to do our own checks (in the UI) and report all discrepencies to the UI itself. - Cluster properties: -- define the right primitivies for analysis -- always show a specific host's properties -- if update failed, cluster properties will pick up the 1st host's properties that match. - Implement DetailsView.UpdateInterfaceInCluster - Cleanup log (including details lparam) - Host-properties: good defaults and preserve existing for host-port-rule-priorities. - Add support for multiple VIPSs: See "10/02/2001 JosephJ managing multiple VIPs" Some outstanding issues: - Create 10.0.0.11 cluster on cheese-x, try to add cheese-y to it -- fails because it complains of invalid cluster spec. - In badcluster case, we don't populate the dedicated ip, then complain? - IP Address database (IP_INFO) - More corner cases DeleteInterface: if host is in an unreachable state, then prompt user and remove. - Handle keeping track of pending operations - Update: - display partial log output - Add to FakeNlbHostXxx apis -- update should return async. - Update phase 2: - Send proper status updates and log entries. - Do update in background thread. (Look for "Pending" under 09/19/2001 JosephJ Notes to self) - Deal with refreshes *while* there is pending activity. - Log: add critical section to document class -- protect changes to log and credentials. - Fix waitcuror in AddHost ?? - Do basic checking -- same host can't be twice in same cluster, same cluster IP can't be in two clusters, etc. keep a global map of cluster IPs to interface IDs (see 09/14/2001 log entry for details). - Put up message box warning the user that they are adding a NIC that has a different cluster IP -- for all versions, especially Add host. - Port Rules: track available port-rule-priorities, put correct defaults in apply_cluster_configuration and in the UI. - Preserve host-specific information as far as possible. - Report to log on error. - Implement rudimentary analyze functionality -- at least cluster IPs. - Encrypt passwords - Fix localization problems. -------- - Add automatically querying for other cluster members. - Get rid of netcfg stuff in application.cpp -- use cfgutil stuff instead. Later: - Deal with HOST NAME change while running NLB manager. - Deal with Adapters coming and going while running NLB manager. - Deal with upgrades and/or re-installs of adapters -- GUIDs change but friendly name can be the same. - Track update generation -- warn if there's been a change. RELEASE NOTES: Port rules: Mode == Single -- priorities aren't created right. If you change a start/end ports, host-specific settings are lost. Mode = multiple -- if you hit "OK" in host-portrule-edit, then "equal" gets changed to 50. - documentation -- see "Documentation" below. - figure out how to de-initialize cleanly -- not clear how to do this with MFC apps -- use destructor ------------ JosephJ 5/19/01 Code paths for main operations 1. Create New Cluster, add a host 2. Connect to existing cluster 3. Modify existing cluster's properties. 05/19/2001 JosephJ Cluster node in treview is created in several places -- LeftView::OnWorldNewCluster. LeftView::OnWorldConnect LeftView::OnWorldConnectIndirect The name is set there (currently the cluster ip address) Application Initialization --------------------------- 05/20/2001 JosephJ When implementing the disclaimer dialog box, which has a checkbox... DDX macros don't work in the context of the Application::InitInstance function. So I had to use the CWnd::IsDlgButtonChecked macro. This works. HOWEVER the DDX macros now work too -- so I changed something and now it works. JosephJ 07/20/2001 Main aspects of new code -------------------------------------------- INTERFACE (keys: GUID, friendly-name) HOST (keys: machine-name (if known), connection ip) CLUSTER (Keys: cluster name, cluster ip, internally-generated name) INTERFACE operations: Add/remove/refresh/update-props HOST operations: Add/remove/refresh/update-props CLUSTER operations: Validate/Add-host/remove-host/update-props Reorganization operations ------------------------- Goals: 1. nlbmprov.dll can run on both w2k and xp 2. nlbmgr.exe can ron on w2k and xp and a client 3. tprov and nlbmgr share code in a common lib List of existing functionality: 1. Utilities that wrap wlbsctrl functionality 2. WMI client utilities 3. WMI server utilities 4. Self-contained utilities NLB manager considers a cluster to consist of a list of interfaces. An interface is idenified by (Machine, Adapter-GUID). The adapter's friendly name is used to resolve the adapter if the GUID doesn't exist (as can happen on a clean install). The friendly name is also used to identify the adapter in the UI. NLB manager does NOT index a cluster by the cluster IP address. NLB Manager supports having individual interfaces in the same cluster be configured with different cluster IP addresses. Likewise, the same cluster IP can occur on interfaces from different clusters. Of course these are cases of mis-configured clusters and NLB manager will report them as such and provides the user with UI to bring the clusters into a consistant state. NLB manager keeps track of the list of hosts it is managing. These hosts can be in an unavailable state (either due to connectivity problems or because the host has been shut down or re-purposed into some different configuration). The authoratitative state of NLB configuration data resides on the hosts. NLB manager's version of the cluster configuration is purely a cache. NLB manager persists the following information: 1. List of machines to manage, and how to get to them (connectivity information) 2. List of clusters managed (since cluster is defined as a list of interfaces, the list of interfaces associated with a cluster is persisted). When NLB manager is launched, it will retrieve this list of machines and clusters and attempt to connect to these machines and enumerate the adapters looking for the persisted list of interfaces. It will suitably display any missing hosts or interfaces (with appropriate icons). If it discovers hosts have been ADDED it will automically add them to the view. If it discovers that interfaces have been removed it will still list those interfaves, but with an appropriate icon to indicate that that interface apperas to be no longer part of the cluster. The user has the option of removing (from view) such interfaces. Initialization: From scratch, manage existing cluster. 1. User selects "Connect to Existing" 2. UI: bring up the connect/select-interface dialog. 3. Engine: connect to host, suck up information on ALL adapters in the specified host and enter the info into the engine's data structures. Issue: may need to do some resolving if info already exists. 4. UI: allow user to select the interface (only allow user to select interfaces which are bound to NLB. 5. Engine: Create a cluster with the specified host. Query the host for other cluster members. Get their machine-names (XP), host IDs and dedicated IPs. Create PLACEHOLDERS for those in the cluster data structure. Attempt to connect to each of these remote hosts. If we can't (either because we can't ping their machinenames (if specified) or dedicated-ip (if specified) or neither is specified we call back to UI to get a connection-string or to retry. For hosts that we DO connect to, we follow process #3 and add the interfaces to the cluster. 6. UI: creates cluster node in tree view, updates status (callbacks from Engine), bring up connect-to-host page for any additional hosts discovered. Initialization: From scratch, create new cluster. 1. UI: User selects "Configure New Cluster" 2. UI: #2 above, but modified to indicate we're creating a new cluster. 3. Engine: #3 above 4. UI: allow user to select interface (only allow user to select interfaces which are NOT bound to existing NLB manager clusters that NLB Manager already knows about). Bring up the cluster and host pages and let the user enter all the relevant information. 5. Engine: Configure the cluster on the specified host. If NLB HAS been bound (even if the rest failed), create a cluster node. Call back to UI for onging status. 6. UI: creates cluster node in tree view, update status. Add host to existing cluster 1. UI: let user select cluster-add host. 2. UI: #2 above, but modified to indicate "select interface to add to cluster". 3. Engine: #3 above 4. UI: Allow user to select any interface except ones that NLB manager already knows as being part of another cluster. Bring up warning dialog if NLB is already bound to the specified interface with different cluster-ip address. 5. Engine: #5 above. 6. UI: Add node to tree view. Refresh: 1. Go through host list, refreshing everything, updateing status and picking up new hosts. Retreiving persisted state on startup: 1. Create shell host and cluster data structures and tree view. 2. Do a refresh. Fixing up broken cluster: 1. Engine: maintains a fMisconfigured flag for the cluster and each interface. 2. UI: reflects this by icon state 3. UI: has "Analyze" menu option -- will re-do analysis 4. Engine: Analyze -- runs through existing cache, spitting out problems it finds to the log. 5. UI: Fixing cluster -- same as changing cluster properties. -------------------------------------------------- 08/03/2001 JosephJ Gutting of NLBMGR.EXE -- Phase I (DONE) - Removed references to externall nlbmgr libraries (wmibase etc) and "reaching-over" includes, copying over files as required. - Removed calls to wmi-client side code. - Ensures that the resultant exe builds and comes up ok. 08/03/2001 JosephJ Differences between NLBMGR UI and NETCFGUI Cluster Properties Page -- OK Port Rules page (from cluster and host) - VIP missing - Port rule description missing - Note that load and priority are displayed differently by design Host Properties - MGR has a NIC combobox -- replace by static text FriendlyName - "Unique Host ID" change to "Unique host identifier" - New initial-host-state and persist-suspend UI. That's it! 08/03/2001 JosephJ Where do we store the "cluster" version of config info? Engine will keep a "cluster" version of the config info, along with which host it obtained it from and when, and whether/not it is modified but not committed. 08/03/2001 JosephJ Gutting of NLBMGR.EXE -- Phase II - Replacd different flavors of port rules by a single flavor. (DONE) - Get rid of tiny classes and their files -- consolidate. (DONE) 08/03/2001 JosephJ Moving to new infrastructure Phase I - define update methods to the LeftView and RightView - replace all places where these views are updated directly by calls to the engine (and migrate the code that updates the views to the update methods in LaftView and RightView) and callback handlers in the document. 08/04/2001 JosephJ Partial xml schema Its just hit me what a big bang for the buck it is to add a Load Template support to NLB to load a partial description of the configuration properties. This template will contain NO host-specific information (no priorities, load weights, dedicated IPs). It will optionally contain the cluster-ips, cluster-mode, and port-rules. For vip-specific portrules, the vip for each port rule is optional. What this lets us do (because theres no host-specific information, and things like cluster IP addresses are optional) is that we or others can publish template descriptions for things like load balance FTP traffic, etc and that xml file can be loaded AS IS into the UI! 08/04/2001 JosephJ New save/restore semantics... NLB Manager will let you load a list of hosts this is a simple text file listing hosts (ip addresses or machine names or FQDNs) one per line. This can be done via the command line or from the application menu. NLB Manager will attempt to connect to each of these hosts, reporting to the user if it cannot connect (thus reporting #1 in the list above). It will suck up information about ALL clusters installed on these hosts, and add them to the cluster tree view based on their IP address (there will be a special placeholder for 0.0.0.0 ip addresses, which can happen). It will report any inconsistencies in configuration or in the list of hosts visible from each node (this addresses #2). Note that if a botched update operation results in incorrect IP address assignments we will not detect this well just report the hosts under multiple clusters. However, read on [Well do this if we have time to spare, the prospect is unlikely, but schedules can change] NLB Manager will support a validate against snapshot command this command will take an xml file which has the information structured as I indicate below, and validates the current view against it. The Validate operation simply reports any discrepancies against the current view. There will be a save snapshot command to save the snapshot. 08/04/2001 JosephJ Add to server-side: ping ip addresses We should have an fNew flag, which if set, server-side will (a) check that the ip address is not already on any other nic in the machine (b) ping all the cluster ip addresses to make sure that they do not exist. 08/05/2001 Josephj Documentation: things to document - top level classes: application, document, engine, leftview, logview, etc. - flow of control for typical operations: - program initialization - adding new cluster - reading filelist - etc. - list of dialogs. - how to add a dialog. - how columns are added to the log and details list views. - how to add/remove those little "+"es in the tree view. - how to edit icons, issues with editing icons. - dialog operation -- DDX, message map, etc. - wait cursors: CCmdTarged::BeginWaitCursor/EndWaitCursor/RestoreWaitCursor - Menu operation -- dropdown and pop up -- how to enable/disable them, how to add items. See: 12/15/2001 How to add a menu item - ENGINEHANDLE -- its properties, design motivations. - Tab order -- see 09/17/2001 tab order entry. - Cool listctrl functions FindItem and GetNextItem - use of type-specific context-sensitive map: ConnectDialog::OnHelpInfo - Splitter windows and the organization of views -- see 10/27/2001 JosephJ Note on the layout of the views - Choosing good host-specific default properties when adding a new host to an existing cluster -- see 10/25/2001 JosephJ Host and Port priorities. - Managing virtual IP addresses, preserving order of ip addresses on individual hosts as far as possible -- see 10/07/2001 JosephJ managing multiple VIPs - Hooking TreeView double-click and key strokes -- see 11/13/2001 JosephJ Hooking TreeView double-click and key strokes 08/05/2001 JosephJ Location of msvc sample icons... C:\Program Files\Microsoft Visual Studio\Common\Graphics\Icons 08/05/2001 JosephJ setting the background on the icons The icons I 08/06/2001 JosephJ feedback from team at today's NLB meeting 1. Change "not bound to NLB" to be "NLB not bound to adapter" 2. Make Host Parameters go before Port Rules, to conform to cfgui. 3. Don't bother with restricting users from moving from page-to-page -- just gray out the OK button until all parameters are filled in and put up an informational message box when focus is removed from the pane _snwprintf while leaving incomplete information specified. 4. Rename or get rid of "Event ID" -- Maybe change it to "Event No." 5. If we implement "check for duplicate IP in the network", then do this AFTER the dedicated IP has been set up. d:\nt\net\wlbs\nlbmgr\exe2\obj\i386;symsrv*symsrv.dll*\\symbols\symbols nlbmgr2!LeftView__OnWorldNewCluster ChildEBP RetAddr Args to Child 0006e57c 77d47797 00000040 40010444 010369e8 ntdll!KiUserExceptionDispatcher+0x4 (FPO: [2,0,0]) [D:\xpclient\base\ntos\rtl\i386\userdisp.asm @ 206] 0006e7d8 77357f04 010369e8 0006e7fc 00000000 USER32!RealDefWindowProcWorker+0x119 (FPO: [Non-Fpo]) 0006e818 77357edb 0009d030 0009d2d8 00130958 COMCTL32!_CreatePageDialog+0x1e (FPO: [Non-Fpo]) [d:\xpclient\shell\comctl32\v5\prpage.c @ 575] 0006e838 77357d20 0009d030 0009d2d8 00130958 COMCTL32!_CreatePage+0x3d (FPO: [Non-Fpo]) [d:\xpclient\shell\comctl32\v5\prpage.c @ 708] 0006ea4c 77358f10 00130958 00000001 77d45944 COMCTL32!PageChange+0x99 (FPO: [2,124,3]) [d:\xpclient\shell\comctl32\v5\prsht.c @ 1909] 0006ee00 77358f2e 00130958 00000143 0009d030 COMCTL32!InitPropSheetDlg+0x9a2 (FPO: [2,230,3]) [d:\xpclient\shell\comctl32\v5\prsht.c @ 1562] 0006ea4c 77358f10 00130958 00000001 77d45944 COMCTL32!PropSheetDlgProc+0x463 (FPO: [Non-Fpo]) [d:\xpclient\shell\comctl32\v5\prsht.c @ 3461] 0006ee30 5ad94828 00000110 5adbd7b8 00000006 COMCTL32!InitPropSheetDlg+0x9a2 (FPO: [2,230,3]) [d:\xpclient\shell\comctl32\v5\prsht.c @ 1562] 0006ee44 5ad93c89 00aea3b8 00aea3a8 77357445 uxtheme!FindDdpHandler+0x18 (FPO: [3,0,0]) [d:\xpclient\shell\themes\uxtheme\handlers.cpp @ 561] 0006ee64 77d43a50 00130958 00000110 000b09c6 uxtheme!CThemeWnd__Release+0xe (FPO: [0,0,2]) [d:\xpclient\shell\themes\uxtheme\nctheme.cpp @ 1460] 0006eda8 77d43ee7 00aea3a8 00000000 0000000a USER32!_InternalCallWinProc+0x1b [D:\xpclient\windows\core\ntuser\client\i386\callproc.asm @ 102] ffffffec 00000000 00000000 00000000 00000000 USER32!GetWindowLongW+0x49 (FPO: [Non-Fpo]) [d:\xpclient\windows\core\ntuser\client\cltxt.h @ 430] :w \\netvbl2\sources\shell\comctl32\v5\prpage.c EditPropSheetTemplate: ... pdwStyle = &pDlgTemplate->style; ... *pdwStyle = dwNewStyle; It looks like this pDlgTemplate->style is not WRITEABLE, even though it appears readable... 0:000> dd 010369e8 l 1 010369e8 80c800c0 0:000> ed 010369e8 0 ^ Memory access error in 'ed 010369e8 0' Looks like it's pointing into read-only resource data... 0:000> dc 010369e8 010369e8 80c800c0 00000000 00000013 00f00000 ................ 010369f8 000000d7 00430000 0075006c 00740073 ......C.l.u.s.t. 01036a08 00720065 00500020 00720061 006d0061 e.r. .P.a.r.a.m. 01036a18 00740065 00720065 00000073 004d0008 e.t.e.r.s.....M. 01036a28 00200053 00680053 006c0065 0020006c S. .S.h.e.l.l. . 01036a38 006c0044 00000067 50000007 00000000 D.l.g......P.... 01036a48 00070007 005400e2 ffff03e8 00430080 ......T.......C. 01036a58 0075006c 00740073 00720065 00260020 l.u.s.t.e.r. .&. 0:000> dc 01036a68 00500049 00430020 006e006f 00690066 I.P. .C.o.n.f.i. 01036a78 00750067 00610072 00690074 006e006f g.u.r.a.t.i.o.n. 01036a88 00000000 50020100 00000000 0015000f .......P........ 08/09/2001 JosephJ problems with msdn tried searching for random -- very poor showing. Online help is vague, sync to toc doesn't work. also look for info on how to define string literals in visual basic. 08/09/2001 JosephJ The "copy operator" for vectors. Looks like vectors copy the dimensions, but their content's don't get copied if they're pointers -- this is reasonable, but it means that when crating a copy of, say CHostSpec, the vector of interface IDs will not be copied. 08/09/2001 JosephJ TODO: when copying NlbCfg we need to copy addresses appropriately. 08/09/2001 JosephJ determing the selection of a list ctrl: POSITION pos = m_portList.GetFirstSelectedItemPosition(); if( pos == NULL ) return; int index = m_portList.GetNextSelectedItem( pos ); m_portList.DeleteItem( index ); 08/09/2001 JosephJ Allowing a full row of a multicol list view to be selected.. m_portList.SetExtendedStyle( m_portList.GetExtendedStyle() | LVS_EX_FULLROWSELECT ); 08/10/2001 JosephJ wlbsctrl.dll functions used in cfgutil.lib: CWlbsControl::Initialize CWlbsControl::CWlbsControl CWlbsControl::~CWlbsControl CWlbsControl::GetClusterFromAdapter CWlbsControl::ReInitialize CWlbsCluster::ReadConfig CWlbsCluster::CommitChanges CWlbsCluster::WriteConfig CWlbsControl::ValidateParam CWlbsControl::LocalClusterControl WlbsSetDefaults WlbsEnumPortRules WlbsAddPortRule WlbsDeleteAllPortRules 08/10/2001 JosephJ CPropertySheet -- has stuff about wizards in it! 08/10/2001 JosephJ ClusterPage directly handles OnNotify, so our virtual OnSetActive method doesn't get called. So I call OnSetActive from ClusterPage::OnNotify 08/12/2001 JosephJ Code that gets notified when focus is set on a control.. Check out the following in hostpage.cpp ... ON_EN_SETFOCUS( IDC_EDIT_DED_MASK, OnGainFocusDedicatedMask ) It fills out the control with defaults. 08/13/2001 JosephJ WlbsToNetcfgConfig (in netcfg\wlbscfg) 08/13/2001 JosephJ somehow m_pCommonClusterPage->Update() is not working when called after the do-modal is done. Also it looks like ClusterPage->OnOk is not called for wizard. Temporary workaround: added the following case PSN_WIZFINISH: void External_UpdateInfo(void) {UpdateInfo();} *pResult = m_pCommonClusterPage->OnKillActive(idCtrl, pNmhdr, *(BOOL*)pResult); this->mfn_SaveToNlbCfg(); return TRUE; TODO: look for mfn_SaveToNlbCfg() -- clean up. 08/14/2001 JosephJ Wizards .... Override CPropertyPage::OnWizardFinish() (eg CHostPage) OR, if you're directly processing OnNotify (eg CClusterPage), you intercept the PSN_WIZFINISH (see 8/13/2001 log entry). 08/14/2001 JosephJ Trying to make the "Connect" button in ConnectDialog the default button. The API is CDialog::SetDefID( UINT nID ); However it's tricky to figure out where to call this from. I was able to call it when the connect-to-host edit control gets called, and from SetActive, but neither gets called when the connect dialog is first put up. Calling DetDefID from OnInitDialog doesn't work either. Tried calling from ConnectDialog::OnActivate -- didn't work. ON_WM_ACTIVATE() ... void ConnectDialog::OnActivate( UINT nState, CWnd* pWndOther, BOOL bMinimized ) { this->SetDefID(IDC_BUTTON_CONNECT); } Actually the above were getting called in special circumstances, like if the windows is being redrawn, or we move back to to this page once there is already something else to focus on. Final solution: Hook the ON_EN_UPDATE messages! Works like a charm. I check the text length, and if it 0 or the string is all whitespace, I disable the button. It remains the default button, but I think that's ok. 08/14/2001 JosephJ engine notifying the ui about cluster/host arrival.... New cluster: The moment the user clicks "Finish" in the new cluster wizard, we populate the tree view with the cluster and the first host -- they should both have the pending icons. We spit something out to the log. On successful completion, we change the icons. We spit something out to the log. On failed status it depends on what failed -- if nlb is bound, we show the cluster and host appropriately -- if things look ok we show it ok, otherwise with appropriate icons. We spit something out to the log. 08/15/2001 JosephJ Properly sizing the list columns. GeorgeJ suggested using GetClientRect to get the size of the list box, then partitioning that space appropriately. 08/15/2001 Displaying pending status0 engine.cpp: implemented process_msgqueue() (got from msdn) and also CWaitCursor. Works like a charm! 09/12/2001 JosephJ Types of operations involving wizards/properties: NewCluster wizard ExistingCluster wizard AddHost wizard HostProperties propertysheet ClusterProperties propertysheet ClusterPage -- can be brought up in several modes: 1: NewCluster first page in wizard, all fields enabled 2: ClusterProperties all fields enabled 3: HostProperties all fields DISABLED Connect page: 1: NewCluster middle page in wizard 2: ExistingCluster first page in wizard 3: AddHost first page in wizard Hosts Properties: 1: NewCluster last page in wizard, all fields enabled 2. AddHost last page in wizard, all fields enabled 3: HostProperties all fields enabled PortRules: 1: NewCluster middle page in wizard, all fields enabled 2. AddHost middle page in wizard, only load weight enabled 3: HostProperties only load weight enabled 4: ClusterProperties all fields enabled, except load weight So we'll add an enum: OP_NEWCLUSTER OP_EXISTINGCLUSTER OP_ADDHOST OP_HOSTPROPERTIES OP_CLUSTERPROPERTIES That's passed into the constructor for each of the dialog pages. DOC bug _snprintf, _snwprintf -- mix up byte- and char- counts 09/14/2001 JosephJ Dealing with exlusive resources -- priorities, IPs, etc - CEngine: Define a private class CEngineCluster which encapsulates a CClusterSpec and also keeps a bitmap of taken host priorities, and a 32-bit array per-port-rule priorities. Also a ref count. The ehClusterSpec maps to this structure. Add access functions. - Pass in the cluster handle to the constructor for host properties. Host properties then gets access to the bitmap of taken properties. (Engine is responsible for maintaining the bitmap). IP addresses: Global map of ip addresses to it's properties. IP Properties: IsVip, IsDip, ehCluster, ehInterface Need to implement: HostPage::mfn_Load/SaveToNlbCfg() PortsPage::mfn_Load/SaveToNlbCfg() and call them for both property-sheet and wizard mode. Following still unacounted for... LOAD: NETCFG_WLBS_PORT_RULE port_rules[CVY_MAX_RULES]; TODO: These fields are set if the user has changed the password -- in this case we need to send this to the host. bool fChangePassword; TCHAR szPassword[CVY_MAX_RCT_CODE + 1]; SAVE: NETCFG_WLBS_PORT_RULE port_rules[CVY_MAX_RULES]; TODO: These fields are set if the user has changed the password -- in this case we need to send this to the host. bool fChangePassword; TCHAR szPassword[CVY_MAX_RCT_CODE + 1]; 09/14/2001 JosephJ GJack suggestions. 1. Make "create new cluster" -- adapter list can potentially show all nics. checkbox can restrict. If nic has a nlb cluster ip which matches the cluster ip, select it as the default, otherwise try to select the interface that appears to be on the same subnet as other interfaces already in the cluster. 09/15/2001 JosephJ Keeping nlbmgr's view upto date. 1. Everytime we present data to the viewer, it's always after refreshing it from the host. This is true even for cluster properties. So 1. Viewing/changing cluster properties: We refresh the cluster properties using the 1st host, then present it. 2. Viewing/changing host properties We get the latest view of the host's properties. 09/16/2001 JosephJ WLBS_PORT_RULE structure and constants typedef struct { TCHAR virtual_ip_addr [WLBS_MAX_CL_IP_ADDR + 1]; DWORD start_port; DWORD end_port; DWORD mode; CVY_SINGLE CVY_MULTI CVY_NEVER DWORD protocol; CVY_TCP CVY_UDP CVY_TCP_UDP union { struct { DWORD priority; } single; struct { WORD equal_load; // 0/1 WORD affinity; CVY_AFFINITY_NONE CVY_AFFINITY_SINGLE CVY_AFFINITY_CLASSC DWORD load; // 0...100 } multi; } mode_data; } WLBS_PORT_RULE, * PWLBS_PORT_RULE; 09/16/2001 JosephJ Supplying unique host priorities and port-rule priorities CEngine should have two functions NLBERR GetAvailableHostPriorities(IN ehCluster, OUT ULONG &bitmap); NLBERR GetAvailablePortRulePriorities( IN ehCluster, IN LPCWSTR szVip, IN UINT start_port, OUT ULONG bitmap); These functions go through the list of interfaces/port-rules, and compile a bitmap of available priorities. 09/16/2001 JosephJ Merging cluster portrules into host's portrules For each rule in cluster if (find rule in host && makes sense) { copy priority or load } else { priority = host id load = 50 } TODO: if you edit the start port, you will loose the interface-specific information. To fix this is not trivial -- need some kind of a key which is common between the cluster-specific version and the port-specific version. IDC_RADIO_EQUAL IDC_RADIO_UNEQUAL 09/17/2001 JosephJ Tab orders are important for radio buttons! I changed the tab order and radio buttons become uncoordinated -- turns out that radio buttions must be in sequential tab order to have the toggle effect. 09/17/2001 JosephJ OnWizardFinish is NOT called unless the page is the last page. So for middle pages you need to do stuff in KillActive. 09/17/2001 JosephJ regexp searh for "if (x=y)" error if.*[^=!<>]=[^=] 09/17/2001 Connect dialog if (!existing) Show: "Interface Name|Interface IP |Cluster IP" 111.111.111.111|111.111.111.111 111.111.111.111| mfn_LookupClusterByIPLk(..., REF pECluster...) pECluster->m_cSpec.m_ehInterfaceIdList.push_back(ehIfId); 09/19/2001 JosephJ Notes to self NLBMGR.EXE Context sensitive help, UI-Ids match those in netcfgx.dll, help linkages. VIP (port rule) support Suspend-state and start-suspended support. Save log: use application event log Analyze cluster functionality see below Demo switch : put up message box. Add fLog field to connect host. LoadHost: connect (fLog=TRUE); then for each ehIF for bound Ifs, lookup clusterip, if new update cluster properties; then AddInterfaceToCluster. Add ehCluster to CInterfaceSpec. Analyze functionality: CNlbEngine::AnalyzeInterfaceClusterPropsLk(pISpec, pCSpec); CNlbEngine::AnalyzeIntefaceHostProps(pISpec, pOtherISpec); -- these also set/clear misconfig flag. CNlbEngine::GetIpInfo(szIp, pIP_INFO) (IP_INFO: ehCluster; ehConnectionIp; ehDedicatedIp; Call above from UI (in validate-data kill-active, etc.) ControlCluster: New methods: ControlCluster(szNIC, operation); ControlPort(szNIC, szVIP, szStartPort); But we'll need also to do query Pending operations [Also see 11/18/2001 JosephJ Pending operations -- take 2] 1. Don't keep any pointers to global dyamic data structures in the background thread. 2. Only one pending operation per interface. fPending flag tracks this. --Add a common superclass "CSpec" to CInterfaceSpec, CClusterSpec, and CHostSpec. CSpec has the following members: type,fPending,fDeinitializing,fCancelOperation, bstrOperationDescription ( eg "Modifying cluster properties" "Modifying host properties" Engine method: mfn_Begin/EndOperation(ehObj) Each of these: Check global fDeinitializing flag is set Check if this object's fDeinitializing flag is set Check if this object's fPending flag is set Set the pending-flag and update the UI appropriately Bump up global head count On decrement, if unloading and zero - Get rid of CEngineCluster - Re-write validate cluster to use the type - Special checks: - If a cluster operation is pending, don't allow interface-level operations to start unless it's in the context of a cluster-wide operation. - If an interface operation is pending, don't allow a cluster-level operation to start. 5. UI: message box if attempt is made to change config while pending operation in progress. 6. For now: Always create thread for the update sequence: get-config,update, sleep,ping,get-result. Later use thread pool. 7. Log updates: pszLog maintained in pISpec – log new data (like tprov does) 8. Cancel operation: will stop the sleep-get-result loop in background thread. FAKE support: Update: Pending: fpending, hthread; Dummy log entries created; Per host delay + random failure Ping: per-host delay/ramdom variation (max is what ping arg specifies); ping host: return connection IP; ping ip any ip on interfaces that are marked as management capable All operations: per host connectivity delay (could be never i.e., host not reachable). Change BRIE, etc. to CHEESE1 Schedule meeting/design review; show demo, docs, code, handout. 09/20/2001 JosephJ map wierdness. [see also 11/10/2001 JosephJ Redesign of the format of ENGINEHANDLE] When iterating through a map using "iterator", you can get entries which have never been set! The second value of these will be NULL. so if mymap[1] = p1; mymap[3] = p3; and you iterate, you'll get an iteration whose first is 2 and 2nd is NULL -- even if you never explicitly inserted that. Pretty creepy. I wonder if you were to insert mymap[100000] = p100 and you iterate you'll get all values from 1 to 100000! GOT It: if you *ever* refer to the map in an expression -- like if (mymap[45] != NULL) {} then a map entry for that value is created. We do this in ValidateHandle. Todo: need to change validate handle so that it doesn't call the map functions. 09/20/2001 JosephJ plan for LoadHosts: LoadHost is implemented *outside* CNlbEngine pseudo code: LoadHost(pConnInfo) { gEngine.ConnectToHost(pConnInfo, REF ehHost); for (each interface ehIF in ehHost) { CInterfaceSpec *pISpec=NULL; gEngine.GetInterfaceSpec(ehIF, REF pISpec); if (pISpec->ehCluster == NULL) { // interface is not part of a cluster... szClusterIp = (extract cluster ip from pISpec.m_NlbConfig); gEngine.LookupClusterByIp(szClusterIp, TRUE, REF ehCluster, REF fIsNew); gEngine.AddInterfeToCluster(ehCluster, ehIF); if (fNew) { gEngine.UpdateCluster(ehCluster, pISpec.m_NlbConfig); } // TODO: call gEngine.AnalyzeXXX functions to report // misconfigured host: or conflicts with other // hosts and/or mismatches with cluster. } } } 09/20/2001 JosephJ -- used real NlbHostXXX functions for the first time -- simply commented out the call to NlbHostFake in CNlbEngine::Initialize -- worked like a charm, first time!!!!!!!!! -- now need to implement update config. 09/21/2001 JosephJ command line processing Created subclass CNlbMgrCommandLineInfo of CCommandLineInfo. This subclass overrides the ParseParam virtual function. Then from Application::InitInstance, call CWinApp::ParseCommandLine. I additinally overrode the function ProcessShellCommand. Turns out that you NEED to call CWinApp::ProcessShellCommand or else the application doesn't start. So I call CWinAPP::ProcessShellCommand from Application::ProcessShellCommand. 09/21/2001 JosephJ implemented command line processing There is a global instance of a new class (defined in document.h), called gCmdLineInfo. This class is filled in by the Application class. It includes the hostlist option. If you type nlbmgr2.exe /help, you'll get a little popup showing the command line options. They are: /demo run in demo mode /hostlist load the hosts specified in /help display this message If the /hostlist option is specified (along with ), then gCmdLine.m_bHostList is TRUE, and gCmdLine.m_bstrHostListFile contains the file name. So given that we'll need to load hosts both from LeftView and on initialization, the place to implement the loadhosts functionality is In the Document class. So add a public method Document::LoadHosts(szFileName) and a private method Document::mfn_LoadHost(szHostConnectiString). Call these methods from Document::Document (the last thing done). In both cases, LoadHostList may need to do stuff in a background thread -- as you suggested. 09/21/2001 JosephJ NewCluster: JosephJ show error if new cluste ip matches any ip on some other nic in host. 09/22/2001 JosephJ Making code organization of the host properties UI The host-specific properties are implemented in class HostPage, which is in hostpage.cpp and hostpage.h. Class HostPage is derived from CPropertyPage. It's resource ID is IDD_HOST_PAGE, defined in resource.h and resource.rc. The various controls are members of this class, for example: CIPAddressCtrl ipAddress; CIPAddressCtrl subnetMask; CButton initialState; The mapping between the UI and these controls is "automagically" done in overridden virtual functino DoDataExchange: HostPage::DoDataExchange( CDataExchange* pDX ) { DDX_Control( pDX, IDC_EDIT_PRI, priority ); DDX_Control( pDX, IDC_CHECK_ACTIVE, initialState ); DDX_Control( pDX, IDC_EDIT_DED_IP, ipAddress ); DDX_Control( pDX, IDC_EDIT_DED_MASK, subnetMask ); } The initial state of these controls is set in the function mfn_LoadFromNlbCfg. On losing focus or the ok button being pressed, the state of the controls are validated using mfn_ValidateData (which is partially disabled because it had some old code), and saved using mfn_SaveToNlbCfg. mfn_LoadFromNlbCfg and mfn_SaveToNlbCfg load from/save to a class of type NLB_EXTENDED_CLUSTER_CONFIGURATION, which is defined in nlbmgr\inc\cfgutil.h, and implemented in nlbmgr\cfgutillib\extcfg.cpp (goes into static library cfgutil.lib). Now NLB_EXTENDED_CLUSTER_CONFIGURATION as not particularly elegant. It includes as it's members the list of IP addresses bound to the NIC, as well as NlbParams, which is of type WLBS_REG_PARAMS. The class is messy, it's get/set functions ugly, and it's use of WLBS_REG_PARAMS unfortunate, because we don't use many of the arcane fields of WLBS_REG_PARAMS, and we are forced to use the WlbsEnumPortRules, etc functions. Anyway, there are access methods in the class for various members, although since the members are not private, I sometimes access members directly, for example (from hostpage.cpp): _bstr_t bstrDedIp = (LPCWSTR) m_pNlbCfg->NlbParams.ded_ip_addr; It definately would be good to cleanup this class, but it's used pretty extensively across all nlbmgr code (including the provider and the console app), so it's nontrivial to change. Getting back to HostPage, the load/save functions (mfn_Load/Save...) access member m_pNlbCfg of type NLB_EXTENDED_CLUSTER_CONFIGURATION. This pointer is initialized in the constructor of class NlbHost. 09/22/2001 JosephJ Making code organization of the ports-related UI [Read note above for reference to location of resource ids, and NLB_EXTENDED_CLUSTER_CONFIGURATION, DoDataExchange, and semantics of mfn_LoadFrom/SaveToNlbCfg.] There are 3 ports-related property-pages/dialogs. These are - PortsPage: public CPropertyPage -- list of ports - ClusterPortsDlg: public CDialog -- cluster-ver of edit port dlg - HostPortsDlg: public CDialog -- host-ver of edit port dlg. Details ... PortsPage: public CPropertyPage -- this contains the list of ports with the add/edit/remove buttons. This class can be invoked in "cluster mode" or in "host mode" -- the fIsClusterLevel argument passed into its constructor indicates which. The resource ID is IDD_DIALOG_PORTS. Depending on fIsClusterLeve, the class selectively enables/ disables certain elements. As with HostPage, the controls are members, which interfact "automagically" with the UI, using DoDataExchange, and these controls are loaded/saved using mfn_LoadFrom/SaveToNlbCfg. ClusterPortsDlg: public CDialog -- launched to edit a portrule from a cluster perspective -- only cluster wide changes are allowed. Resource ID: IDD_DIALOG_PORT_RULE_PROP_CLUSTER UNLIKE PortsPage, this class doesn't use dde to map ui to member controls -- instead it uses win32 apis, for example: :SendDlgItemMessage(m_hWnd, IDC_EDIT_MULTI, ....); Also it loads/saves UI state to/from member m_portData, which is passed by REFERENCE to its constructor. The parent window (expected to be of type PortsPage) is also passed into the constructor. Stuff is loaded from m_portData by method SetControlData, and saved to m_portData by method OnOk. m_portData is of type PortsPage::PortData, which is simply: struct PortData { PortData(); DWORD key; _bstr_t start_port; _bstr_t end_port; _bstr_t protocol; _bstr_t mode; _bstr_t priority; _bstr_t load; _bstr_t affinity; }; Historical note: this code has been modified from the original version, not re-written. In the original version there were subclasses for each type of port rule, a bunch of methods for handling each type and so forth. Also it's unfortunate that this is "yet another" data structure for port rule info. HostPortsDlg: public CDialog -- launched to edit a portrule from a host perspective -- only host-specific properties can be changed. Resource ID: IDD_DIALOG_PORT_RULE_PROP_HOSTS Its orginaziation is very similar to ClusterPortsDlg above. 09/22/2001 JosephJ details on the "LogView" The lower region of nlbmgr is maintained by an instance of class LogView : public CListView. The class is implemented in logview.h and logview.cpp. It's constructor gets called (probably) in the context of Application::InitInstance, as does it's initialization function, LogView::OnInitialUpdate. In OnInitialUpdate, LogView inserts a bunch of columns into it's list control, and adds a starting entry (which needs to be localized!) "NLB Manager session started". Thereafter, the only interesting thing it does, is to get calls to it's LogString function from the document instance. The LogString prototype is: void LogString( IN IUICallbacks::LogEntryType Type, IN const wchar_t *szCluster, OPTIONAL IN const wchar_t *szHost, OPTIONAL IN const wchar_t *szText ); LogString adds an entry to the log, with an icon determined by the value of "Type". TODO: how do we save logs -- by symultaneously logging to the application event log or by adding a "save log" feature (the menu item "save log" is already present, and does nothing currently). If we decide to save the log, we need to decide on a format: plain text or simple xml like: NLB Manager session started Of course we want to avoid creating ad hoc formats! paramp -> i_convert_mac = TRUE; // TODO 09/15/01, shouse Context sensitive help has been attached to all known dialogs. For the three NLB netcfg UI clones, all help appears to work except for the "Cluster IP address" group box on the Add/Edit port rule (properties) page - this is also missing in the netcfg UI. To add the hook for context sensitive help on other dialogs, you need to add a help handler to the class - see hostpage.cpp and hostpage.h for an example (OnHelpInfo and OnContextMenu). Further, you need to create a table of mappings from UI IDs to help IDs - in NLB we always use the UI ID as the host ID to simplify things. See hostpage.h for an example table. Then, have the UI text people (Joe Holliday) write the help for the needed components and give him the UI ID to hook into. Joe will send a private wlbs.hlp to test with (place this file in %WINDIR%\help\. Be sure to review and edit the help - Joe is VERY open to our suggestions on wording and content. Support for the new initial host state, persisted states and per-VIP port rules has been added to the appropriate dialogs. Most dialogs were reviewed for layout and text and were mod- ified - several new dialogs, or some legacy dialogs that may not be used any more were not updated yet. Also, the equal checkbox has been added to the cluster-level port rule properties dialog. At this time, the host-level can not change this setting - if the rule is equal, the host can not make it unequal and set a load weight (the rule must be changed to unequal at the cluster-level first), so if we want hosts to be able to change this, changes will be necess- ary in at least clusterportsdlg.cpp. 09/25/01 karthicn : Notes to self The FinalInitialize function (in document.cpp) is called in a background thread from Document::registerLeftView. The only thing that FinalInitialize does is : If a file name is provided in the command line, it reads host names from the file connects to them. May want to check if the file name is specified in the command line before spawning off the thread. 09/26/2001 Credentials UI -- the proper way of managing utilities. From Mike Lai of the secure windows initiative team (http://swiweb, email: switeam): "Please use Cred UI, Cred Mgr in LSA makes sure the sensitive data is protected via LsaProtectMemory/LsaEncryptMemory which is encrypting the data while the data is memory." John Franco in MSCS (Dave Potter's) team pointed me to http://benhutzdev, which contains API documentation for the Credentials API. From those docs, it appears that the APIs I nead to call are: - CredUIPromptForCredentials with the CREDUI_FLAGS_GENERIC_CREDENTIALS - CredWrite specifying CRED_TYPE_DOMAIN_VISIBLE_PASSWORD. - CredRead Additinally, it appears that I don't need to call CredWrite if I specify the CREDUI_FLAGS_PERSIST flag in CredUIPromptForCredentials. I've asked John Franco for sample code. From: Cliff Van Dyke Sent: Wednesday, September 26, 2001 10:17 AM Subject: RE: Question on how to safely cache user credentials in a user-mode program. OK. Here's my recommendation. You should you credui to prompt for the credential since we want to have that as the only UI being used. You shouldn't use credman to store the cred since credman creds are used by all processes and not just yours. If NLB manager isn't the process doing the prompting, you should encrypt the password using RtlEncryptMemory with the RTL_ENCRYPT_OPTION_CROSS_PROCESS flag as you pass the password between processes. In the NLB manager, while the password isn't actively being used, you should encrypt the password using RtlEncryptMemory with no special flags. Any buffer that temporarily contains the password should be zeroes as soon as you're done with it. SwiTeam should be able to point you to a web page with more detailed related info. In a future release, the "password" returned to you from credui will be a "handle" to data stored more securely. That handle will be accepted as the password parameter to any API that uses NTLM or Kerberos. 9/27/01 ChrisDar Things that still need to be fixed with logging: 1. Macro var names MAXFILEPATHLEN and MAXSTRINGLEN are hokey and should be changed. Also value should be reviewed. 2. Currently logging class is called even when logging is not enabled because internal to method is where enabled flag is checked. Need to move the check into LogView::LogString for efficiency. 3. Related to 2., LogView gets back LOG_RESULT enum for some calls to document class to tell it what error to report in error message dialog. I realize now that this can be done in the document class itslef. This should changed so that the LogView class just knows an error occurred and can react as needed. The dialog should be popped in the document class. 4. When reading log file name from registry, we don't truncate but give an error if file name is too long. Need to figure out how to read N bytes even if there is more available. 10/07/2001 Got the following error in setupapi.log during an install... Build 3563.Lab03_N.011003-1919 [2001/10/05 15:40:37 1740.157 Driver Install] #-019 Searching for hardware ID(s): ms_wlbsmp #-198 Command line processed: D:\WHISTLER\System32\wbem\wmiprvse.exe #I022 Found "ms_wlbsmp" in D:\WHISTLER\inf\netwlbsm.inf; Device: "Network Load Balancing Filter Device"; Driver: "Network Load Balancing Filter Device"; Provider: "Microsoft"; Mfg: "Microsoft"; Section name: "WLBSMP.ndi". #I023 Actual install section: [WLBSMP.ndi]. Rank: 0x00000000. Effective driver date: 07/01/2001. #I063 Selected driver installs from section [WLBSMP.ndi] in "d:\whistler\inf\netwlbsm.inf". #I320 Class GUID of device remains: {4D36E972-E325-11CE-BFC1-08002BE10318}. #I060 Set selected driver. #I058 Selected best compatible driver. #-166 Device install function: DIF_INSTALLDEVICEFILES. #I124 Doing copy-only install of "ROOT\MS_WLBSMP\0000". #-166 Device install function: DIF_REGISTER_COINSTALLERS. #I056 Coinstallers registered. #-166 Device install function: DIF_INSTALLINTERFACES. #-011 Installing section [WLBSMP.ndi.Interfaces] from "d:\whistler\inf\netwlbsm.inf". #I054 Interfaces installed. #-166 Device install function: DIF_INSTALLDEVICE. #I123 Doing full install of "ROOT\MS_WLBSMP\0000". #-035 Processing service Add/Delete section [WLBSMP.ndi.Services]. #E279 Add Service: Failed to create service "WLBS". Error 1021: Cannot create a stable subkey under a volatile parent key. #E033 Error 1021: Cannot create a stable subkey under a volatile parent key. #E275 Error while installing services. Error 1021: Cannot create a stable subkey under a volatile parent key. #E122 Device install failed. Error 1021: Cannot create a stable subkey under a volatile parent key. #E154 Class installer failed. Error 1021: Cannot create a stable subkey under a volatile parent key. #-166 Device install function: DIF_REMOVE. #I289 Removing device "ROOT\MS_WLBSMP\0000". #I048 Device removed. 10/07/2001 JosephJ managing multiple VIPs - New cluster: NlbCfg list includes only primary vip. - Existing cluster: NlbCfg list includes all ips in tcpip except dip, if present. - Per-host cluster info: NlbCfg list includes all ips in tcpip except dip, if present. So to simply the vipslist code, it'll alwaws exclude the dip from the list, if it sees a non-null dip. Todo: 1. vipspage: reload on set-active, save on kill active. reload: Clear and re-build list view save: if list view modified: re-populate addresses, adding dip and then vip at head of the list. 2. vipspage: add: call gEngine.ValidateAddress( szAddress, ehCluster, ehHost, cip/pprvip/dip, out szErr); 3. Apply-to-per-host: - for each ip in old list if found in new list add to tmp list (this preserves the order of old addresses) - for each ip in new list if found in tmp list set cursor to this location in tmp list else insert after cursor, set cursor to this location in tmp list Add new host/connect to existing: - The trick here is that we need to preserve the existing host-specific settings as far as possible (ONLY if the NIC already has the *save* cluster IP). The user can select an arbitrary NIC -- we need to use that's NIC's host-specific settings. If the user goes back to the connect page and selects a different NIC, we need to switch to the new NIC's settings. - On startup, if a NIC has the same IP address, we make that the default selection. - So each time the NIC selection changes - Make a copy of of the NIC's current settings - If bound to NLB - if it has a different IP address: put warning msg - We apply the cluster-wide properties in the current m_pNlbCfg (which is the cluster's version) to the copy of the NIC's current settings. - We copy the result back to m_pNlbCfg. 10/25/2001 JosephJ Host and Port priorities. Because NLB manager allows multiple hosts to have pending activity at the same time, we need to keep a list of pending host priorities and port-priorities. The UNION of current and pending host priorities and port-priorities is used to get the list of currently taken priorities -- the inverse consists of available priorities. This inverse set *could* be empty -- tough luck. So each cluster in the engine needs to keep: (a) bitmap of pending host ids (b) map[start-port] of bitmaps of pending port-rule ids NewCluster AddHost HostProperties ConnectDialog: Constructor: AddHost: set the host priority in m_pNlbCfg to a good default, taking into account available hosts in the cluster (ask engine). Likewise, set the single-host port priorities appropriately (ask engine). NewCluster: may as well do the same as above, so we don't differentiate. NONONO: instead, changed this so that where add host is CALLED (in leftview) we set up the defaults appropriately. OnButtonConnect: NewCluster: AddHost: if (IF already bound to cluster with same IP) { apply cluster properties to existing interface props, save the config to m_pNlbCfg. } OnSelChange: (we don't allow changing if there exists an IF which is already bound to the clsuter (i.e. cluster IP matches). NewCluster: AddHost: don't need to modify m_pNlbCfg. HostPage: Constructor: if (!new cluster) { -- initialize host prority bitmap with currently available priorities (ask engine) -- initialize array of port-rule-priorities bitmap. } On mfn_LoadFromNlbCfg: populate the host priorities listbox with list of available host priorities + m_pNlbCfg->host-priority, select the current value of the latter. On mfn_SaveToNlbCfg: save the selected host priority to m_pNlbCfg->host-priority. HostPortsDialog: SetControlData: if (single-host) { Get list of available priorities for this port rule (ask engine). } Fill out list box accordingly. Q: What if on new-cluster, user specifies port rules, connect, select an IF, go back to the port rules and change them around? A: Since its a new cluster, all port priorities are available at this point. 10/27/2001 JosephJ Note on the layout of the views This is done in MainForm.CPP -- in class MainForm. The class has two splitter windows (CSplitterWnd) splitterWindow, and splitterWindow2. The individual views are refered by a coordinate system -- splitterWindow.CreateStatic( this, 2, 1 ); <-- creates a static splitter window with two rows and one column. splitterWindow2.CreateStatic( &splitterWindow, 1, 2, WS_CHILD | WS_VISIBLE | WS_BORDER, splitterWindow.IdFromRowCol( 0, 0 ) ); <-- creates a CHILD splitter window, with one row and two columns (the 1,2 as second and third args) The location of the child in the PARENT splitter window is (0,0) -- the call to splitterWindow.IdFromRowCol(0, 0); splitterWindow2.CreateView( 0, 0, RUNTIME_CLASS( LeftView ), CSize( 0, 0 ), pContext ); <--- says to create an instance of LeftView at coordinate (0,0) of the CHILD splitter window. Etc... 10/27/2001 JosephJ Note on handling menu controls Moved handling of menu controls from LeftView to MainForm, because left view wasn't getting the control messages (naturally) when the focus was not on the left view. Currently MainForm calls left view's public methods for control operations -- strictly speaking these methods also need to migrate over to MainForm -- left view should just be consulted on the currently selected item. However, in the interest of minimizing code changes, the implementation of the controls remains in LeftView. So with this change the menu items work even if the focus is not on the left view. 10/30/2001 JosephJ Cases to test: 1. Connect to existing: a: 0 nics are bound to cluster OK b: 1 nics is bound to cluster OK c: 2 nics are bound to clusters OK if no change in sel e: switch NIC selections and verify things work. BUG f: switch host names and verify things work. OK (recheck) 2. New cluster: a: 0 nics are bound to cluster b: 1 nic is bound to cluster (a different cluster) c: 1 nics are bound to cluster (same cluster) d: 2 nics are bound to cluster (one same one different) e: switch NIC selections and verify things work. f: switch host names and verify things work. If same: check that all host-specific properties are preserved. (including null ded-ip if specified) If not same: check that dedicated IP address is filled in: - with 1st IP address on NIC 3. Add Host: a: 0 nics are bound to cluster b: 1 nic is bound to cluster (a different cluster) BUG -- priority clashes with existing c: 1 nics are bound to cluster (same cluster) d: 2 nics are bound to cluster (one same one different) e: switch NIC selections and verify things work. f: switch host names and verify things work. BUGS that were fixed recently: x AV when connecting (via new cluster) to an existing cluster, then moving back and forth between pages. Via add host also, I think. - Seems to be fixed by taking out a reference to uninitialized memory. x In connect-to-existing, if 2 nics are bound, on selecting a nic we get the "next" button, not finished button. x In new-cluster UI, got some host-priorities blanked out -- should be all available? Once cluster was created, didn't see this problem. x In add host the host priority was 1 -- clashes with the first host. 10/30/2001 JosephJ Handling the "F5" key The CWnd method is OnKeyDown, need to check that the first arg (nChar) is equal to VK_F5. I tried getting mainform to take the F5, but its OnKeyDown afx handler was not getting called. I then added the handler to LeftView instead and it works -- need to add the afx handler as well as mention it in the message map -- search for OnKeyDown in leftview.cpp. 10/31/2001 JosephJ handling the double clicking of list view items (logview) Add the following message handler to the listview: ON_NOTIFY_REFLECT(NM_DBLCLK, OnDoubleClick) In OnDoubleClick, cast the first arg to LPNMLISTVIEW. LPNMLISTVIEW.iItem is the list view item index (zero based). See LogView::OnDoubleClick for details. 11/05/2001 JosephJ trying to get the right (details) view to have a caption: Setting the WS_CAPTION windows style didn't work -- produced a large blue caption with rounded borders, and allowed resizing within the application. I tried other combinations of the WBL_STYLE property -- no luck. I then tried putting an additional splitter window above the existing one -- this produced a movable border between the two windows -- I didn't want any border -- couldn't figure out how to get rid of the border. Finally tried using a CFormView -- this appears to be the way to go. Need to specify the dilog ID in the RC file, and this dialog should have only WS_CHILD as it's window properties. 11/05/01 update: The details view comes up with the valid dialog template. HOWEVER it was not being filled out -- inserting columns and items etc into the list control wasn't being updated in the uI. Fix: Call UpdateData(FALSE); in OnInitialUpdate -- this is mentioned in Chapter fifteen (Separating the Document from Its View), step #8 (Edit the file Ex15aView.cpp, pp 356), in the book Inside Visual C++ 4th ed (MS Press). Apparantly unlike true Dialogs, the CFormView class doesn't call UpdateData automatically, so we have to call it ourselves. Go Figure (took me a while to figure this out). 11/05/2001 JosephJ Details view: selectively displaying the icons: If you don't want to display the icons next to each item, call SetImageList(NULL,...). Otherwise the first icon of the previously used image list is used whether you want it or not. For example, when listing the port rules, the image list for the cluster view was being used. 11/10/2001 JosephJ Redesign of the format of ENGINEHANDLE See "09/20/2001 JosephJ map wierdness." 1. Keep separate counters for each kind of handle. 2. First 2 bits of ENGINEHANDLE define type of handle. 0 -- invalid 1 -- interface 2 -- host 3 -- cluster Implemented the above, except that I keep a single counter for simplicity, through we'll run out of handles after 1billion creations vs 4 billion. 11/13/2001 JosephJ more things to test: picking username and pwd in connect Connecting to machine -- we look up the connection string -- if we find a match, we'll use that machine's username and password, rather than the default. 11/13/2001 JosephJ Hooking TreeView double-click and key strokes DoubleClick: Message map: ON_WM_LBUTTONDBLCLK() afx function: OnLButtonDblClk( UINT nFlags, CPoint point ) Keystrokes: Message map: ON_WM_KEYDOWN() afx function: LeftView::OnKeyDown( UINT nChar, UINT nRepCnt, ...); 11/18/2001 JosephJ Pending operations -- take 2 [See 09/19/2001 JosephJ Notes to self] - Engine has private method: mfn_BeginOperationLk(ENGINEHANDLE ehObj, bstrOperation, OUT bstrPendingOp). if ehObj is type cluster: - we'll first check if there's any pending operation on this cluster, if not we mark cluster started - we'll then try to start this operation on all the member interfaces. - if failure, we stop all we tried to start above and return failure, filling bstrPendingOp with the description of any pending op that caused the failure. if ehObj is type interface: - we'll first check if there's any pending operation on this interface, if not we mark interface op started. - we'll then try to start this operation on the host - if failure, we stop all we tried to start above and return failure, filling bstrPendingOp with the description of any pending op that caused the failure. if ehObj is type host: - we'll first check if there's any pending operation on this host, if not we mark host op started. - on failure, we'll bstrPendingOp with the description of any pending op that caused the failure. EXCEPTION: if the operation we're asked to start is already started, we won't fail -- instead we bump up an internal repeat count. This is so that we can start a cluster-wide operation and as part of that operation start a host or interface operation without the latter failing claiming that an operation is already pending. CAUTION: need to make sure that interfaces aren't added to or removed from clusters or hosts while a cluster-wide operation or a host-wide operation is pending. This is so that the correct list of interfaces are stopped when the cluster/host operation is stopped. mfn_EndOperationLk is the inverse of mfn_BeginOperationLk. Helper class Operation below will help with the implementation. EXTRA: need way to (a) cancel pending operations and (b) disallow new pending operations and (c) enumerate pending operations -- This is mainly to handle proper cleanup on existing the program. class Operation { ... public: static OPERATIONHANDLE NewOperationHandle(); static void ReleaseOperationHandle(OPERATIONHANDLE); NLBERR Start(OPERATIONHANDLE oh, _bstr_t bstrDescription, OUT pendingDescr); void Stop(OPERATIONHANDLE oh); private: static UINT next_handle; _bstr_t m_bstrDescritpion; OPERATIONHANDLE m_ohCurrent; UINT m_uRepeatCount; } 12/12/2001 KarthicN Redundant calls from ClusterPage.cpp & HostPage.cpp ClusterPage.cpp : During cluster creation, ie. wizard mode ClusterPage::OnNotify is called with PSN_KILLACTIVE. The calls made are 1. CCommonClusterPage::OnKillActive to read data from UI 2. CCommonClusterPage::OnApply to validate the data 3. If validation is successful, ClusterPage::mfn_SaveToNlbCfg : This function calls CCommonClusterPage::OnKillActive. This call seems to be redundant During modification of cluster properties, ie. non-wizard mode 1. ClusterPage::OnNotify is called with PSN_KILLACTIVE. The calls listed above are made 2. ClusterPage::OnNotify is called with PSN_APPLY. The following calls are made in this step 2.1 : CCommonClusterPage::OnApply to validate the data : This call is redundant since it was called in #1. 2.2 : CPropertyPage::OnNotify : This leads to a call to ClusterPage::OnOK(). ClusterPage::OnOK() calls ClusterPage::mfn_SaveToNlbCfg. This call is redundant since ClusterPage::mfn_SaveToNlbCfg was called in step #1. Summarizing, the actions to be taken are : 1. In ClusterPage::mfn_SaveToNlbCfg, remove the call to CCommonClusterPage::OnKillActive 2. In ClusterPage::OnNotify for PSN_APPLY, remove call to CCommonClusterPage::OnApply 3. In ClusterPage::OnOK(), remove call to ClusterPage::mfn_SaveToNlbCfg HostPage.cpp : During host addition, ie. wizard mode HostPage::OnWizardFinish() is called The calls made are 1. mfn_ValidateData to validate the data 2. If validation is successful, mfn_SaveToNlbCfg During modification of host properties, ie. non-wizard mode 1. HostPage::OnKillActive() is called. The following calls are made in this step 1.1 : mfn_ValidateData to validate data 1.2 : If validation is successful, mfn_SaveToNlbCfg 2. HostPage::OnOK() is called. mfn_SaveToNlbCfg is called here. This call is redundant since it was just made in #1.2. Summarizing, the actions to be taken are : 1. In HostPage::OnOK(), remove call to mfn_SaveToNlbCfg 12/15/2001 JosephJ How to add a menu item There are two kinds of menus -- the application menu and the ctxt-sensitive menu. Most functionality is available in both, so when adding a menu item they probably need to go to both places. 1. Add to RC file: -- [Main menu] Add under IDR_MAINFRAME MENU DISCARDABLE this adds items to the main menu. -- [Ctxt sensitive menu] Add under IDR_POPUP MENU DISCARDABLE -- add the constants to resource.h 2. Add OnXXX menu item handler to MainForm (mainform.h, .cpp) follow the form of the existing menu item handlers -- you'll see that it just calls the LeftView handler by the same name (see note 10/27/2001 JosephJ Note on handling menu controls) 3. Add OnXXX menu item handlers to LeftView (leftview.h, .cpp) this handler will actually do the action of the item. 4. Control whether you want the menu item to be enabled or disabled depending on what item is selected in the leftview -- add items to LeftView::mfn_EnableHostMenuItems and LeftView::mfn_EnableClusterMenuItems (or create a NEW mfn_EnableXXXMenuItems if it's a whole new catigory). 12/15/2001 JosephJ misconfiguration UI Implemented the following: 1. 207015 Reporting Cluster Misconfiguations - Add "View Status" menu item to ctxt-sensitive and main manu. - Bring up status dialog when user selects this. - Maintain status -- misconfiguration adds the status caption and details string. It's a bstr, so should be ok. 12/15/2001 JosephJ Logview: LVS_NOSORTHEADER Added LVS_NOSORTHEADER to logview style because we don't need to sort columns. 12/15/2001 JosephJ Fixed unresponsive dialogs spawned by ctxt-sensitive mnu I noticed that the dialogs are responsive to keystrokes just not responsive to the mouse (well, until you force a re-draw of the window by temporarily overlapping it with another window). So I figured that its a mouse-specific thing that somehow the processing of the r-button click was not proper so on a hunch I called the base class function FIRST: i.e, moved the call CTreeView::OnRButtonDown(nFlags, point) to the START of the function LeftView::OnRButtonDown. That did the trick! 12/16/2001 JosephJ Dealing with remote control password 1. Add new fields to EXTCFG: BOOL fSetPassword; DWORD dwNewHashedPassword; 2. Add related methods to EXTCFG: SetNewRemoteControlPassword(szPwd) SetNewHashedRemoteControlPassword(dwPwd) ClearNewRemoteControlPassword(void) 3. In nlbclientlib, it will ONLY set the szPassword or the hashed pwd if the fSetPassword field is set. 4. EXTCFG::Update does not copy over the newpwd field -- instead it clears it. 5. In analyze-interface, an error is reported if: - rct-enabled is different in cluster vs IF props - rct-enabled in both cluster and IF props, but dwHash value is different. In the misconfig report, the user is asked to fix this by re-entering the rct password. 6. ONLY place where new Hashed rct pwd is specified is when adding a node to a cluster. clusocm.dll copies files and handles upgrade scenarios, etc. On a fresh install, it puts cluster admin to the adminstrative tools thing. 12/26/2001 JosephJ done the following a week ago: 506822 Port control window unresponsive 207015 Reporting Cluster Misconfiguations 499068 save host list includes clusters or hosts that were removed 503027 Connect dialog: duplicate hotkey (N) 503031 Duplicate hotkey (N) 503046 Log Settings: duplicate hotkey (E) 504165 "Host\Delete" menu item is disabled when host is selected. 209117 UI to manage cluster VIPS: troubleshoot and fix any issues 502585 Update details view 12/27/2001 JosephJ fixed the following ... 203647 NLBManager should wrap the WMI error... 326514 Modify remote-control pwd, report differences in pwd: 509397 error check: when inital state conflicts a warning is given... 509400 error check: add check for conflicting port rule where affinity.. 12/27/2001 JosephJ Getting rid of literal strings in Nlbmgr.exe 484842 For localization compatibility, replace all %xx fields by %num This was a pretty big effort -- there were literal strings all over the place. I grepped for " (dquote), made a list of each one, went through each and tested each case. I added the Log method to CLocalLogger and used it in numerous places. Another effort was to move to the %1,%2 etc FormatMessage format for a bunch of strings, mainly port-rule-descriptions. 12/29/2001 JosephJ Making "equal/unequal" port rule state host-specific 202129 Make equal/unequal property of port rules a host-specific property. Look for BUGFIX202129 conditionally compiled code. Summary of changes: clusterportsdlg: hide the equal/unequal checkbox and don't try to read/write it. Change the load description from equal/unequal to "empty" (i.e. "--"). hostportsdlg: enable the equal/unequal checkbox, and read it's value. Add a handler for the checkbox events, and in the handler enable/disable the load-weight controls appropriately. portspage.cpp: change logic for handling equal/unequal when in cluster vs host mode. engine.cpp: ignore cluster equal/unequal and load weight when applying cluster wide properties to an individual host. 12/29/2001 JosephJ Reporting provider-side update results log. Relevant bug: 203634 Nlb Manager should report useful error when netcfgs.. To do: 1. Nlb Manager should report the results of the log. -- In GetUpdateResult, pick up the log and set the host status field with that info. YES!!! -- On update completion, fill in the log with the result. 2. NLBMPROV.DLL -- if fAsync, should do a check if it can get the netcfg write lock, and fail if it cant (with appropriate log message). [Addendum] Implemented both of the above. 01/01/2002 JosephJ Tracking available single-host port rule priorities Implemented gNlbEngine::GetAvailablePortRulePriorities, and use it in hostportsdlg.cpp and engine.cpp to choose proper host priorities. Relevant bug: 485541 Provide valid defaults for host-priorities to single-host port rule 01/01/2002 JosephJ Full reporting of misconfigurations and config difs Relevant bugs: 360469 Warn user if whole cluster will come down because of operation. 502793 warn user when attempting to remove / change DIP 288769 error check: Verify that Primary IP, DIP and additional VIPs' .. The plan is to write a utility function with the following prototype: NLBERROR AnalyzeConfigurationPair( IN const EXTCFG &Cfg, IN const EXTCFG *pOtherCfg, IN BOOL fOtherIsCluster, IN BOOL fCheckOtherForConsistancy, OUT BOOL fConnectivityChange, IN OUT CLocalLogger &logErrors, IN OUT CLocalLogger &logDifferences ); // // logErrors - a log of config errors // logDifferences - a log of differences between // Cfg and UpOtherCfg. // fCheckOtherForConsistancy -- if true, we will check Cfg // against pOtherCfg. If fOtherIsCluster, we expect // cluster wide properties to match, else we expect // cluster-wide properties to match as well as host-specific // properteis to not conflict. // Also a new related function for analyzing a SINGLE configuration for validity with itself (eg CIP should be in the IP address list): NLBERROR AnalyzeNlbConfiguration( IN const NLB_EXTENDED_CLUSTER_CONFIGURATION &Cfg, IN OUT CLocalLogger &logErrors ); These two functions are a superset of the existing functions analyze_nlbcfg(in engine.cpp) and EXTCFG::AnalyzeUpdate and CfgUtilsAnalyzeNlbUpdate. So we'll stop using those functions and use these functions instead. Traffic mode change: From unicast to multicast WARNING: asda asdfasdf asdfasdf IP addresses to be added: 10.0.0.1/255.255.255.0 10.0.0.1/255.255.255.0 10.0.0.1/255.255.255.0 10.0.0.1/255.255.255.0 IP addresses to be removed: 10.0.0.1/255.255.255.0 10.0.0.1/255.255.255.0 IP addresses to have subnet mask changed: 10.0.0.1: From 255.255.255.0 to 255.255.0.0 Cluster name change: From "" to "" Remote Control: From enabled to disabled New password specified Some port rules were added, removed or modified. Cluster-wide-control: stops and suspends. 502793 warn user when attempting to remove / change DIP 01/04/2002 JosephJ Bug in CNlbEngine::mfn_ReallyUpdateInterface -- keep copy of a internal bstr pointer without lock held. ConnInfo.szUserName = (LPCWSTR) pHSpec->m_UserName; (and related bstrs) -- should replace by keeping keeping bstr local variables. Check for other places where this can occur. 01/04/2002 JosephJ -- wierd "/" ip addresses showing up (gjack) 01/08/2002 JosephJ checked in to LAB03 fixes for the following 203647 NLBManager should wrap the WMI error... 326514 Modify remote-control pwd, report differences in pwd: 509397 error check: when inital state conflicts a warning is given... 509400 error check: add check for conflicting port rule where affinity.. 484842 For localization compatibility, replace all %xx fields by %num 202129 Make equal/unequal property of port rules a host-specific property. 203634 Nlb Manager should report useful error when netcfgs.. 485541 Provide valid defaults for host-priorities to single-host port rule 288769 error check: Verify that Primary IP, DIP and additional VIPs' .. 360469 Warn user if whole cluster will come down because of operation. 01/08/2002 JosephJ Checks for preventing attempt to remove/change DIP 502793 warn user when attempting to remove / change DIP (Also, check if new dip is already used elsewhere) 1. Place to add check: HostPage::mfn_ValidateDip 2. What to check for? LookupIfByIP, see if IF matches LookupHostByConnIP -- see if we find one. LookupClusterByIp -- fail if we find one. 01/10/2002 More IP address checks If adding a new host or creating a new cluster, and the cip matches the IF already on that cluster, we end up having the same dip and VIP. Need to fix this case. Still problems: If you pick an IF with first IF matching cip, we now put blank. However, if you go back to cluster page, make a change, then back again to host page, we don't appear to re-do this change. We should LOCK DOWN the if if we find that the cip matches that IF. 01/11/2002 JosephJ Steps to add support for query cluster members 1. Implement SKELETON CfgUtilGetClusterMembers -- just return failure Prototype is already defined in nlbmgr\inc\cfgutil.h Implementation goes in nlbmgr\cfgutillib\cfgutil.cpp, below function CfgUtilControlCluster. 2. Implement test harness in nlbmgr\provider\tests\tprov.cpp Implement function parse_query (see parse_control for sample code) Suggestded output format: HosdID DedicatedIP HostName ---------------------------------------------------- 1 0.0.0.0 "nlb-x.cheesegalaxy.com" 2 0.0.0.0 "nlb-y.cheesegalaxy.com" ... etc 3. Test test harness itself... Compile tprov.exe with the line "// NlbHostFake();" in tprov.cpp UNcommented. Using this version, type: tprov.exe nlb-x u nic1 query ; to call function parse_query q ; to quit The above exercises the WMI version. Since it's useing the fake (demo) version, it connects to fake host "nlb-x" and loads the configuration for NIC "nic1". The "query" command applies to that nic. NlbHostGetClusterMembers (running in fake mode) will return info for a single member -- so you can test your parse_query() code to make sure it is displaying the output correctly (see sample format above). Now using the same tprov.exe, type: tprov.exe - al This will list a list of REAL NICs and guids on your test machine. Then pick a NIC xxx and type tprov - u xxx query q This will again call parse_query, but this time it should call CfgUtilGetClusterMembers (which you have implemented a skeleton version in sstep 1). NOTE: the Real function is called for this, not any fake stuff (the fake stuff applies only to WMI). 4. Implement CfgUtilGetClusterMembers fully (By now your test harness has been implemented and tested) Test using: tprov.exe - u xxx (where xxx is the adapter name) query q 5. Implement NlbHostGetClusterMembers Prototype is already defined in nlbmgr\inc\nlbclient.h Skeletion implementation exists in nlbmgr\nlbclientlib\nlbclient.cpp Look at NlbHostGetCompatibleNics for an example of dealing with arrays of strings. 6. Implement SKELETON ProvGetClusterMembers in nlbmgr\provider\nlbsnic.cpp The prototype is already defined in nlbsnic.cpp. Implement it below existing function ProvControlCluster Call it from ProvExecStaticMethod (just below the call to ProvControlCluster, if bstrMethodName matches "GetClusterMembers"). The skeleton implementation can just populate the 3 string arrays with fake data, just so you can make sure that all the wmi gook (both client and server) are working before you plug in the real call to CfgUtilGetClusterMembers 7. Test the above skeleton implementation using (locally, using WMI): tprov . u xxx (where xxx is the adapter name) query q The query output should list the fake info you added in Step 6. 8. Implement and test ProvGetClusterMembers fully Make it call CfgUtilGetClusterMembers. Refer to ProvControlCluster, and ProvGetCompatibleAdapterGuids for sample code. Test using procedure in Step 7. Additionally test remote operation by using: tprov mmm u xxx (where mmm is machine name and xxx is the adapter name) query q 01/15/2002 JosephJ Plan for Mode Change 1. If mode change, bring up a message box explaining the steps... 2. Save away a copy of the cluster params IP address list. 3. Do the update operation once. 4. When it completes, check that all IFs have the same mode and that they match the cluster-specific version. If NO, list the IPaddresses and subnets in the log entry and mention what happened. If YES, re-enter the cluster IP addresses and try again. Issue: once we make update parallel, what will we do? UpdateInterface 01/21/2002 JosephJ Latest thoughts on RefreseshInterface, RefreshCluster, etc.. New policy on when the cluster-wide copy of properties is updated. Cluster-props are ONLY updated on a refresh-cluster. In this case, the properties are updated only for the FIRST host that has NLB bound to the specified IP address. They are NOT updated when: add host refresh host update cluster props update host props The above behavior is different than what is implemented currently, when the cluster is refreshed more aggressively, for example, when updating a host's properties. However it is an improvement, as it has simpler semantics and makes it more clear (via misconfig reporting) when there are discrepancies between the cluster-wide version and host versions. So there will be two RefreshInterface versions: RefreshInterface(BOOL fClusterRefresh) has the following semantics: if (!fClusterRefresh) { Verify that there is no cluster-wide operation ongoing. } Start interface operation (bail if we can't start) call mfn_RefreshInterface(TRUE); // TRUE == report misconfig. Remove interface if not in cluster Remove cluster if no more interfaces in cluster mfn_RefreshInterface(BOOL fReportMisconfig) { really refresh interface if (fReportMisconfig) { Check against cluster props and all non-misconfig hosts upto this one, and report misconfigurations if any } } RefreshCluster (or rather UpdateInter with NULL pConfig) { start-cluster-opearation (bail if can't start) BOOL fClusterPropsUpdated = FALSE; for each interface { fRet = RefreshInterface(TRUE); // TRUE == cluster refresh if ( fRet && !fClusterPropsUpdated && props bound to NLB && cluster-ip matches ) { Update cluster props for fClusterPropsUpdated = TRUE; } } } 01/22/2002 shouse Features added: * F5 refreshes only the cluster or interface selected in the tree view. CTRL+F5 refreshes ALL clusters in the tree view. * A new command line option, /autorefresh [interval], will instruct NLB manager to automatically refresh its view every seconds. By default, this is not turned on and must be specified on the command line to activate it. The default is 60 seconds and the minimal allowable is 15 seconds. * Cluster IPs dialog is now a listview with IP address control popups to edit and add virtual IP addresses to a cluster. * Added columns in the host and port rule detail views to contain statistics and state, such as the state of a port rule and approximate load being handled, and some convergence information, such as the total number of convergences by a particular host and the time of the last convergence. Along with auto-refresh, these should give a user a nice way to monitor some information about a live cluster, rather than just using NLB manager for configuration. Note the the WMI support for retrieving this information is not yet available, so the columns are empty - they will be supported soon. * Lots of UI cleanup was done - hopefully this is the last round. Notes: To eliminate the selected text in an edit box, which seems to select everything by default, subscribe to the WM_ACTIVE notification and SetSel(0, 0, FALSE). This does not work when executed from OnInitDialog for some unknown reason. To decipher multiple key combinations such as CTRL+F5, subscribe to the WM_KEYDOWN notification and ignore all keys other than the one(s) you're filtering for, including modifiers (CTRL, ALT, Shift, etc.). When the key you're interested in is pressed, use GetKeyState(VK_WHATEVER) to retrieve the up/down state of any modifiers you're interested in. GetKeyState returns a SHORT. If the most signif- icant bit it set, the key is pressed down; otherwise, it is not. For example: void OnKeyDown (UINT nChar, UINT nRepCnt, UINT nFlags) { if (nChar == VK_F5) { if (GetKeyState(VK_CONTROL) & 0x8000) ::MessageBox(NULL, L"The CTRL is pressed!!!\n", L"Notice", MB_ICONINFORMATION | MB_OK); else ::MessageBox(NULL, L"The CTRL is NOT pressed!!!\n", L"Notice", MB_ICONINFORMATION | MB_OK); } } To re-size a control within a window or frame, subscribe to the WM_SIZE noti- fication of the window or frame. In the handler for this notification, use GetClientRect to get the window coordinates of the window or any controls within it. Use MoveWindow to resize the control you wish to modify. For example, suppose you wanted to resize an edit control to be the size of the window around it, but with a border of 15 pixels on each side. To do this: void OnSize (UINT nType, int cx, int cy) { RECT WindowRect; RECT MyRect; /* Get a pointer to the edit box. */ CWnd * pEdit = GetDlgItem(IDC_EDIT_WHATEVER); /* Get the client rectangle of the window. */ GetClientRect(&WindowRect); MyRect.top = WindowRect.top + 15; MyRect.left = WindowRect.left + 15; MyRect.bottom = WindowRect.bottom - 15; MyRect.right = WindowRect.right - 15; pEdit->MoveWindow(&MyRect, TRUE); } 01/23/2002 JosephJ DEADLOCK in Leftview::mfn_Lock Background (thread pool) thread aquires LeftView lock, then gets stuck in CTreeCtrl::SetItem, which does a SendMessage and blocks, I guess waiting for the main application windows message loop to process the message. USER32!SendMessageW+0x44 CTreeCtrl::SetItem+0x1f LeftView::mfn_InsertInterface+0x258 LeftView::HandleEngineEvent+0x344 Document::HandleEngineEvent+0x72 CNlbEngine::ControlClusterOnInterface+0x1 CNlbEngine::RefreshInterfaceNew+0x4fe CNlbEngine::mfn_ReallyUpdateInterface+0x4 CNlbEngine::UpdateInterfaceWorkItem+0x205 UpdateInterfaceWorkItemRoutine+0x11 Unfortunately, the main (winmsg) thread, cant process messages because it's stuck trying to acquire the LeftView lock! LeftView::mfn_Lock+0x22 LeftView::mfn_InsertCluster+0x11f LeftView::HandleEngineEvent+0x13c Document::HandleEngineEvent+0x72 CNlbEngine::UpdateCluster+0x22b (FPO: [No LeftView::OnRefresh+0x150 LeftView::OnKeyDown+0x37 RESOLUTION: Changed the implementation of CLeftView::mfn_Lock from EnterCriticalSection(&m_crit) to while (!TryEnterCriticalSection(&m_crit)) { ProcessMsgQueue(); Sleep(100); } I verified this by deliberatly producing the deadlock by introducing sleeps, then verifying that the deadlock doesn't happen if the above version of mfn_Lock is used. 01/24/2002 JosephJ How to hook the "CLOSE" message 1. To MainForm add: afx_msg void OnClose( ); 2. Implement OnClose ... void MainForm::OnClose( ) { int sel = MessageBox( L"OK to close?", L"OK to close?", MB_OKCANCEL | MB_ICONEXCLAMATION ); if ( sel == IDOK ) { CFrameWnd::OnClose( ); } } 3. Make sure it gets called ... BEGIN_MESSAGE_MAP( MainForm, CFrameWnd ) ... ON_WM_CLOSE() ... END_MESSAGE_MAP() 01/24/2002 JosephJ Dealing with app closing On wm close, put up msgbox/dlg: Title: Close NLB Manager Text: OK to close NLB Manager while the following operation(s) are in progress? OK CANCEL On OK -- call Document->PrepareToClose() .... VOID Document::PrepareToClose(void) { // // Cancel any pending operations in the engine, and prevent any // new operations to be launched. During this time, we want the // views and the log to be updated, so we don't PrepareToDeinitialize // for ourselves or the views yet... // { CWaitCursor wait; gEngine.PrepareToDeinitialize(); // After the Engine's PrepareToDeinitialize function is called, it // fails attempts to create new handles and new operations. gEngine.CancelAllPendingOperations(TRUE); // TRUE == block } // // At this time there should be no more pending activity. Block // any further updates to the views... // m_fPrepareToDeinitialize = TRUE; if (m_pLeftView != NULL) { m_pLeftView->PrepareToDeinitialize(); } if (m_pDetailsView != NULL) { m_pDetailsView->PrepareToDeinitialize(); } if (m_pLogView != NULL) { m_pLogView->PrepareToDeinitialize(); } } (See also entry right below ...) 01/24/2002 JosephJ Dealing with work items on app closing. The classic problem here is that the moment the work item signals that it's done, it can trigger the deinitialization of the app, before the work item completes. So to avoid this, I keep a CNlbEngine::m_WorkItemCount, which is maintained by InterlockedIncrement/Decrement. CNlbEngine::CancelAllPendingOperations will block until this count goes to zoro (as well as until the operations go to zero). The logic is quite subtle -- see this function for details. 01/24/2002 JosephJ Creating our own extended MessageBox: Uses: Report misconfigs -- OK Report pending operations on close -- OK CANCEL Report proposed cluster changes -- YES NO Format: text text 01/29/2002 JosephJ problems with updating UI in the background thread. Backing out of doing updates in a work items for now, because of misc AVs and deadlocks during the update process. 01/29/2002 JosephJ problems with re-entrancy of Application::ProcessMsgQueue ProcessMsgQueue was getting reentered and we got a deadlock in handling WM_CLOSE (which ends up calling Document::PrepareToClose, which calls CNlbEngine::CancelAllPendingOperations which deadlocks waiting for all operations to go to zero because the WM_CLOSE msg was handled in the context of waiting to retry to get an update completion status. Temporary fix: - Keep a reentrancy count in Application: m_lMsgProcReentrancyCount. - Application::ProcessMsgQueue will do an interlocked increment on this, and if the result is > 1, it simply does a decrement and returns. - Various UI-initiated functions don't do anything if called in the context of ProcessMsgQueue (i.e, called when m_lMsgProcReentrancyCount is > 0. The function Application::IsProcessMsgQueueExecuting checks for this case. 01/30/2002 JosephJ checked in the following 334243 perform extended operations in the background; allow a cancel 484025 When using alt-F4 to close nlbmgr.exe, the process stays in memory. 513056 add host failed because nlbmgr ended up pinging for "13:36:48". 502793 warn user when attempting to remove / change DIP 512303 Block user from merging 2 clusters that are in the nlbmgr context 512370 update sometims fails when IP is zero (0.0.0.0) 478932 ipconflict when changing modes of operation ( unicast to multicast ) 505153 NlbConfigurationUpdate::DoUpdate returns WBEM_E_ALREADY_EXISTS 509346 delete host adds a homenet IP when it should keep the DIP 509355 replace or remove home net ip functions like IP with the real home 02/08/2002 JosephJ suspicious imported functions FormatMessageW lstrcpyW WideCharToMultiByte lstrlenW GetFileAttributesW LockResource LoadResource FindResourceW GetCurrentProcess InitializeCriticalSection GetComputerNameExW MultiByteToWideChar GetStartupInfoW LoadLibraryW VirtualQuery _XcptFilter _wspawnlp _wgetenv _ftol _wfsopen RegCreateKeyExW TraceEvent RegQueryValueExW RegSetValueExW LoadStringW WinHelpW wsprintfW GetClientRect LoadIconW LoadMenuW CoCreateInstance CLSIDFromString CoSetProxyBlanket CredUIPromptForCredentialsW IcmpCreateFile IcmpSendEcho2 Code review tool: http://shell/Development/Danger/Dangerous%20APIs.htm http://massweb/security/Lists/Announcements/DispForm.htm?ID=8&Source=http%3a%2f%2fmassweb%2fsecurity%2f // 2/12/02 JosephJ SECURITY BUGBUG: 02/14/2002 JosephJ Processing UI updates in the foreground This is completing the fix to: 334243 perform extended operations in the background; allow a cancel Changes: Added a application-specific message: MYWM_DEFER_UI_MSG Added class CUIWorkItem in document.h -- this data class encapsulates the parameters for (a) log messages and (b) HandleEngineEvent notifications. Added Document::mfn_DeferUIOperation(CUIWorkItem *pWorkItem) -- it posts a MYWM_DEFER_UI_MSG message to the MainForm's window's queue (the pointer to the mainform object is saved in global g_pMainFormWnd -- a bit of a hack). Added Document::HandleDeferedUIWorkItem(pWorkItem) -- will actually call the Log or HandleEngineEvent encapsulated in a work item. Added virtual function MainForm::WindowProc that overrides the default window handling proc, and on getting a MYWM_DEFER_UI_MSG, will extract the work item (lParam) and call pDocument->HandleDeferedUIWorkItem. 04/09/2002 JosephJ Bugs at this point: P1's 489012 If ICMP is disabled by customer for security reasons, they cannot 538191 user's password saved in user-mode memory 540636 password is not updated locally 535616 memory leak running remove from view 535969 too much memory allocated for event logs 568198 remove from view should not remember cluster properties P2's: 540917 Duplicate shortcut character "P" are there. 565697 delete host when host is unreachalbe 563150 PREFIX:net: \nt\net\wlbs\nlbmgr\provider\updatecfg.cpp: 566671 NLBS:NET3:nlbmgr.exe:Typo:Cluster configuration 585280 NLBMgr fails to look up a cluster instance where nlb was bound 552641 autorefresh require valid input as int and is in a reasonable 528007 Error message pops up stating "invalid cluster IP" when viewing 532302 invalid user credentials:If machine is not on a domain anything is 535561 mgr says that ping failed but ping.exe succeeds Done: 1. Remove demo mode in retail build -- remove it from the help message too 2. 489012 If ICMP is disabled by customer for security reasons, they ... x Add NoPing option (remember to add it to cmdline help) x Add fNoPing and fNlbMgr flag to CfgUtilsInitialize 3. 538191 user's password saved in user-mode memory 7. 540917 Duplicate shortcut character "P" are there. 9. 566671 NLBS:NET3:nlbmgr.exe:Typo:Cluster configuration 3. 540636 password is not updated locally 7. Make nlbmgr.exe NOT load wlbsctrl.dll -- even if it's not there (so make CfgUtilInitialize take a "nlbmgr.exe" flag). 04/15/2002 JosephJ details of fix to: 538191 user's password saved in user-mode memory The password is stored internally in an encrypted form. It is decrypted only when (a) bringing up UI to modify the password -- this is done in PromptForEncryptedCreds; and (b) when connecting to a host -- this is done in nlbclient.lib!connect_to_server (in wlbs\nlbmgr\nlbclientlib\nlbclient.cpp). Encryption scheme: We call RtlEncrypt/DecryptMemory with "0" as flags. Additional, we "encode" the binary encrypted form into a printable version (which is also guaranteed to not have any 0 characters, so it can be stored and processed as any other string). This was done because the password is stored and processed in several places where it is assumed to be a string, or bstr etc. The encrypt/decrypt and encode/decode is done in cfgutillib!CfgUtilEncrypt/DecryptPassword (in wlbs\nlbmgr\cfgutillib\cfgutil.cpp). Test program nlbmgr\provider\tprov.cpp has function test_encrypt_memory() which does rudimentary functional tests on CfgUtilEncrypt/DecryptMemory. 04/15/2002 JosephJ details of fix for 540636 password is not updated locally Problem is that on setting a new remote control password (a cluster level change), the cluster's NLB properties hashed password value is not updated, so after each host is updated, it reports an config error because its hash value doesn't match the cluster's (obsolete) value. Fix is to: 1. Mark the cluster's hash value as being stale -- this is done by a new flag CClusterSpec::m_fNewRctPassword which is set to true IFF the user has just specifed a new passord, and the hosts are in the process of carrying out the update. It is set in CNlbEngine::UpdateCluster (look for m_fNewRctPassword). 2. When analyzing an interface for config errors, when checking the interface's config against the cluster config, the check of remote control hash is NOT made if the cluster's m_fNewRctPassword flag is set. This is done in CNlbEngine::mfn_AnalyzeInterfaceLk which calls analyze_nlbcfg with a parameter "ignore rct password" set appropriately. analyze_nlbcfg will ignore the rct password check if this flag is set. 3. Once an interface update is complete, AND if a new password has been set, AND the update completed successfully, AND the cluster's m_fNewRctPassword flag is TRUE, THEN we update the clusters's remote control hash value and clear the cluster's m_fNewRctPassword flag. This is all done in CNlbEngine::mfn_ReallyUpdateInterface, which actually performs the interface update and synchronously waits for the update to complete. 04/15/2002 JosephJ details of fix for 568198 remove from view should not remember cluster properties Problem is that we remember all configs on all hosts we have ever connected to. So even if we've unmanaged a cluster, we report a conflict if some config conflicts with this removed cluster. Fix is to (gulp) delete hosts from nlbmgr once the last managed cluster goes away. One side effect will be that the credentials used to connect to that host are lost -- we can keep a separate datastructure just for that if needed. New function CNlbEngine::mfn_DeleteHostIfNotManagedLk deletes the host and its interfaces if the interfaces are not part of any cluster (and have no ongoing operation on them, although the latter should never happen if the interface is not part of a cluster). Need to watch for the case of pruning hosts wile they are in the process of being added in the context of hostlist (we don't check -- could be a potential problem). CNlbEngine::DeleteCluster calls mfn_DeleteHostIfNotManagedLk for each of its interfaces. CNlbEngine::UpdateInterfaceWorkItem calls mfn_DeleteHostIfNotManagedLk if once done updating, the interface is now unbound. New function CNlbEngine::PurgeUnmanagedHosts: This function effectively calls mfn_DeleteHostIfNotManagedLk for all hosts. This function is called from leftview.cpp from all the places that bring up the ConnectDialog UI, after it is done with processing the UI. It is called here because the ConnectDialog UI can create several unmanaged hosts -- either if the user keeps connecting to different computers or because the user cancels from the dialog. We could have cleaned this up in the ConnectDialog itself, but it's a bit tricky. We also call this function at the end of LeftView::OnRefresh. This need to call this purge function from several places is not very clean. 04/17/2002 JosephJ Not overwriting the user-entered connection string with the FQDN obtained from the hosts via connect-to-existing This was causing a subsequent attempt to connect to the same host to fail because the user didn't specify the fqdn. Added a boolean paramater "fOverwriteConnectionInfo" to CNlbEngine::ConnectToHost. If this is true, it will overwrite any previously-existing connection info (connection string, username pwd) with those specified in pConnectionInfo. The UI (ConnectDialog::OnButtonConnect) calls ConnectToHost specifying TRUE (override), while CNlbEngine::LoadHost calls ConnectToHost specifying false (don't override). LoadHost is called in the context of Connect-to-existing, so with this fix, it doesn't overwrite the info intered via the UI. Also needed to added this same flag to CNlbEngine::mfn_RefreshHost. 04/17/2002 Preventing the cluster from being unmanaged if there are pending ops CNlbEngine::DeleteCluster now calls a newly written function CNlbEngine::mfn_ClusterOrInterfaceOperationsPendingLk, which reports whether there are any pending operations on a cluster or on its interfaces. If this is the case, the DeleteCluster (which is what "unmanages" the cluster) fails. 04/17/2002 Details of fix for 535969 too much memory allocated for event logs -- also limit growth of log file... Logfile: On file open, seek to the end (but don't check for file size) -- we don't check so that a message gets logged in the case of starting nlbmgr with a size already too large. In Document::logStatus , check (using ftell) if the position exceeds the max size (tentatively 10MB) -- if so it logs an event and closes the log file. In the context of Document::logStatus, I log a message saying that the file-size has exceeded. This gets logged even on starting up nlbmgr with a file that is too large -- so the user is notified of this. Pruning in-memory log: In log-string, if in-memory count of lines is exceeded by 100, it will delete the earliest 100 and then add a warning log msg about deletinon. This is done in LogView::LogString. 04/18/2002 JosephJ Details of fix to 565697 delete host when host is unreachalbe. In LeftView::OnHostRemove, if the host state is unreachable, bring up UI asking if we should simply stop managing this host. We also need to properly track the reachable/unreachable state of the host -- we now do this in mfn_Refresh 05/10/2002 JosephJ Details of fix to 603411 nlbmgr:ACCESSIBILITY:can not tab between window panes 1. Need to catch VK_TAB and VK_F6 in each of the views -- this was simple to do on the left and log views, but a pain on the details-view (and in fact we could not figure out out to capture TAB in the detail's view (which is a form view)). -- for left and log view: Add a OnKeyDown handler (including add a ON_WM_KEYDOWN() entry in the message map. Check the OnKeyDown functions for LeftView and LogView, under VK_TAB and VK_F6 -- check also how we check for the SHIFT key being pressed (because if it is we go anticlockwise around the views). -- for DetailsView: Add to message map: ON_NOTIFY(LVN_KEYDOWN, IDC_LIST_DETAILS, OnNotifyKeyDown) See DetailsView::OnNotifyKeyDown for more details... 2. Need to decide which view to shift focus to -- this is done in Document::SetFocusNextView and SetFocusPrevView. 3. For setting the focus on DetailsView, we can't simply call SetFocus on that view's CWnd -- instead we need to call SetFocus on the detail view's list control -- (we got very flaky behavior calling SetFocus in DetailsView's CWnd). Also we need to select an item in the list control if none is selected so the user can get some visual cue. All this is implemented in DetailsView::SetFocus. 4. Because we can't hook the TAB key in the details view, we just toggle between leftview and log view when the user presses TAB, but cycle between all three when F6 is pressed. 05/13/2002 ChriDar Need to check code for memory allocation failures. One example noted is in the Document class constructor. A CImageList is constructed and dereferenced, but the memory alloc wasn't verifed. 08/22/2002 JosephJ Fix for: CyndaR: So, if you connect to the to be NLB host with the DHCP assigned ip address of the nic to be bound to NLB, the wizard will show the DIP and subnet mask as blank. If you try to specify anything it produces the error that Frank referenced. The wizard allows you to continue even if the DIP is left blank and will result in only the cluster ip address being bound to the nic. The dip is now blank in NLB properties of network connections and NLBMgr can no longer connect to the host to get status update. NLBMgr will indicate that NLB is not bound to the NLB host and if you refresh it will tell you that the host is unreachable. Fix is to add the check in ConnectDialog::mfn_ValidateData() (connect.cpp): ... if (ehConnectionIF == *m_pehSelectedInterfaceId) { if (iSpec.m_NlbCfg.fDHCP) { put up a message box and return error. } }