xenadmin/XenModel/Actions/VM/ImportVmAction.cs
Mihaela Stoica 7ac856943e CA-272143: Import wizard stuck in "Waiting for Import template wizard to complete" when importing a default template
When trying to import a default template from xva, xapi notices that it is a default template, finds an existing one on the host and returns that one.
With these changes, XenCenter handles this case and displays an error, instead of waiting for the returned VM to be linked to the import task (which never happens).

Signed-off-by: Mihaela Stoica <mihaela.stoica@citrix.com>
2017-11-09 11:31:00 +00:00

437 lines
17 KiB
C#

/* 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<Proxy_VIF> m_proxyVIFs;
private object monitor = new object();
private bool m_wizardDone;
/// <summary>
/// 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.
/// </summary>
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 = "<value><array><data><value>";
string tail = "</value></data></array></value>";
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<VM>(vmRef))) == null)
Thread.Sleep(100);
if (Cancelling)
throw new CancelledException();
isTemplate = VM.get_is_a_template(Session, vmRef);
if (isTemplate && 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_proxyVIFs != 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<XenRef<VIF>> vifs = VM.get_VIFs(Session, vmRef);
List<XenAPI.Network> networks = new List<XenAPI.Network>();
bool canMoveVifs = Helpers.ElyOrGreater(Connection);
foreach (XenRef<VIF> 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_proxyVIFs.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_proxyVIFs.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 (Proxy_VIF proxyVIF in m_proxyVIFs)
{
VIF vif = new VIF(proxyVIF) {VM = new XenRef<VM>(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<string> TaskErrorInfo()
{
return new List<string>(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<string> 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<string> 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<Proxy_VIF> vifs)
{
m_startAutomatically = start;
m_proxyVIFs = vifs;
lock (monitor)
{
m_wizardDone = true;
Monitor.PulseAll(monitor);
}
}
}
}