/* 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.ComponentModel; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Net; using XenAdmin.Core; using XenAPI; using System.Threading; using XenAdmin.Network; namespace XenAdmin.Actions { public class ImportVmAction : AsyncAction { private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); private const int HTTP_PUT_TIMEOUT = 30*60*1000; //30 minutes private string m_filename; private readonly Host m_affinity; public const string IMPORT_TASK = "import_task"; private bool m_startAutomatically; private List m_VIFs; private object monitor = new object(); private bool m_wizardDone; /// /// These calls are always required to import a VM, as opposed to the set which are only called if needed set in the constructor of this action. /// public readonly static RbacMethodList ConstantRBACRequirements = new RbacMethodList( "vm.set_name_label", "network.destroy", "vif.create", "vif.destroy", "http/put_import" ); public ImportVmAction(IXenConnection connection, Host affinity, string filename, SR sr) : base(connection, string.Format(Messages.IMPORTVM_TITLE, filename, Helpers.GetName(connection)), Messages.IMPORTVM_PREP) { Pool = Helpers.GetPoolOfOne(connection); m_affinity = affinity; Host = affinity ?? connection.Resolve(Pool.master); SR = sr; VM = null; m_filename = filename; #region RBAC Dependencies ApiMethodsToRoleCheck.AddRange(ConstantRBACRequirements); if (affinity != null) ApiMethodsToRoleCheck.Add("vm.set_affinity"); //?? //if (startAutomatically) // ApiMethodsToRoleCheck.Add("vm.start"); ApiMethodsToRoleCheck.AddRange(Role.CommonTaskApiList); ApiMethodsToRoleCheck.AddRange(Role.CommonSessionApiList); #endregion } private string GetVmRef(string result) { if (string.IsNullOrEmpty(result)) return null; string head = ""; string tail = ""; if (!result.StartsWith(head) || !result.EndsWith(tail)) return null; int start = head.Length; int length = result.IndexOf(tail) - start; return result.Substring(start, length); } protected override void Run() { SafeToExit = false; bool isTemplate; try { string vmRef; if (m_filename.EndsWith("ova.xml"))//importing version 1 from of VM { m_filename = m_filename.Replace("ova.xml", ""); vmRef = GetVmRef(applyVersionOneFiles()); } else//importing current format of VM vmRef = GetVmRef(applyFile()); if (Cancelling) throw new CancelledException(); // Now lets try and set the affinity and start the VM if (string.IsNullOrEmpty(vmRef)) return; while (!Cancelling && (VM = Connection.Resolve(new XenRef(vmRef))) == null) Thread.Sleep(100); if (Cancelling) throw new CancelledException(); isTemplate = VM.get_is_a_template(Session, vmRef); if (isTemplate && Helpers.FalconOrGreater(Connection) && VM.get_is_default_template(Session, vmRef)) { var otherConfig = VM.get_other_config(Session, vmRef); if (!otherConfig.ContainsKey(IMPORT_TASK) || otherConfig[IMPORT_TASK] != RelatedTask.opaque_ref) { throw new Exception(Messages.IMPORT_TEMPLATE_ALREADY_EXISTS); } } Description = isTemplate ? Messages.IMPORT_TEMPLATE_UPDATING_TEMPLATE : Messages.IMPORTVM_UPDATING_VM; VM.set_name_label(Session, vmRef, DefaultVMName(VM.get_name_label(Session, vmRef))); if (!isTemplate && m_affinity != null) VM.set_affinity(Session, vmRef, m_affinity.opaque_ref); // Wait here for the wizard to finish Description = isTemplate ? Messages.IMPORT_TEMPLATE_WAITING_FOR_WIZARD : Messages.IMPORTVM_WAITING_FOR_WIZARD; lock (monitor) { while (!(m_wizardDone || Cancelling)) Monitor.Wait(monitor); } if (Cancelling) throw new CancelledException(); if (m_VIFs != null) { Description = isTemplate ? Messages.IMPORT_TEMPLATE_UPDATING_NETWORKS : Messages.IMPORTVM_UPDATING_NETWORKS; // For ElyOrGreater hosts, we can move the VIFs to another network, // but for older hosts we need to destroy all vifs and recreate them List> vifs = VM.get_VIFs(Session, vmRef); List networks = new List(); bool canMoveVifs = Helpers.ElyOrGreater(Connection); foreach (XenRef vif in vifs) { // Save the network as we may have to delete it later XenAPI.Network network = Connection.Resolve(VIF.get_network(Session, vif)); if (network != null) networks.Add(network); if (canMoveVifs) { var vifObj = Connection.Resolve(vif); if (vifObj == null) continue; // try to find a matching VIF in the m_proxyVIFs list, based on the device field var matchingProxyVif = m_VIFs.FirstOrDefault(proxyVIF => proxyVIF.device == vifObj.device); if (matchingProxyVif != null) { // move the VIF to the desired network VIF.move(Session, vif, matchingProxyVif.network); // remove matchingProxyVif from the list, so we don't create the VIF again later m_VIFs.Remove(matchingProxyVif); continue; } } // destroy the VIF, if we haven't managed to move it VIF.destroy(Session, vif); } // recreate VIFs if needed (m_proxyVIFs can be empty, if we moved all the VIFs in the previous step) foreach (VIF vif in m_VIFs) { vif.VM = new XenRef(vmRef); VIF.create(Session, vif); } // now delete any Networks associated with this task if they have no VIFs foreach (XenAPI.Network network in networks) { if (!network.other_config.ContainsKey(IMPORT_TASK)) continue; if (network.other_config[IMPORT_TASK] != RelatedTask.opaque_ref) continue; try { if (XenAPI.Network.get_VIFs(Session, network.opaque_ref).Count > 0) continue; if (XenAPI.Network.get_PIFs(Session, network.opaque_ref).Count > 0) continue; XenAPI.Network.destroy(Session, network.opaque_ref); } catch (Exception e) { log.ErrorFormat("Exception while deleting network {0}. Squashing.", network.Name()); log.Error(e, e); } } } if (!VM.get_is_a_template(Session, vmRef)) { if (m_startAutomatically) { Description = Messages.IMPORTVM_STARTING; VM.start(Session, vmRef, false, false); } } } catch (CancelledException) { Description = Messages.CANCELLED_BY_USER; throw; } Description = isTemplate ? Messages.IMPORT_TEMPLATE_IMPORTCOMPLETE : Messages.IMPORTVM_IMPORTCOMPLETE; } private int VMsWithName(string name) { int i = 0; foreach (VM v in Connection.Cache.VMs) if (v.name_label == name) i++; return i; } private string DefaultVMName(string vmName) { string name = vmName; int i = 0; if (VMsWithName(vmName) > 1) while (VMsWithName(name) > 0) { i++; name = string.Format(Messages.NEWVM_DEFAULTNAME, vmName, i); } return name; } private List TaskErrorInfo() { return new List(Task.get_error_info(Session, RelatedTask)); } private long getSize(DirectoryInfo dir, long count) { long s = count; foreach (FileInfo f in dir.GetFiles("*.gz")) s += f.Length; foreach (DirectoryInfo d in dir.GetDirectories()) s += getSize(d, s); return s; } private string applyVersionOneFiles() { RelatedTask = Task.create(Session, "importTask", Messages.IMPORTING); try { long totalSize = getSize(new DirectoryInfo(m_filename), 0); long bytesWritten = 0; if (totalSize == 0) { // We didn't find any .gz files, just bail out here throw new Exception(Messages.IMPORT_INCOMPLETE_FILES); } CommandLib.Config config = new CommandLib.Config { hostname = Connection.Hostname, username = Connection.Username, password = Connection.Password }; CommandLib.thinCLIProtocol tCLIprotocol = null; int exitCode = 0; tCLIprotocol = new CommandLib.thinCLIProtocol(delegate(string s) { throw new Exception(s); }, delegate { throw new Exception(Messages.EXPORTVM_NOT_HAPPEN); }, delegate(string s, CommandLib.thinCLIProtocol t) { log.Debug(s); }, delegate(string s) { log.Debug(s); }, delegate(string s) { log.Debug(s); }, delegate { throw new Exception(Messages.EXPORTVM_NOT_HAPPEN); }, delegate(int i) { exitCode = i; tCLIprotocol.dropOut = true; }, delegate(int i) { bytesWritten += i; PercentComplete = (int)(100.0*bytesWritten/totalSize); }, config); string body = string.Format("vm-import\nsr-uuid={0}\nfilename={1}\ntask_id={2}\n", SR.uuid, m_filename, RelatedTask.opaque_ref); log.DebugFormat("Importing Geneva-style XVA from {0} to SR {1} using {2}", m_filename, SR.Name(), body); CommandLib.Messages.performCommand(body, tCLIprotocol); // Check the task status -- Geneva-style XVAs don't raise an error, so we need to check manually. List excep = TaskErrorInfo(); if (excep.Count > 0) throw new Failure(excep); // CA-33665: We found a situation before were the task handling had been messed up, we should check the exit code as a failsafe if (exitCode != 0) throw new Failure(new[] {Messages.IMPORT_GENERIC_FAIL}); return Task.get_result(Session, RelatedTask); } catch (Exception exn) { List excep = TaskErrorInfo(); if (excep.Count > 0) throw new Failure(excep); else throw exn; } finally { Task.destroy(Session, RelatedTask); } } private string applyFile() { log.DebugFormat("Importing Rio-style XVA from {0} to SR {1}", m_filename, SR.Name()); Host host = SR.GetStorageHost(); string hostURL; if (host == null) { Uri uri = new Uri(Session.Url); hostURL = uri.Host; } else { log.DebugFormat("SR is not shared -- redirecting to {0}", host.address); hostURL = host.address; } log.DebugFormat("Using {0} for import", hostURL); return HTTPHelper.Put(this, HTTP_PUT_TIMEOUT, m_filename, hostURL, (HTTP_actions.put_ssbbs)HTTP_actions.put_import, Session.uuid, false, false, SR.opaque_ref); } public override void RecomputeCanCancel() { CanCancel = m_wizardDone; } protected override void CancelRelatedTask() { log.InfoFormat("VM/Template import from '{0}' cancelled", m_filename); lock (monitor) { Monitor.PulseAll(monitor); } base.CancelRelatedTask(); } public void EndWizard(bool start, List vifs) { m_startAutomatically = start; m_VIFs = vifs; lock (monitor) { m_wizardDone = true; Monitor.PulseAll(monitor); } } } }