/* Copyright (c) Citrix Systems, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; using System.ComponentModel; using XenAdmin.Controls; using XenAPI; using XenAdmin.Core; using XenAdmin.Properties; using System.Threading; using System.Drawing; using System.Drawing.Design; using System.Collections.ObjectModel; using XenAdmin.Network; namespace XenAdmin.Commands { /// /// This is the base ToolStripMenuItem for StartVMOnHostToolStripMenuItem, ResumeVMOnHostToolStripMenuItem and MigrateVMToolStripMenuItem. /// internal abstract class VMOperationToolStripMenuItem : CommandToolStripMenuItem { private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); private readonly vm_operations _operation; private ProduceConsumerQueue workerQueueWithouWlb; private readonly bool _resumeAfter; protected VMOperationToolStripMenuItem(Command command, bool inContextMenu, vm_operations operation) : base(command, inContextMenu) { if (operation != vm_operations.start_on && operation != vm_operations.resume_on && operation != vm_operations.pool_migrate) { throw new ArgumentException("Invalid operation", "operation"); } if (operation.Equals(vm_operations.resume_on)) _resumeAfter = true; _operation = operation; base.DropDownItems.Add(new ToolStripMenuItem()); } private bool _isDropDownClosed; protected override void OnDropDownClosed(EventArgs e) { base.OnDropDownClosed(e); workerQueueWithouWlb.CancelWorkers(false); _isDropDownClosed = true; } protected override void OnDropDownOpening(EventArgs e) { base.DropDownItems.Clear(); _isDropDownClosed = false; // Work around bug in tool kit where disabled menu items show their dropdown menus if (!Enabled) { ToolStripMenuItem emptyMenuItem = new ToolStripMenuItem(Messages.HOST_MENU_EMPTY); emptyMenuItem.Font = Program.DefaultFont; emptyMenuItem.Enabled = false; base.DropDownItems.Add(emptyMenuItem); return; } VisualMenuItemAlignData.ParentStrip = this; IXenConnection connection = Command.GetSelection()[0].Connection; bool wlb = Helpers.WlbEnabled(connection); if (wlb) { base.DropDownItems.Add(new VMOperationToolStripMenuSubItem(Messages.WLB_OPT_MENU_OPTIMAL_SERVER, Images.StaticImages._000_ServerWlb_h32bit_16)); } else { base.DropDownItems.Add(new VMOperationToolStripMenuSubItem(Messages.HOME_SERVER_MENU_ITEM, Images.StaticImages._000_ServerHome_h32bit_16)); } workerQueueWithouWlb = new ProduceConsumerQueue(25); List hosts = new List(connection.Cache.Hosts); hosts.Sort(); foreach (Host host in hosts) { VMOperationToolStripMenuSubItem item = new VMOperationToolStripMenuSubItem(String.Format(Messages.MAINWINDOW_CONTEXT_UPDATING, host.name_label.EscapeAmpersands()), Images.StaticImages._000_ServerDisconnected_h32bit_16); item.Tag = host; base.DropDownItems.Add(item); } // start a new thread to evaluate which hosts can be used. ThreadPool.QueueUserWorkItem(delegate { SelectedItemCollection selection = Command.GetSelection(); Session session = selection[0].Connection.DuplicateSession(); if (Helpers.WlbEnabled(selection[0].Connection)) { WlbRecommendations recommendations = new WlbRecommendations(selection.AsXenObjects(), session); recommendations.Initialize(); if (recommendations.IsError) EnableAppropriateHostsNoWlb(session); else EnableAppropriateHostsWlb(session, recommendations); } else { EnableAppropriateHostsNoWlb(session); } }); } private void EnableAppropriateHostsWlb(Session session, WlbRecommendations recommendations) { SelectedItemCollection selection = Command.GetSelection(); // set the first menu item to be the WLB optimal server menu item VMOperationToolStripMenuSubItem firstItem = (VMOperationToolStripMenuSubItem)base.DropDownItems[0]; var firstItemCmd = new VMOperationWlbOptimalServerCommand(Command.MainWindowCommandInterface, selection, _operation, recommendations); var firstItemCmdCanExecute = firstItemCmd.CanExecute(); Program.Invoke(Program.MainWindow, delegate { firstItem.Command = firstItemCmd; firstItem.Enabled = firstItemCmdCanExecute; }); List hostMenuItems = new List(); foreach (VMOperationToolStripMenuSubItem item in base.DropDownItems) { Host host = item.Tag as Host; if (host != null) { var cmd = new VMOperationWlbHostCommand(Command.MainWindowCommandInterface, selection, host, _operation, recommendations.GetStarRating(host)); var canExecute = cmd.CanExecute(); Program.Invoke(Program.MainWindow, delegate { item.Command = cmd; item.Enabled = canExecute; }); hostMenuItems.Add(item); } } // Shuffle the list to make it look cool // Helpers.ShuffleList(hostMenuItems); // sort the hostMenuItems by star rating hostMenuItems.Sort(new WlbHostStarCompare()); // refresh the drop-down-items from the menuItems. foreach (VMOperationToolStripMenuSubItem menuItem in hostMenuItems) { Program.Invoke(Program.MainWindow, delegate() { base.DropDownItems.Insert(hostMenuItems.IndexOf(menuItem) + 1, menuItem); }); } Program.Invoke(Program.MainWindow, () => AddAdditionalMenuItems(selection)); } private void EnableAppropriateHostsNoWlb(Session session) { SelectedItemCollection selection = Command.GetSelection(); IXenConnection connection = selection[0].Connection; VMOperationCommand cmdHome = new VMOperationHomeServerCommand(Command.MainWindowCommandInterface, selection, _operation, session); Host affinityHost = connection.Resolve(((VM)Command.GetSelection()[0].XenObject).affinity); Program.Invoke(Program.MainWindow, delegate { var firstItem = (VMOperationToolStripMenuSubItem)base.DropDownItems[0]; bool oldMigrateToHomeCmdCanRun = cmdHome.CanExecute(); if (affinityHost == null || _operation == vm_operations.start_on || oldMigrateToHomeCmdCanRun) { firstItem.Command = cmdHome; firstItem.Enabled = oldMigrateToHomeCmdCanRun; } else { VMOperationCommand cpmCmdHome = new CrossPoolMigrateToHomeCommand(Command.MainWindowCommandInterface, selection, affinityHost); if (cpmCmdHome.CanExecute()) { firstItem.Command = cpmCmdHome; firstItem.Enabled = true; } else { firstItem.Command = cmdHome; firstItem.Enabled = false; } } }); List dropDownItems = DropDownItems.Cast().ToList(); // Adds the migrate wizard button, do this before the enable checks on the other items Program.Invoke(Program.MainWindow, () => AddAdditionalMenuItems(selection)); foreach (VMOperationToolStripMenuSubItem item in dropDownItems) { if (_isDropDownClosed) { // Stop making requests to assert can start on each host after dropdown is closed break; } Host host = item.Tag as Host; if (host != null) { // API calls could happen in CanExecute(), which take time to wait. // So a Producer-Consumer-Queue with size 25 is used here to : // 1. Make API calls for different menu items happen in parallel; // 2. Limit the count of concurrent threads (now it's 25). workerQueueWithouWlb.EnqueueItem(() => { if (_isDropDownClosed) return; VMOperationCommand cmd = new VMOperationHostCommand(Command.MainWindowCommandInterface, selection, delegate { return host; }, host.Name().EscapeAmpersands(), _operation, session); CrossPoolMigrateCommand cpmCmd = new CrossPoolMigrateCommand(Command.MainWindowCommandInterface, selection, host, _resumeAfter); VMOperationToolStripMenuSubItem tempItem = item; bool oldMigrateCmdCanRun = cmd.CanExecute(); if ((_operation == vm_operations.start_on) || oldMigrateCmdCanRun) { Program.Invoke(Program.MainWindow, delegate { tempItem.Command = cmd; tempItem.Enabled = oldMigrateCmdCanRun; }); } else { bool crossPoolMigrateCmdCanRun = cpmCmd.CanExecute(); if (crossPoolMigrateCmdCanRun || !string.IsNullOrEmpty(cpmCmd.CantExecuteReason)) { Program.Invoke(Program.MainWindow, delegate { tempItem.Command = cpmCmd; tempItem.Enabled = crossPoolMigrateCmdCanRun; }); } else { Program.Invoke(Program.MainWindow, delegate { tempItem.Command = cmd; tempItem.Enabled = oldMigrateCmdCanRun; }); } } }); } } } /// /// Hook to add additional members to the menu item /// Note: Called on main window thread by executing code /// /// protected virtual void AddAdditionalMenuItems(SelectedItemCollection selection) { return; } /// /// This class is an implementation of the 'IComparer' interface /// for sorting vm placement menuItem List when wlb is enabled /// private class WlbHostStarCompare : IComparer { public int Compare(VMOperationToolStripMenuSubItem x, VMOperationToolStripMenuSubItem y) { int result = 0; // if x and y are enabled, compare their start rating if (x.Enabled && y.Enabled) result = y.StarRating.CompareTo(x.StarRating); // if x and y are disabled, they are equal else if (!x.Enabled && !y.Enabled) result = 0; // if x is disabled, y is greater else if (!x.Enabled) result = 1; // if y is disabled, x is greater else if (!y.Enabled) result = -1; return result; } } } }