/* 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.Linq; using System.Collections.Generic; using XenAPI; using XenAdmin.Alerts; using System.ComponentModel; using System.Threading; using XenAdmin.Core; using XenAdmin.Actions.HostActions; namespace XenAdmin.Actions { /// /// This Action is for v6 (Midnight Ride) licensing. It sets the license server details and sets the edition to advanced, enterprise, enterprise-xd, platinum or free. /// public class ApplyLicenseEditionAction : AsyncAction { private readonly IEnumerable xos; private readonly Host.Edition _edition; private readonly string _licenseServerAddress; private readonly string _licenseServerPort; public List LicenseFailures { get; private set; } protected readonly Action, string> DoOnLicensingFailure; /// /// Initializes a new instance of the class. /// /// The hosts for which the license should be set. /// The edition the hosts should be set to. /// The license server address. /// The license server port. /// The method to call when the licensing fails. This method will show failure details in a dialog public ApplyLicenseEditionAction(IEnumerable xos, Host.Edition edition, string licenseServerAddress, string licenseServerPort, Action, string> DoOnLicensingFailure) : base(null, Messages.LICENSE_UPDATING_LICENSES) { LicenseFailures = new List(); Util.ThrowIfEnumerableParameterNullOrEmpty(xos, "xenobjects"); _edition = edition; this.xos = xos; _licenseServerAddress = licenseServerAddress; _licenseServerPort = licenseServerPort; this.DoOnLicensingFailure = DoOnLicensingFailure; } private static void SetLicenseServer(Host host, string licenseServerAddress, string licenseServerPort) { Dictionary d = new Dictionary(); if (!string.IsNullOrEmpty(licenseServerAddress) && !string.IsNullOrEmpty(licenseServerPort)) { d.Add("address", licenseServerAddress); d.Add("port", licenseServerPort); } XenAPI.Host.set_license_server(host.Connection.Session, host.opaque_ref, d); } protected override void Run() { // PR-1102: hosts that have been updated, plus the previous edition information - this data will be sent to the licensing server Dictionary updatedHosts = new Dictionary(); this.Description = Messages.LICENSE_UPDATING_LICENSES; foreach (IXenObject xo in xos) { Connection = xo.Connection; if (!Connection.IsConnected) { continue; } Host host = null; Pool pool = null; if(xo is Host) host = xo as Host; if(xo is Pool) { pool = xo as Pool; host = xo.Connection.Resolve(pool.master); } string previousLicenseServerAddress = null; string previousLicenseServerPort = null; CollectionChangeEventHandler alertsChangeHandler = null; string alertText = null; object lck = new object(); if (host != null && host.license_server.ContainsKey("address")) { previousLicenseServerAddress = host.license_server["address"]; } if (host != null && host.license_server.ContainsKey("port")) { previousLicenseServerPort = host.license_server["port"]; } try { if(pool != null) pool.Connection.Cache.Hosts.ToList().ForEach(h=>SetLicenseServer(h, _licenseServerAddress, _licenseServerPort)); else SetLicenseServer(host, _licenseServerAddress, _licenseServerPort); IXenObject xoClosure = xo; alertsChangeHandler = delegate(object sender, CollectionChangeEventArgs e) { if (e.Action == CollectionChangeAction.Add) { lock (lck) { Alert alert = (Alert)e.Element; if (host != null && host.uuid == alert.HostUuid) { if (alert.Title == PropertyManager.GetFriendlyName("Message.name-license_not_available")) { // the license server reported there were no licenses available. alertText = string.Format(PropertyManager.GetFriendlyName("Message.body-license_not_available"), xoClosure.Name); } else if (alert.Title == PropertyManager.GetFriendlyName("Message.name-license_server_unreachable")) { // couldn't check out license because couldn't contact license server alertText = string.Format(PropertyManager.GetFriendlyName("Message.body-license_server_unreachable"), xoClosure.Name); } else if (alert.Title == PropertyManager.GetFriendlyName("Message.name-grace_license")) { alertText = string.Empty; } } } } }; Alert.RegisterAlertCollectionChanged(alertsChangeHandler); // PR-1102: catch the host's license data, before applying the new one, so it can be sent later to the licensing server LicensingHelper.LicenseDataStruct previousLicenseData = new LicensingHelper.LicenseDataStruct(host); if(xo is Host && host != null) { if (Helpers.ClearwaterOrGreater(host)) Host.apply_edition(host.Connection.Session, host.opaque_ref, Host.GetEditionText(_edition), false); else Host.apply_edition(host.Connection.Session, host.opaque_ref, Host.GetEditionText(_edition)); // PR-1102: populate the list of updated hosts updatedHosts.Add(host, previousLicenseData); } if (xo is Pool) { if(!Helpers.ClearwaterOrGreater(xo.Connection)) { foreach (Host poolHost in xo.Connection.Cache.Hosts) { Host.apply_edition(host.Connection.Session, poolHost.opaque_ref, Host.GetEditionText(_edition)); } } else { Pool.apply_edition(xo.Connection.Session, pool.opaque_ref, Host.GetEditionText(_edition)); //TODO: Look into why this is required. Event.Next returning late? XAPI returning before updated? Thread.Sleep(200); } xo.Connection.Cache.Hosts.ToList().ForEach(h => updatedHosts.Add(h, previousLicenseData)); } Description = Messages.APPLYLICENSE_UPDATED; } catch (Failure e) { for (int i = 0; i < 50; i++) { Thread.Sleep(100); lock (lck) { if (alertText != null) break; } } LicenseFailures.Add(new LicenseFailure(host, alertText ?? e.Message)); if (pool != null) pool.Connection.Cache.Hosts.ToList().ForEach(h => SetLicenseServer(h, previousLicenseServerAddress, previousLicenseServerPort)); else SetLicenseServer(host, previousLicenseServerAddress, previousLicenseServerPort); } finally { Alert.DeregisterAlertCollectionChanged(alertsChangeHandler); } } // PR-1102: Send licensing data to the activation server if (updatedHosts.Count > 0) { LicensingHelper.SendLicenseEditionData(updatedHosts, Host.GetEditionText(_edition)); } if (LicenseFailures.Count > 0) { string exceptionText = LicenseFailures.Count == 1 ? string.Format(Messages.LICENSE_ERROR_1, LicenseFailures[0].Host.Name) : string.Format(Messages.LICENSE_ERROR_MANY, LicenseFailures.Count, new List(xos).Count); if (DoOnLicensingFailure != null) DoOnLicensingFailure(LicenseFailures, exceptionText); throw new InvalidOperationException(exceptionText); } } } public class LicenseFailure { public Host Host; public string AlertText; public LicenseFailure(Host host, string alertText) { Host = host; AlertText = alertText; } } }