/* 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 { /// /// 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 /// 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 GetCustomFields() { return customFieldsCache.GetCustomFields(); } public static List GetCustomFields(IXenConnection connection) { return customFieldsCache.GetCustomFields(connection); } /// The CustomFieldDefinition with the given name, or null if none is found. 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 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 customFields = customFieldsCache.GetCustomFields(connection); if (!customFields.Contains(customField)) { customFields.Add(customField); SaveCustomFields(session, connection, customFields); } } private static String GetCustomFieldDefinitionXML(List 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 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 CustomFieldValues(IXenObject o) { //Program.AssertOnEventThread(); List customFields = new List(); Dictionary 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 CustomFieldArrays(IXenObject o) { List ans = new List(); 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 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 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 GetOtherConfigCopy(IXenObject o) { Dictionary output = new Dictionary(); InvokeHelper.Invoke(delegate() { Dictionary otherConfig = Helpers.GetOtherConfig(o); if (otherConfig == null) { output = null; } else { output = new Dictionary(otherConfig); } }); return output; } #endregion } }