/* 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.Text; using XenAPI; using XenAdmin.Core; using XenAdmin.Model; using XenAdmin.Network; namespace XenAdmin.Actions { public class CreateBondAction : AsyncAction { private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); private class NewBond { internal NewBond(Bond bond, PIF master, List slaves) { this.bond = bond; this.master = master; this.slaves = slaves; } internal Bond bond; internal PIF master; internal List slaves; } private readonly string name_label; private readonly bool autoplug; private readonly long mtu; private readonly bond_mode bondMode; private readonly Dictionary> PIFs = new Dictionary>(); private readonly bool bostonOrGreater; // in Boston, most network configuration is done automatically by xapi (PR-1006) private readonly bool tampaOrGreater; private readonly Host Master; private readonly Bond.hashing_algoritm hashingAlgoritm; /// The name for the new network. /// The PIFs on the master representing the physical NICs that are to be bonded together. /// Whether the new network is marked AutoPlug. /// The MTU for the Bond, ignored for pre cowley /// The bond mode, ignored for pre-Boston public CreateBondAction(IXenConnection connection, string name_label, List PIFs_on_master, bool autoplug, long mtu, bond_mode bondMode, Bond.hashing_algoritm hashingAlgoritm) : base(connection, string.Format(Messages.ACTION_CREATE_BOND_TITLE, name_label), string.Format(Messages.ACTION_CREATE_BOND_DESCRIPTION, name_label)) { this.name_label = name_label; this.autoplug = autoplug; this.mtu = mtu; this.bondMode = bondMode; this.hashingAlgoritm = hashingAlgoritm; Pool = Helpers.GetPoolOfOne(Connection); if (Pool == null) throw new Failure(Failure.INTERNAL_ERROR, "Pool has gone away"); Master = Connection.Resolve(Pool.master); if (Master == null) throw new Failure(Failure.INTERNAL_ERROR, "Pool master has gone away"); bostonOrGreater = Helpers.BostonOrGreater(connection); tampaOrGreater = Helpers.TampaOrGreater(connection); foreach (Host host in Connection.Cache.Hosts) AppliesTo.Add(host.opaque_ref); #region RBAC Dependencies ApiMethodsToRoleCheck.Add("host.management_reconfigure"); ApiMethodsToRoleCheck.Add("network.create"); ApiMethodsToRoleCheck.Add("network.destroy"); ApiMethodsToRoleCheck.Add("network.remove_from_other_config"); ApiMethodsToRoleCheck.Add("pif.reconfigure_ip"); ApiMethodsToRoleCheck.Add("pif.plug"); ApiMethodsToRoleCheck.Add("bond.create"); ApiMethodsToRoleCheck.Add("bond.destroy"); ApiMethodsToRoleCheck.AddRange(XenAPI.Role.CommonSessionApiList); ApiMethodsToRoleCheck.AddRange(XenAPI.Role.CommonTaskApiList); #endregion PIFs = NetworkingHelper.PIFsOnAllHosts(PIFs_on_master); // these locks will be cleared in clean() foreach (List pifs in PIFs.Values) { foreach (PIF pif in pifs) { pif.Locked = true; } } } private List new_bonds; private XenAPI.Network network; protected override void Run() { // the network lock and the connection.expectDisruption will be cleared in clean() new_bonds = new List(); network = null; Connection.ExpectDisruption = true; int inc = 100 / (Connection.Cache.HostCount * 3 + 1); string network_ref = CreateNetwork(0, inc); try { network = Connection.WaitForCache(new XenRef(network_ref)); network.Locked = true; XenAPI.Network.remove_from_other_config(Session, network_ref, XenAPI.Network.CREATE_IN_PROGRESS); int lo = inc; foreach (Host host in GetHostsMasterLast()) { List pifs = PIFs[host].FindAll(x => x.physical).ToList(); List> pif_refs = new List>(); foreach (PIF pif in pifs) { pif_refs.Add(new XenRef(pif.opaque_ref)); } log.DebugFormat("Creating bond on {0} with {1} PIFs...", Helpers.GetName(host), pifs.Count); Dictionary bondProperties = new Dictionary(); if (bondMode == bond_mode.lacp) bondProperties.Add("hashing_algorithm", Bond.HashingAlgoritmToString(hashingAlgoritm)); RelatedTask = tampaOrGreater ? Bond.async_create(Session, network_ref, pif_refs, "", bondMode, bondProperties) : bostonOrGreater ? Bond.async_create(Session, network_ref, pif_refs, "", bondMode) : Bond.async_create(Session, network_ref, pif_refs, ""); PollToCompletion(lo, lo + inc); lo += inc; log.DebugFormat("Creating bond on {0} done: bond is {1}.", Helpers.GetName(host), Result); Bond new_bond = Connection.WaitForCache(new XenRef(Result)); if (new_bond == null) throw new Failure(Failure.INTERNAL_ERROR, "Bond didn't appear in our cache!"); PIF new_master = Connection.Resolve(new_bond.master); if (new_master == null) throw new Failure(Failure.INTERNAL_ERROR, "Bond master didn't appear in our cache!"); new_bonds.Add(new NewBond(new_bond, new_master, pifs)); new_bond.Locked = true; new_master.Locked = true; } foreach (NewBond new_bond in new_bonds) { lo += inc; ReconfigureManagementInterfaces(new_bond.slaves, new_bond.master, lo); } foreach (NewBond new_bond in new_bonds) { lo += inc; if (!bostonOrGreater) NetworkingActionHelpers.Plug(this, new_bond.master, lo); } } catch (Exception) { foreach (NewBond new_bond in new_bonds) { RevertManagementInterfaces(new_bond); DestroyBond(new_bond.bond); } DestroyNetwork(network_ref); throw; } Description = string.Format(Messages.ACTION_CREATE_BOND_DONE, name_label); } private List GetHostsMasterLast() { List result = new List(Connection.Cache.Hosts); result.Remove(Master); result.Add(Master); return result; } /// /// Nothrow guarantee. /// /// /// private void UnlockAll(XenAPI.Network network, List new_bonds) { if (network != null) { network.Locked = false; foreach (NewBond newbond in new_bonds) { newbond.master.Locked = false; newbond.bond.Locked = false; } } foreach (Host host in PIFs.Keys) { foreach (PIF pif in PIFs[host]) { pif.Locked = false; } } } private void ReconfigureManagementInterfaces(List slaves, PIF new_master, int hi) { int lo = PercentComplete; int inc = (hi - lo) / slaves.Count; foreach (PIF pif in slaves) { lo += inc; // In Boston and later, the only thing we need to do is move the ManagementPurpose // (= management interface name) to the bond (see PR-1006/CP-2059). if (bostonOrGreater) { NetworkingActionHelpers.MoveManagementInterfaceName(this, pif, new_master); } else if (pif.management) { log.DebugFormat("Moving primary management interface to {0}...", pif.uuid); NetworkingActionHelpers.ReconfigureSinglePrimaryManagement(this, pif, new_master, lo); log.DebugFormat("Moving primary management interface to {0} done.", pif.uuid); } else if (pif.IsSecondaryManagementInterface(true)) { log.DebugFormat("Moving secondary management interface to {0}...", pif.uuid); ReconfigureSecondaryManagement(pif, new_master, lo); log.DebugFormat("Moving secondary management interface to {0} done.", pif.uuid); } } } private void ReconfigureSecondaryManagement(PIF src, PIF dest, int hi) { System.Diagnostics.Trace.Assert(!bostonOrGreater); int mid = (PercentComplete + hi) / 2; PIF new_dest = NetworkingHelper.CopyIPConfig(src, dest); NetworkingActionHelpers.BringDown(this, src, mid); NetworkingActionHelpers.BringUp(this, new_dest, dest, hi); } /// /// Nothrow guarantee. /// /// /// private void RevertManagementInterfaces(NewBond new_bond) { List slaves = new_bond.slaves; PIF master = new_bond.master; Bond bond = new_bond.bond; PIF slave = Connection.Resolve(bond.primary_slave); if (slave == null) { if (slaves.Count == 0) return; slave = slaves[0]; } try { if (bostonOrGreater) { NetworkingActionHelpers.MoveManagementInterfaceName(this, master, slave); } else if (master.management) { log.DebugFormat("Reverting primary management interface change for {0} as part of cleanup...", master.uuid); NetworkingActionHelpers.ReconfigurePrimaryManagement(this, master, slave, PercentComplete); log.DebugFormat("Reverting primary management interface change for {0} as part of cleanup done.", master.uuid); } else if (master.IsSecondaryManagementInterface(true)) { log.DebugFormat("Reverting secondary management interface change for {0} as part of cleanup...", master.uuid); ReconfigureSecondaryManagement(master, slave, PercentComplete); log.DebugFormat("Reverting secondary management interface change for {0} as part of cleanup done.", master.uuid); } } catch (Exception exn) { log.Warn(exn, exn); } } string CreateNetwork(int lo, int hi) { log.DebugFormat("Creating network {0}...", name_label); XenAPI.Network network = new XenAPI.Network(); network.name_label = name_label; network.AutoPlug = autoplug; if (Helpers.CowleyOrGreater(Connection)) network.MTU = mtu; if (network.other_config == null) network.other_config = new Dictionary(); network.other_config[XenAPI.Network.CREATE_IN_PROGRESS] = "true"; RelatedTask = XenAPI.Network.async_create(Session, network); PollToCompletion(lo, hi); log.DebugFormat("Created network {0} as {1}.", name_label, Result); return Result; } /// /// Nothrow guarantee. /// /// private void DestroyNetwork(string network) { log.DebugFormat("Destroying network {0} as part of cleanup...", network); try { XenAPI.Network.destroy(Session, network); } catch (Exception exn) { log.Warn(exn, exn); } log.DebugFormat("Destroying network {0} as part of cleanup done.", network); } /// /// Nothrow guarantee. /// /// private void DestroyBond(Bond bond) { log.DebugFormat("Destroying bond {0} as part of cleanup...", bond.uuid); try { XenAPI.Bond.destroy(Session, bond.opaque_ref); } catch (Exception exn) { log.Warn(exn, exn); } log.DebugFormat("Destroying bond {0} as part of cleanup done.", bond.uuid); } protected override void Clean() { Connection.ExpectDisruption = false; UnlockAll(network, new_bonds); } } }