65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 VIX API 在 C# 中自动化 VMWare 任务

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (29投票s)

2008 年 12 月 22 日

MIT

5分钟阅读

viewsIcon

409426

downloadIcon

4066

一个 VMware C# 任务库。

VMWare

引言

我最近一直在玩 VMware,包括 Workstation 和 VMware Infrastructure (VI)。该公司通过新的 SDK 和可编程接口的级别确实下了苦功,做了一些出色的实现决策,使开发人员能够通过异步、基于作业的编程模型以编程方式驱动虚拟机。不幸的是,对于 99.9% 的场景来说,这都太复杂了,大多数开发人员希望使用简单的面向对象接口来处理常见的 VMware 任务。VMwareTasks 库实现了这个接口,使得针对虚拟机进行编程变得轻而易举。本文将介绍如何使用该库并讨论其一些实现细节。

背景

VMware API 有两种类型。

  • VMware Virtual Infrastructure SDK:一套用于管理 VMware Infrastructure 环境的工具和 API。此外还发布了一个工具包,其中包含基于 VMware 部署提供的 SOAP 接口的托管包装器。它专注于 VMware ESX 或 VirtualCenter 管理,超出了本文的范围。
  • VMware VIX API。VIX API 允许开发人员编写程序和脚本来自动化虚拟机操作,以及虚拟机内的客户机。它同时支持 Windows 和 Linux,并支持 VMware Server、Workstation 和 Virtual Infrastructure(包括 ESX 和 vCenter)的管理。提供了 C、Perl 和 COM(Visual Basic、VBscript、C#)的绑定。在本文中,我将重点介绍 C# 实现。

使用库

为了使用该库或构建/运行源代码,您必须安装以下 VMware 软件。

在您的项目中,添加对 Vestris.VMWareLib.dll 的引用和一个命名空间引用。

using Vestris.VMWareLib;

现在您可以连接到本地 VMware Workstation 或远程 ESX 服务器并执行 VMware 任务。这是一个在 VMware Workstation 上创建、恢复、开启和删除快照的示例。

// declare a virtual host
VMWareVirtualHost virtualHost = new VMWareVirtualHost();
// connect to a local (VMWare Workstation) virtual machine
virtualHost.ConnectToVMWareWorkstation();
// open an existing virtual machine
VMWareVirtualMachine virtualMachine = virtualHost.Open("C:\Virtual Machines\xp\xp.vmx");
// power on this virtual machine
virtualMachine.PowerOn();
// login to the virtual machine
virtualMachine.Login("Administrator", "password");
// run notepad
virtualMachine.RunProgramInGuest("notepad.exe", string.Empty);
// create a new snapshot
string name = "New Snapshot";
// take a snapshot at the current state
virtualMachine.Snapshots.CreateSnapshot(name, "test snapshot");
// power off
virtualMachine.PowerOff();
// find the newly created snapshot
VMWareSnapshot snapshot = virtualMachine.Snapshots.GetNamedSnapshot(name);
// revert to the new snapshot
snapshot.RevertToSnapshot();
// delete snapshot
snapshot.RemoveSnapshot();

实现

以下各节描述了 VMWareTasks 库的实现细节。要理解这些内容以使用该库,完全不是必需的。

在纯 VIX API 中连接到 VMware

同步连接到本地 VMware Workstation 或 ESX 服务器几乎是相同的。ESX 服务器需要 SOAP SDK 的 URL(例如,https://esxserver/sdk)以及用户名和密码。

public IHost ConnectToVMWareWorkstation()
{
    return Connect(Constants.VIX_SERVICEPROVIDER_VMWARE_WORKSTATION, 
		string.Empty, 0, string.Empty, string.Empty);
}

public IHost ConnectToVMWareVIServer(string hostName, int hostPort, 
			string username, string password)
{
    return Connect(Constants.VIX_SERVICEPROVIDER_VMWARE_VI_SERVER,  
			hostName, hostPort, username, password);
}

public IHost Connect(int hostType, string hostName, int hostPort, 
			string username, string password)
{
    VixLib vix = new VixLib();
    IJob vmJob = vix.Connect(Constants.VIX_API_VERSION, hostType, 
		hostName, hostPort, username, password, 0, null, null);
    // You need to get the IHost object that represents the host 
    // where your VM is located.
    // Since COM allocates the object you need to use this funky mechanism 
    // to extract the IHosts array.
    object[] properties = { Constants.VIX_PROPERTY_JOB_RESULT_HANDLE };
    // Wait for the operation to complete
    object hosts = VmwareVixInterop.Wait(vmJob, properties);
    object[] hostArray = hosts as object[];
    return (IHost) hostArray[0];
}

您必须声明一个您希望作业生成的属性数组,启动一个 VMware 作业,并检查主机句柄的结果。您可以看到这有多么麻烦!该 API 最初是为 C 设计的,然后扩展到 COM,并具有非常有限的对象模型:声明了许多接口,但没有实现相应的 COM 类。此外,由于作业接口是通用的,因此没有强类型结果。

我们可以在 C# 实现中轻松地填补这一空白。

将错误代码转换为异常

包装任何 API 的第一个任务是实现错误处理。我们的托管实现必须将错误代码转换为托管异常。VIX API 为 IVixLib 接口提供了实现,该接口包含一些辅助方法(非常 C 程序员的风格)。我们将关注 IVixLib.ErrorIndicatesFailureIVixLib.GetErrorText,并结合一个新类 VMWareException

public abstract class VMWareInterop
{
    public static VixLib Instance = new VixLib();

    public static void Check(ulong errCode)
    {
        if (Instance.ErrorIndicatesFailure(errCode))
        {
            throw new VMWareException(errCode);
        }
    }
}  

除了抽象的 VMWareInterop,我们的目标是生成具体的类来包装 VMware 功能的各个方面。

基本 VMWareVixHandle

VMware COM API 返回接口指针,如 ISnapshot。对象还实现 IVixHandle,该接口提供对一组对象属性的访问。我们将把所有内容都基于 VMWareVixHandle 进行派生。

public class VMWareVixHandle<T>
{
    protected T _handle = default(T);

    protected IVixHandle _vixhandle
    {
        get
        {
            return (IVixHandle) _handle;
        }
    }

    public VMWareVixHandle()
    {

    }

    public VMWareVixHandle(T handle)
    {
        _handle = handle;
    }

    public object[] GetProperties(object[] properties)
    {
        object result = null;
        VMWareInterop.Check(_vixhandle.GetProperties(properties, ref result));
        return (object[]) result;
    }

    public R GetProperty<R>(int propertyId)
    {
        object[] properties = { propertyId };
        return (R) GetProperties(properties)[0];
    }
}  

实现 VMWareJob

由于 VMware 中的所有操作都是基于作业的,因此让我们包装一个作业。如果我们直接使用 COM API,则必须调用 IVixLib.Wait 并传递一个作业句柄。在面向对象的库中,这个操作属于作业内部,并且作业也是一个 VMWareVixHandle

 public class VMWareJob : VMWareVixHandle<IJob>
 {
     public VMWareJob(IJob job)
         : base(job)
     {

     }

     public void Wait()
     {
         VMWareInterop.Check(_handle.WaitWithoutResults());
     }
 }  

VMware API 实现中一个非常普遍的问题是将异步作业转换为同步作业,即使用上面阻塞的 wait。这是一个糟糕的设计决策,因为这个调用可能永远不会返回。VMware 服务器可能会超时,或者有人可以拔掉网线,导致您的程序挂起。我最初编写了一个忙等待,其中所有外部可见的等待函数都基于以下 InternalWait

private void InternalWait(int timeoutInSeconds)
{
    if (timeoutInSeconds == 0)
    {
        throw new ArgumentOutOfRangeException("timeoutInSeconds");
    }

    // active wait for the job to finish
    bool isComplete = false;
    while (!isComplete && timeoutInSeconds > 0)
    {
        VMWareInterop.Check(_handle.CheckCompletion(out isComplete));
        if (isComplete) break;
        Thread.Sleep(1000);
        timeoutInSeconds--;
    }

    if (timeoutInSeconds == 0)
    {
        throw new TimeoutException();
    }
}

一个更优雅的实现结合了 VixCOM 提供的回调机制和每个异步 API,以及一个带有超时功能的阻塞 wait。现在 Wait 得到了信号,并且没有 CPU 自旋等待异步 API 调用完成。

public class VMWareJobCallback : ICallback
{
    #region ICallback Members

    private EventWaitHandle _jobCompleted = new EventWaitHandle(
         false, EventResetMode.ManualReset);

    public void OnVixEvent(IJob job, int eventType, IVixHandle moreEventInfo)
    {
        switch (eventType)
        {
            case VixCOM.Constants.VIX_EVENTTYPE_JOB_COMPLETED:
                _jobCompleted.Set();
                break;
        }
    }

    public bool TryWaitForCompletion(int timeoutInMilliseconds)
    {
        return _jobCompleted.WaitOne(timeoutInMilliseconds, false);
    }

    public void WaitForCompletion(int timeoutInMilliseconds)
    {
        if (!TryWaitForCompletion(timeoutInMilliseconds))
        {
            throw new TimeoutException();
        }
    }

    #endregion
}  

这个和 VMWareJob 中的一些通用代码现在可以在 Connect 中使用。我已经修改了 VMWareJob 以要求 VMWareCallback,以防止调用者进行阻塞等待。

VMWareJobCallback callback = new VMWareJobCallback();
VMWareJob job = new VMWareJob(VMWareInterop.Instance.Connect(
    Constants.VIX_API_VERSION, Constants.VIX_SERVICEPROVIDER_VMWARE_SERVER, 
	hostName, hostPort, username, password, 0, null, callback), callback); 

VMwareVirtualHost

有了 VMWareJobVMWareException,现在就可以实现 VMWareVirtualHost 并连接到它。请注意对默认超时(一系列常量)的引用以及 VMWareJob 中用于为 VMware 作业结果添加强类型的一些功能。

public class VMWareVirtualHost
{
    private IHost _host = null;

    public VMWareVirtualHost()
    {

    }

    public void ConnectToVMWareWorkstation()
    {
        ConnectToVMWareWorkstation(VMWareInterop.Timeouts.ConnectTimeout);
    }

    public void ConnectToVMWareWorkstation(int timeoutInSeconds)
    {
        Connect(Constants.VIX_SERVICEPROVIDER_VMWARE_WORKSTATION,
            string.Empty, 0, string.Empty, string.Empty, timeoutInSeconds);
    }

    private void Connect(int hostType, string hostName, 
	int hostPort, string username, string password, int timeout)
    {
        int serviceProvider = (int)serviceProviderType;
        VMWareJobCallback callback = new VMWareJobCallback();
        VMWareJob job = new VMWareJob(VMWareInterop.Instance.Connect(
            Constants.VIX_API_VERSION, serviceProvider, hostName, hostPort,
            username, password, 0, null, callback), callback);

        _handle = job.Wait
(Constants.VIX_PROPERTY_JOB_RESULT_HANDLE, timeout);
        _serviceProviderType = serviceProviderType;
    }
}

VMWareVirtualHost 现在可以实现打开实际虚拟机并返回 VMWareVirtualMachine 的实例。

public VMWareVirtualMachine Open(string fileName, int timeoutInSeconds)
{
    VMWareJobCallback callback = new VMWareJobCallback();
    VMWareJob job = new VMWareJob(_handle.OpenVM(fileName, callback), callback);
    return new VMWareVirtualMachine(job.Wait<ivm2>
    (
        Constants.VIX_PROPERTY_JOB_RESULT_HANDLE,
        timeoutInSeconds));
}

基于此模型,我们可以编写 VMWareVirtualMachineVMWareSnapshot 等的许多函数。其余的都是实现细节。

高级实现细节

生成属性数组

VIX COM API 的一个特殊构造是返回属性数组的组合。这是通过两个函数完成的:GetNumPropertiesGetNthProperties。第一个函数返回作业返回的属性数组的数量,第二个函数在给定索引处获取属性数组。第一个明显的步骤是将函数包装在作业类中。

public T GetNthProperties<T>(int index, object[] properties)
{
    object result = null;
    VMWareInterop.Check(_handle.GetNthProperties(index, properties, ref result));
    return (T) result;
}

public int GetNumProperties(int property)
{
    return _handle.GetNumProperties(property);
}

我们现在可以编写诸如 RunningVirtualMachines 这样的属性。

public IEnumerable<VMWareVirtualMachine> RunningVirtualMachines
{
    get
    {
        VMWareJobCallback callback = new VMWareJobCallback();
        VMWareJob job = new VMWareJob(_handle.FindItems(
            Constants.VIX_FIND_RUNNING_VMS, null, -1, callback),
            callback);
        object[] properties = { Constants.VIX_PROPERTY_FOUND_ITEM_LOCATION };
        for (int i = 0; i < job.GetNumProperties((int) properties[0]); i++)
        {
            yield return this.Open(
                (string) job.GetNthProperties<object[]>(i, properties)[0]);
        }
    }
}

这很好,但仍然不够好。让我们在 YieldWait 方法中组合结果数量和结果本身。

public IEnumerable <object[]> YieldWait(object[] properties, int timeoutInSeconds)
{
    Wait(timeoutInSeconds);
    for (int i = 0; i < GetNumProperties((int)properties[0]); i++)
    {
        yield return GetNthProperties<object[]>(i, properties);
    }
}

这比之前的实现有了很好的改进。

public IEnumerable<VMWareVirtualMachine> RunningVirtualMachines
{
    get
    {
        VMWareJobCallback callback = new VMWareJobCallback();
        VMWareJob job = new VMWareJob(_handle.FindItems(
            Constants.VIX_FIND_RUNNING_VMS, null, -1, callback),
            callback);
        foreach (object[] runningVirtualMachine in job.YieldWait
		(properties, VMWareInterop.Timeouts.FindItemsTimeout))
        {
            yield return this.Open((string) runningVirtualMachine[0]);
        }
    }
}  

日期和时间

VMware VIX 中的日期/时间以 UNIX EPOCH(自 1970 年 1 月 1 日起的秒数)表示。

DateTime currentDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0).AddSeconds((int) dt);

源代码和补丁

该项目托管在 CodePlex 上,网址为 http://www.codeplex.com/vmwaretasks。您可以在 code.dblock.org 上找到有关此库的最新信息。我们鼓励您提交用于添加功能和修复错误的补丁。

历史

  • 2008/12/22:初始文章,版本 1.0
  • 2009/02/11:修订了“使用 VMWareTasks”并描述了阻塞作业 wait 的实现
  • 2009/02/12:迁移到 CodePlex,版本 1.1
使用 VIX API 在 C# 中自动化 VMware 任务 - CodeProject - 代码之家
© . All rights reserved.