/* 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.Windows.Forms; using XenAdmin.Controls; using XenAPI; using XenAdmin.Core; using XenAdmin.Actions; 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 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()); } protected override void OnDropDownClosed(EventArgs e) { base.OnDropDownClosed(e); Stop(); } protected override void OnDropDownOpening(EventArgs e) { base.DropDownItems.Clear(); // 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; var selection = Command.GetSelection(); IXenConnection connection = selection[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)); } 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); } // Adds the migrate wizard button, do this before the enable checks on the other items AddAdditionalMenuItems(selection); UpdateHostList(); } /// /// 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; } #region UpdateHostList private ProduceConsumerQueue workerQueueWithoutWlb; readonly object locker = new object(); private bool stopped; private bool Stopped { set { lock (locker) { stopped = value; } } get { lock (locker) { return stopped; } } } private void Stop() { Stopped = true; if (workerQueueWithoutWlb != null) workerQueueWithoutWlb.CancelWorkers(false); } private void UpdateHostList() { Stopped = false; var selection = Command.GetSelection(); var connection = selection[0].Connection; if (Helpers.WlbEnabled(connection)) { var vms = selection.AsXenObjects(); var retrieveVmRecommendationsAction = new WlbRetrieveVmRecommendationsAction(connection, vms); retrieveVmRecommendationsAction.Completed += delegate { if (Stopped || retrieveVmRecommendationsAction.Cancelled || !retrieveVmRecommendationsAction.Succeeded) return; var recommendations = new WlbRecommendations(vms, retrieveVmRecommendationsAction.Recommendations); Program.Invoke(Program.MainWindow, delegate { if (recommendations.IsError) EnableAppropriateHostsNoWlb(); else EnableAppropriateHostsWlb(recommendations); }); }; retrieveVmRecommendationsAction.RunAsync(); } else { EnableAppropriateHostsNoWlb(); } } private void EnableAppropriateHostsWlb(WlbRecommendations recommendations) { if (Stopped || DropDownItems.Count == 0) return; // set the first menu item to be the WLB optimal server menu item var firstItem = DropDownItems[0] as VMOperationToolStripMenuSubItem; if (firstItem == null) return; var selection = Command.GetSelection(); var firstItemCmd = new VMOperationWlbOptimalServerCommand(Command.MainWindowCommandInterface, selection, _operation, recommendations); firstItem.Command = firstItemCmd; firstItem.Enabled = firstItemCmd.CanExecute(); var hostMenuItems = new List(); foreach (var item in DropDownItems) { var hostMenuItem = item as VMOperationToolStripMenuSubItem; if (hostMenuItem == null) continue; var host = hostMenuItem.Tag as Host; if (host != null) { var cmd = new VMOperationWlbHostCommand(Command.MainWindowCommandInterface, selection, host, _operation, recommendations.GetStarRating(host)); hostMenuItem.Command = cmd; hostMenuItem.Enabled = cmd.CanExecute(); hostMenuItems.Add(hostMenuItem); } } // sort the hostMenuItems by star rating hostMenuItems.Sort(new WlbHostStarCompare()); // refresh the drop-down-items from the menuItems. foreach (VMOperationToolStripMenuSubItem menuItem in hostMenuItems) { DropDownItems.Insert(hostMenuItems.IndexOf(menuItem) + 1, menuItem); } } private void EnableAppropriateHostsNoWlb() { if (Stopped || DropDownItems.Count == 0) return; var firstItem = DropDownItems[0] as VMOperationToolStripMenuSubItem; if (firstItem == null) return; // 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). workerQueueWithoutWlb = new ProduceConsumerQueue(25); var selection = Command.GetSelection(); var connection = selection[0].Connection; var session = connection.DuplicateSession(); var affinityHost = connection.Resolve(((VM) selection[0].XenObject).affinity); EnqueueHostMenuItem(this, session, affinityHost, firstItem, true); var hostMenuItems = DropDownItems.OfType().ToList(); if (Stopped) return; foreach (VMOperationToolStripMenuSubItem item in hostMenuItems) { var host = item.Tag as Host; if (host != null) { var tempItem = item; EnqueueHostMenuItem(this, session, host, tempItem, false); } } } private void EnqueueHostMenuItem(VMOperationToolStripMenuItem menu, Session session, Host host, VMOperationToolStripMenuSubItem hostMenuItem, bool isHomeServer) { workerQueueWithoutWlb.EnqueueItem(() => { var selection = menu.Command.GetSelection(); var cmd = isHomeServer ? new VMOperationHomeServerCommand(menu.Command.MainWindowCommandInterface, selection, menu._operation, session) : new VMOperationHostCommand(menu.Command.MainWindowCommandInterface, selection, delegate { return host; }, host.Name().EscapeAmpersands(), menu._operation, session); var oldMigrateCmdCanRun = cmd.CanExecute(); if (Stopped) return; if (host == null || menu._operation == vm_operations.start_on || oldMigrateCmdCanRun) { Program.Invoke(Program.MainWindow, delegate { hostMenuItem.Command = cmd; hostMenuItem.Enabled = oldMigrateCmdCanRun; }); } else { var cpmCmd = isHomeServer ? new CrossPoolMigrateToHomeCommand(menu.Command.MainWindowCommandInterface, selection, host) : new CrossPoolMigrateCommand(menu.Command.MainWindowCommandInterface, selection, host, menu._resumeAfter); var crossPoolMigrateCmdCanRun = cpmCmd.CanExecute(); if (Stopped) return; Program.Invoke(Program.MainWindow, delegate { if (crossPoolMigrateCmdCanRun || !string.IsNullOrEmpty(cpmCmd.CantExecuteReason)) { hostMenuItem.Command = cpmCmd; hostMenuItem.Enabled = crossPoolMigrateCmdCanRun; } else { hostMenuItem.Command = cmd; hostMenuItem.Enabled = false; } }); } }); } #endregion /// /// 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; } } } }