xenadmin/XenModel/CustomFields/CustomFieldsManager.cs
Konstantina Chremmou bc40d47ba6 Corrections to removal of custom fields:
- Custom fields were not removed from all types of objects the UI allows
  setting them for, which meant they were left behind in the other_config.
- Only remove a custom field if it is set on an object, otherwise we hammer
  the server with unnecessary requests.

Signed-off-by: Konstantina Chremmou <Konstantina.Chremmou@cloud.com>
2023-02-28 21:03:16 +00:00

322 lines
12 KiB
C#

/* Copyright (c) Cloud Software Group, Inc.
*
* 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.Network;
using XenAPI;
using XenAdmin.Core;
using System.Xml;
namespace XenAdmin.CustomFields
{
/// <summary>
/// Provide custom fields management support for VMs. The coordinator list of custom fields will be
/// maintained in the pool class using the same conventions as the tags implementation (see
/// XenAdmin.XenSearch.Tags). When persisting the label-value pairs in the VMs, the
/// following key/value convention will be used:
/// "XenCenter.CustomFields.foo1" value
/// "XenCenter.CustomFields.foo2" value
/// </summary>
public static class CustomFieldsManager
{
#region These functions deal with caching the list of custom fields
private static readonly CustomFieldsCache customFieldsCache = new CustomFieldsCache();
private const String CUSTOM_FIELD_DELIM = ".";
public const String CUSTOM_FIELD_BASE_KEY = "XenCenter.CustomFields";
public const String CUSTOM_FIELD = "CustomField:";
public static event Action CustomFieldsChanged;
static CustomFieldsManager()
{
OtherConfigAndTagsWatcher.GuiConfigChanged += OtherConfigAndTagsWatcher_GuiConfigChanged;
}
private static void OtherConfigAndTagsWatcher_GuiConfigChanged()
{
InvokeHelper.AssertOnEventThread();
customFieldsCache.RecalculateCustomFields();
OnCustomFieldsChanged();
}
private static void OnCustomFieldsChanged()
{
Action handler = CustomFieldsChanged;
if (handler != null)
{
handler();
}
}
#endregion
#region These functions deal with custom field definitions on the pool object
public static List<CustomFieldDefinition> GetCustomFields()
{
return customFieldsCache.GetCustomFields();
}
public static List<CustomFieldDefinition> GetCustomFields(IXenConnection connection)
{
return customFieldsCache.GetCustomFields(connection);
}
/// <returns>The CustomFieldDefinition with the given name, or null if none is found.</returns>
public static CustomFieldDefinition GetCustomFieldDefinition(string name)
{
foreach (CustomFieldDefinition d in GetCustomFields())
{
if (d.Name == name)
return d;
}
return null;
}
public static void RemoveCustomField(Session session, IXenConnection connection, CustomFieldDefinition definition)
{
List<CustomFieldDefinition> customFields = customFieldsCache.GetCustomFields(connection);
if (customFields.Remove(definition))
{
SaveCustomFields(session, connection, customFields);
// theoretically any object type with other_config may have a custom field set,
// but the object types more likely to have one are those shown on the treeview,
// i.e. pools, hosts, VMs (including snapshots and templates), SRs, VDIs, and networks
string customFieldKey = GetCustomFieldKey(definition);
foreach (var pool in connection.Cache.Pools)
{
if (pool.other_config.ContainsKey(customFieldKey))
Pool.remove_from_other_config(session, pool.opaque_ref, customFieldKey);
}
foreach (var host in connection.Cache.Hosts)
{
if (host.other_config.ContainsKey(customFieldKey))
Host.remove_from_other_config(session, host.opaque_ref, customFieldKey);
}
foreach (var vm in connection.Cache.VMs)
{
if (vm.other_config.ContainsKey(customFieldKey))
VM.remove_from_other_config(session, vm.opaque_ref, customFieldKey);
}
foreach (var sr in connection.Cache.SRs)
{
if (sr.other_config.ContainsKey(customFieldKey))
SR.remove_from_other_config(session, sr.opaque_ref, customFieldKey);
}
foreach (var vdi in connection.Cache.VDIs)
{
if (vdi.other_config.ContainsKey(customFieldKey))
VDI.remove_from_other_config(session, vdi.opaque_ref, customFieldKey);
}
foreach (var network in connection.Cache.Networks)
{
if (network.other_config.ContainsKey(customFieldKey))
XenAPI.Network.remove_from_other_config(session, network.opaque_ref, customFieldKey);
}
}
}
public static void AddCustomField(Session session, IXenConnection connection, CustomFieldDefinition customField)
{
List<CustomFieldDefinition> customFields = customFieldsCache.GetCustomFields(connection);
if (!customFields.Contains(customField))
{
customFields.Add(customField);
SaveCustomFields(session, connection, customFields);
}
}
private static String GetCustomFieldDefinitionXML(List<CustomFieldDefinition> customFieldDefinitions)
{
XmlDocument doc = new XmlDocument();
XmlNode parentNode = doc.CreateElement("CustomFieldDefinitions");
doc.AppendChild(parentNode);
foreach (CustomFieldDefinition customFieldDefinition in customFieldDefinitions)
{
parentNode.AppendChild(customFieldDefinition.ToXmlNode(doc));
}
return doc.OuterXml;
}
#endregion
#region These functions deal with the custom fields themselves
public static string GetCustomFieldKey(CustomFieldDefinition customFieldDefinition)
{
return CUSTOM_FIELD_BASE_KEY + CUSTOM_FIELD_DELIM + customFieldDefinition.Name;
}
private static void SaveCustomFields(Session session, IXenConnection connection, List<CustomFieldDefinition> customFields)
{
Pool pool = Helpers.GetPoolOfOne(connection);
if (pool != null)
{
string customFieldXML = GetCustomFieldDefinitionXML(customFields);
Pool.remove_from_gui_config(session, pool.opaque_ref, CUSTOM_FIELD_BASE_KEY);
Pool.add_to_gui_config(session, pool.opaque_ref, CUSTOM_FIELD_BASE_KEY, customFieldXML);
}
}
public static List<CustomField> CustomFieldValues(IXenObject o)
{
//Program.AssertOnEventThread();
List<CustomField> customFields = new List<CustomField>();
Dictionary<String, String> otherConfig = GetOtherConfigCopy(o);
if (otherConfig != null)
{
foreach (CustomFieldDefinition customFieldDefinition in customFieldsCache.GetCustomFields(o.Connection))
{
string customFieldKey = GetCustomFieldKey(customFieldDefinition);
if (!otherConfig.ContainsKey(customFieldKey) || otherConfig[customFieldKey] == String.Empty)
{
continue;
}
object value = ParseValue(customFieldDefinition.Type, otherConfig[customFieldKey]);
if (value != null)
{
customFields.Add(new CustomField(customFieldDefinition, value));
}
}
}
return customFields;
}
// The same as CustomFieldValues(), but with each custom field unwound into an array
public static List<object[]> CustomFieldArrays(IXenObject o)
{
List<object[]> ans = new List<object[]>();
foreach (CustomField cf in CustomFieldValues(o))
{
ans.Add(cf.ToArray());
}
return ans;
}
// Whether the object has any custom fields defined
public static bool HasCustomFields(IXenObject o)
{
Dictionary<String, String> otherConfig = GetOtherConfigCopy(o);
if (otherConfig != null)
{
foreach (CustomFieldDefinition customFieldDefinition in GetCustomFields(o.Connection))
{
string customFieldKey = GetCustomFieldKey(customFieldDefinition);
if (otherConfig.ContainsKey(customFieldKey) && otherConfig[customFieldKey] != String.Empty)
{
return true;
}
}
}
return false;
}
public static Object GetCustomFieldValue(IXenObject o, CustomFieldDefinition customFieldDefinition)
{
Dictionary<String, String> otherConfig = GetOtherConfigCopy(o);
if (otherConfig == null)
return null;
String key = GetCustomFieldKey(customFieldDefinition);
if (!otherConfig.ContainsKey(key))
return null;
String value = otherConfig[key];
if (value == String.Empty)
return null;
return ParseValue(customFieldDefinition.Type, value);
}
private static object ParseValue(CustomFieldDefinition.Types type, string value)
{
switch (type)
{
case CustomFieldDefinition.Types.Date:
DateTime datetime;
if (DateTime.TryParse(value, out datetime))
return datetime;
return null;
case CustomFieldDefinition.Types.String:
return value;
default:
return null;
}
}
private static Dictionary<string, string> GetOtherConfigCopy(IXenObject o)
{
Dictionary<string, string> output = new Dictionary<string, string>();
InvokeHelper.Invoke(delegate()
{
Dictionary<String, String> otherConfig = Helpers.GetOtherConfig(o);
if (otherConfig == null)
{
output = null;
}
else
{
output = new Dictionary<string, string>(otherConfig);
}
});
return output;
}
#endregion
}
}