mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-12-23 08:56:02 +01:00
21c2296410
* CP-19605: Improve the performance of the CreateNewRootNode function Make GroupKey a class and override GetHashCode and Equals Signed-off-by: Mihaela Stoica <mihaela.stoica@citrix.com> * CP-19605: Remove debugging lines Signed-off-by: Mihaela Stoica <mihaela.stoica@citrix.com>
571 lines
19 KiB
C#
571 lines
19 KiB
C#
/* 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 XenAdmin.Model;
|
|
using XenAdmin.Network;
|
|
using System.Collections;
|
|
using XenAPI;
|
|
using XenAdmin.Core;
|
|
|
|
// I think this is more complicated then it needs to be. Rather than have three different types of nodes,
|
|
// depending on the grouping of the next level, we should just have one type of node and do something like
|
|
// foreach (IXenObject o in XenSearchableObjects)
|
|
// foreach (Grouping g in search.Grouping)
|
|
// add node for the group;
|
|
// add node for the object;
|
|
// SRET 2009-04-28
|
|
|
|
namespace XenAdmin.XenSearch
|
|
{
|
|
public interface IAcceptGroups
|
|
{
|
|
IAcceptGroups Add(Grouping grouping, Object group, int indent);
|
|
|
|
void FinishedInThisGroup(bool defaultExpand);
|
|
}
|
|
|
|
public abstract class Group
|
|
{
|
|
public static Group GetGrouped(Search search)
|
|
{
|
|
Group group = GetGroupFor(null, search, search.EffectiveGrouping);
|
|
|
|
GetGrouped(search, group);
|
|
|
|
return group;
|
|
}
|
|
|
|
protected static Group GetGroupFor(Grouping grouping, Search search, Grouping subgrouping)
|
|
{
|
|
if (grouping is FolderGrouping)
|
|
return new FolderGroup(search, grouping);
|
|
else if (subgrouping == null)
|
|
return new LeafGroup(search);
|
|
else
|
|
return new NodeGroup(search, subgrouping);
|
|
}
|
|
|
|
private static void GetGrouped(Search search, Group group)
|
|
{
|
|
search.Items = 0;
|
|
|
|
foreach (IXenConnection connection in ConnectionsManager.XenConnectionsCopy)
|
|
{
|
|
if (connection.IsConnected && Helpers.GetPoolOfOne(connection) != null)
|
|
{
|
|
foreach (IXenObject o in connection.Cache.XenSearchableObjects)
|
|
if (!Hide(o))
|
|
group.FilterAdd(search.Query,o );
|
|
}
|
|
else
|
|
{
|
|
// Fake out disconnected connections with a host.
|
|
// We do this because IXenConnection is not an IXenObject, and so
|
|
// is not XenSearchable and cannot be put in the treeview.
|
|
Host disconnectedHost = new Host();
|
|
disconnectedHost.opaque_ref = connection.HostnameWithPort;
|
|
disconnectedHost.name_label = Helpers.GetName(connection);
|
|
disconnectedHost.Connection = connection;
|
|
|
|
group.FilterAdd(search.Query, disconnectedHost);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool Hide(IXenObject o)
|
|
{
|
|
if (XenAdminConfigManager.Provider.ObjectIsHidden(o.opaque_ref))
|
|
return true;
|
|
|
|
if (o is VM)
|
|
{
|
|
VM vm = o as VM;
|
|
if (vm.is_control_domain
|
|
|| !vm.Show(XenAdminConfigManager.Provider.ShowHiddenVMs))
|
|
return true;
|
|
|
|
// Hide VMs on non-live hosts
|
|
Host host = vm.Home();
|
|
if (host != null && !host.IsLive)
|
|
return true;
|
|
}
|
|
else if (o is SR)
|
|
{
|
|
SR sr = o as SR;
|
|
if (!sr.Show(XenAdminConfigManager.Provider.ShowHiddenVMs) || sr.IsToolsSR)
|
|
return true;
|
|
|
|
// Hide SRs on non-live hosts
|
|
Host host = sr.Home;
|
|
if (host != null && !host.IsLive)
|
|
return true;
|
|
}
|
|
else if (o is XenAPI.Network)
|
|
{
|
|
XenAPI.Network network = o as XenAPI.Network;
|
|
|
|
return !network.Show(XenAdminConfigManager.Provider.ShowHiddenVMs);
|
|
}
|
|
else if (o is Folder)
|
|
{
|
|
// Hide the root folder
|
|
Folder folder = o as Folder;
|
|
return folder.IsRootFolder;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
protected readonly Search search;
|
|
|
|
protected Group(Search search)
|
|
{
|
|
this.search = search;
|
|
}
|
|
|
|
protected int Compare(Object _1, Object _2)
|
|
{
|
|
return Compare(_1, _2, search);
|
|
}
|
|
|
|
protected int CompareGroupKeys(GroupKey _1, GroupKey _2)
|
|
{
|
|
return Compare(_1.key, _2.key);
|
|
}
|
|
|
|
public static int Compare(Object _1, Object _2, Search search)
|
|
{
|
|
// 1) Non-IXMOs always come before IXMOs. This is because if they're at the
|
|
// same level, they represent grouped items vs ungrouped items.
|
|
|
|
IXenObject i1 = _1 as IXenObject;
|
|
IXenObject i2 = _2 as IXenObject;
|
|
|
|
if (i1 != null && i2 == null)
|
|
return 1;
|
|
if (i1 == null && i2 != null)
|
|
return -1;
|
|
|
|
// 2) Try and separate them using the requested sorting, if any
|
|
|
|
if (search != null && search.Sorting != null)
|
|
{
|
|
foreach (Sort sort in search.Sorting)
|
|
{
|
|
int r = sort.Compare(_1, _2);
|
|
if (r != 0)
|
|
return r;
|
|
}
|
|
}
|
|
|
|
// 3) If that failed, sort by type (for IXMOs);
|
|
// if they're of the same type, the built-in sort for the type (not always alphabetical: see CA-27829);
|
|
// if those still don't separate them, object name.
|
|
if (i1 != null && i2 != null)
|
|
{
|
|
int r = CompareByType(i1, i2);
|
|
if (r != 0)
|
|
return r;
|
|
}
|
|
|
|
if (_1.GetType() == _2.GetType() && _1 is IComparable)
|
|
{
|
|
int r = Comparer.Default.Compare(_1, _2);
|
|
if (r != 0)
|
|
return r;
|
|
}
|
|
|
|
string s1 = (i1 == null ? _1.ToString() : Helpers.GetName(i1));
|
|
string s2 = (i2 == null ? _2.ToString() : Helpers.GetName(i2));
|
|
return StringUtility.NaturalCompare(s1, s2);
|
|
}
|
|
|
|
// Compare two IXMOs by type. I wanted to use the proper user-facing type from
|
|
// PropertyAccessors.properties[PropertyNames.type] but it turned out to be much
|
|
// too slow. Instead we use the type of the object with a few tweaks to sort
|
|
// important objects first.
|
|
private static int CompareByType(IXenObject _1, IXenObject _2)
|
|
{
|
|
string t1 = TypeOf(_1);
|
|
string t2 = TypeOf(_2);
|
|
return t1.CompareTo(t2);
|
|
}
|
|
|
|
private static string TypeOf(IXenObject o)
|
|
{
|
|
if (o is Folder)
|
|
return "10";
|
|
if (o is Pool)
|
|
return "20";
|
|
if (o is Host)
|
|
return "30";
|
|
VM vm = o as VM;
|
|
if (vm != null && vm.is_a_real_vm)
|
|
return "40";
|
|
return o.GetType().ToString();
|
|
}
|
|
|
|
private void FilterAdd(Query query, IXenObject o)
|
|
{
|
|
if (query == null || !query.Match(o))
|
|
return;
|
|
|
|
Add(o);
|
|
}
|
|
|
|
public abstract void Add(IXenObject o);
|
|
|
|
public virtual bool Populate(IAcceptGroups adapter)
|
|
{
|
|
return Populate(adapter, 0, true);
|
|
}
|
|
|
|
public abstract bool Populate(IAcceptGroups adapter, int indent, bool defaultExpand);
|
|
|
|
public abstract void PopulateFor(IAcceptGroups adapter, GroupKey group, int indent, bool defaultExpand);
|
|
|
|
public abstract void GetNextLevel(List<GroupKey> nextLevel);
|
|
}
|
|
|
|
public class GroupKey : IEquatable<GroupKey>
|
|
{
|
|
public Grouping grouping;
|
|
public object key;
|
|
|
|
public GroupKey(Grouping grouping, object key)
|
|
{
|
|
this.grouping = grouping;
|
|
this.key = key;
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return key.GetHashCode();
|
|
}
|
|
|
|
public bool Equals(GroupKey other)
|
|
{
|
|
return other != null && grouping.Equals(other.grouping) && key.Equals(other.key);
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
GroupKey other = obj as GroupKey;
|
|
return other != null && Equals(other);
|
|
}
|
|
}
|
|
|
|
public abstract class AbstractNodeGroup : Group
|
|
{
|
|
protected readonly Dictionary<GroupKey, Group> grouped;
|
|
protected Group ungrouped = null; //this is late bound
|
|
protected readonly Grouping grouping;
|
|
|
|
protected AbstractNodeGroup(Search search, Grouping grouping)
|
|
: base(search)
|
|
{
|
|
this.grouped = new Dictionary<GroupKey, Group>();
|
|
this.grouping = grouping;
|
|
}
|
|
|
|
public override bool Populate(IAcceptGroups adapter)
|
|
{
|
|
return Populate(adapter, 0, false);
|
|
}
|
|
|
|
public override bool Populate(IAcceptGroups adapter, int indent, bool defaultExpand)
|
|
{
|
|
bool added = false;
|
|
|
|
List<GroupKey> groups = new List<GroupKey>();
|
|
|
|
GetNextLevel(groups);
|
|
|
|
groups.Sort(CompareGroupKeys);
|
|
|
|
foreach (GroupKey group in groups)
|
|
{
|
|
IAcceptGroups subAdapter = adapter.Add(group.grouping, group.key, indent);
|
|
|
|
if (subAdapter == null)
|
|
continue;
|
|
|
|
added = true;
|
|
|
|
PopulateFor(subAdapter, group, indent + 1, defaultExpand);
|
|
}
|
|
|
|
adapter.FinishedInThisGroup(defaultExpand);
|
|
|
|
return added;
|
|
}
|
|
|
|
public override void PopulateFor(IAcceptGroups adapter, GroupKey group, int indent, bool defaultExpand)
|
|
{
|
|
if (grouped.ContainsKey(group))
|
|
{
|
|
grouped[group].Populate(adapter, indent, defaultExpand);
|
|
}
|
|
else if (ungrouped != null)
|
|
{
|
|
ungrouped.PopulateFor(adapter, group, indent, defaultExpand);
|
|
}
|
|
}
|
|
|
|
public override void GetNextLevel(List<GroupKey> nextLevel)
|
|
{
|
|
nextLevel.AddRange(grouped.Keys);
|
|
|
|
if(ungrouped != null)
|
|
ungrouped.GetNextLevel(nextLevel);
|
|
}
|
|
|
|
public Group FindOrAddSubgroup(Grouping grouping, object o, Grouping subgrouping)
|
|
{
|
|
GroupKey key = new GroupKey(grouping, o);
|
|
if (!grouped.ContainsKey(key))
|
|
grouped[key] = GetGroupFor(grouping, search, subgrouping);
|
|
return grouped[key];
|
|
}
|
|
}
|
|
|
|
public class NodeGroup : AbstractNodeGroup
|
|
{
|
|
public NodeGroup(Search search, Grouping grouping)
|
|
: base(search, grouping)
|
|
{
|
|
}
|
|
|
|
public override void Add(IXenObject o)
|
|
{
|
|
if (grouping.BelongsAsGroupNotMember(o))
|
|
{
|
|
GroupKey key = new GroupKey(grouping, o);
|
|
if (!grouped.ContainsKey(key))
|
|
grouped[key] = GetGroupFor(grouping, search, grouping.subgrouping);
|
|
|
|
return;
|
|
}
|
|
|
|
Object group = grouping.GetGroup(o);
|
|
|
|
if (group == null)
|
|
{
|
|
AddUngrouped(o);
|
|
return;
|
|
}
|
|
|
|
IList groups = group as IList;
|
|
|
|
if (groups == null)
|
|
{
|
|
AddGrouped(o, group);
|
|
return;
|
|
}
|
|
|
|
if (groups.Count == 0)
|
|
{
|
|
AddUngrouped(o);
|
|
return;
|
|
}
|
|
|
|
foreach (Object g in groups)
|
|
{
|
|
if (g == null)
|
|
{
|
|
AddUngrouped(o);
|
|
continue;
|
|
}
|
|
|
|
AddGrouped(o, g);
|
|
}
|
|
}
|
|
|
|
private void AddGrouped(IXenObject o, Object group)
|
|
{
|
|
// We sometimes need to apply the query to the group. For example, suppose VM 1
|
|
// is connected to Network 1 and Network 2. Then "VMs grouped by Network" will
|
|
// show
|
|
//
|
|
// -- Network 1
|
|
// -- VM 1
|
|
// -- Network 2
|
|
// -- VM 1
|
|
//
|
|
// So far so good. Now consider "VMs connected to Network 1 grouped by Network".
|
|
// Without the following piece of code, that would show exactly the same thing:
|
|
// because the second VM 1 is connected to Network 1, and is correctly grouped
|
|
// in Network 2. But what the user presumably wanted to see was just Network 1
|
|
// with its VMs under it: and that is achieved by applying the filter to the
|
|
// networks.
|
|
//
|
|
// The consequence when adding new searches is that a search that returns a
|
|
// XenObject of type T must return (T)o rather than null when o is a T,
|
|
// otherwise if you both group and filter by that type, the group will fail
|
|
// to match the filter and you'll get no results.
|
|
//
|
|
IXenObject groupModelObject = group as IXenObject;
|
|
|
|
if (groupModelObject != null && XenAdminConfigManager.Provider.ObjectIsHidden(groupModelObject.opaque_ref))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (search.Query != null && groupModelObject != null)
|
|
{
|
|
QueryFilter subquery = grouping.GetRelevantGroupQuery(search);
|
|
if (subquery != null && subquery.Match(groupModelObject) == false)
|
|
return;
|
|
}
|
|
|
|
Group nextGroup;
|
|
|
|
// Some types of grouping can add several levels to the hierarchy.
|
|
// This should not be confused with the IList in Add(IXenObject o):
|
|
// that adds the item to several groups, whereas this adds it to a
|
|
// single group several levels deep. In order to reach here,
|
|
// grouping.GetGroup(o) must return a list of arrays.
|
|
//
|
|
// NB We don't do the GetRelevantGroupQuery() check as above for the
|
|
// groups added in this way because we never need it: but if we ever
|
|
// add it, we should probably do a first pass to check all the groups
|
|
// first before adding any.
|
|
Array groups = group as Array;
|
|
if (groups != null)
|
|
{
|
|
nextGroup = this;
|
|
for (int i = 0; i < groups.Length; ++i)
|
|
{
|
|
Grouping gr = (i == groups.Length - 1 ? grouping.subgrouping : grouping);
|
|
nextGroup = (nextGroup as AbstractNodeGroup).FindOrAddSubgroup(grouping, groups.GetValue(i), gr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nextGroup = FindOrAddSubgroup(grouping, group, grouping.subgrouping);
|
|
}
|
|
|
|
nextGroup.Add(o);
|
|
}
|
|
|
|
|
|
protected void AddUngrouped(IXenObject o)
|
|
{
|
|
if (!XenAdminConfigManager.Provider.ObjectIsHidden(o.opaque_ref))
|
|
{
|
|
if (ungrouped == null)
|
|
ungrouped = GetGroupFor(grouping, search, grouping.subgrouping);
|
|
|
|
|
|
ungrouped.Add(o);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class FolderGroup : AbstractNodeGroup
|
|
{
|
|
public FolderGroup(Search search, Grouping grouping)
|
|
: base(search, grouping)
|
|
{
|
|
}
|
|
|
|
public override void Add(IXenObject o)
|
|
{
|
|
if (o is Folder)
|
|
{
|
|
GroupKey key = new GroupKey(grouping, o);
|
|
if (!grouped.ContainsKey(key))
|
|
grouped[key] = new FolderGroup(search, grouping);
|
|
}
|
|
else
|
|
{
|
|
if (ungrouped == null)
|
|
ungrouped = new LeafGroup(search);
|
|
ungrouped.Add(o);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class LeafGroup : Group
|
|
{
|
|
internal readonly List<IXenObject> items;
|
|
|
|
public LeafGroup(Search search)
|
|
: base(search)
|
|
{
|
|
this.items = new List<IXenObject>();
|
|
}
|
|
|
|
public override void Add(IXenObject o)
|
|
{
|
|
if (o is Folder && items.Contains(o)) // one folder can appear on several connections
|
|
return;
|
|
search.Items++;
|
|
items.Add(o);
|
|
}
|
|
|
|
public override bool Populate(IAcceptGroups adapter, int indent, bool defaultExpand)
|
|
{
|
|
bool added = false;
|
|
|
|
items.Sort(Compare);
|
|
|
|
foreach (IXenObject o in items)
|
|
{
|
|
IAcceptGroups subAdapter = adapter.Add(null, o, indent);
|
|
|
|
if (subAdapter != null)
|
|
{
|
|
added = true;
|
|
subAdapter.FinishedInThisGroup(defaultExpand);
|
|
}
|
|
}
|
|
|
|
adapter.FinishedInThisGroup(defaultExpand);
|
|
|
|
return added;
|
|
}
|
|
|
|
public override void GetNextLevel(List<GroupKey> nextLevel)
|
|
{
|
|
foreach(IXenObject item in items)
|
|
nextLevel.Add(new GroupKey(null, item));
|
|
}
|
|
|
|
public override void PopulateFor(IAcceptGroups adapter, GroupKey group, int indent, bool defaultExpand)
|
|
{
|
|
adapter.FinishedInThisGroup(defaultExpand);
|
|
}
|
|
}
|
|
}
|