Showing posts with label Hyper-V. Show all posts
Showing posts with label Hyper-V. Show all posts

Thursday, September 12, 2013

Hyper-V KVP Data Exchange for CloudStack

Background


Virtual machines created from the same VM template will behave identically unless they are passed additional configuration information.

For instance, CloudStack uses a single, generic VM template as the basis of every system VM.  When a VM is created from this template, it is passed additional information that determines what services it will run.  Depending on the configuration, the VM might become a virtual router, a secondary storage VM, or a proxy for VM console access.


Problem


The CloudStack system VM template does not harvest configuration when it is running on Hyper-V 2012.  There needs to be a mechanism whereby this information is passed to the VM and harvested by the VM at start up.


Solution


Use the Hyper-V KVP Data Exchange to pass data to VMs at startup.  Update the template with integration services that write KVP data to disk.  Finally, update the System VM start up to harvest KVP data.

Let's elaborate on what each step involves...


Hyper-V KVP Data Exchange using WMI

KVP stands for Key Value Pair.  These key / value pairs originally modeled information that appears in a Windows registry entry: registry entries have a name (a key), and they contain data (a value).  However, KVPs are operating system agnostic.  For example, it is just as easy to store a set of key value pairs on disk in Linux as it is to put them in the registry on Windows.

KVP Data Exchange involves transmitting data between the host and the guest OS over the VMBus.  "VMBus" is the term Hyper-V uses for its inter-partition communication channel.  Specifically, the data exchanged consists of KVPs.  Depending on the source of the KVP, their data is transmitted from host to guest or guest to host.

KVP Data Exchange is accessed through the WMI APIs exposed by Hyper-V.  First, KVP data is placed in an Msvm_KvpExchangeDataItem.  Place the key in the "Name" filed, the value in the "Data" field, and set the "Source" field to 0 to indicate we are pushing KVP from host to guest.  E.g.

KvpExchangeDataItem kvpItem = KvpExchangeDataItem.CreateInstance();
kvpItem.LateBoundObject["Name"] = "cloudstack-vm-userdata";
kvpItem.LateBoundObject["Data"] = "username=root;password=1pass@word1";
kvpItem.LateBoundObject["Source"] = 0;

The example above uses C# WMI wrappers generated using visual Studio.  If you must insist, source is available at.  Another alternative is to use PowerShell commands.

Next, register the KVP object with Hyper-V using the AddKvpItems method of the Msvm_VirtualSystemManagementService class  I.e.

uint32 AddKvpItems(
  [in]   CIM_ComputerSystem REF TargetSystem,
  [in]   string DataItems[],
  [out]  CIM_ConcreteJob REF Job
);

If you are new to WMI, let me explain further:  The aim of WMI is to provide APIs that are platform agnostic.  Therefore, parameters for the method call have to be serialisable in a cross platform manner.  As a result, object references look more like a URI than a memory address.  E.g. here is a sample CIM_ComputerSystem reference used to call AddKvpItems:

\\CC-SVR10\root\virtualization\v2:Msvm_ComputerSystem.CreationClassName="Msvm_ComputerSystem",Name="3969FFB5-5341-422A-B8D6-A60ECB6D73D6"


Indeed, WMI objects refer to such a reference as the WMI object path

Likewise, the DataItems parameter is an array of serialised Msvm_KvpExchangeDataItem objects serialised according to a WMI-specific format.  When it comes to serialisation, WMI follows standards set by the DTMF.  Specifically, the format used to encode a WMI object is CIM XML DTD 2.0.  Therefore, the DataItems parameter is an array of XML-serialized data.  E.g.

<INSTANCE CLASSNAME="Msvm_KvpExchangeDataItem">
  <PROPERTY NAME="Caption" PROPAGATED="true" TYPE="string"></PROPERTY>
  <PROPERTY NAME="Data" TYPE="string">
    <VALUE>username=root;password=1pass@word1</VALUE>
  </PROPERTY>
  <PROPERTY NAME="Description" PROPAGATED="true" TYPE="string"></PROPERTY>
  <PROPERTY NAME="ElementName" PROPAGATED="true" TYPE="string"></PROPERTY>
  <PROPERTY NAME="InstanceID" PROPAGATED="true" TYPE="string"></PROPERTY>
  <PROPERTY NAME="Name" TYPE="string">
    <VALUE>cloudstack-vm-userdata</VALUE>
  </PROPERTY>
  <PROPERTY NAME="Source" TYPE="uint16">
    <VALUE>0</VALUE>
  </PROPERTY>
</INSTANCE>

In C#, the System.Management namespace provides objects to facilitate to create this XML for us.  Specifically, ManagementObjectBase.GetText performs the nescessary serialisation.

Following on from the C# above, we call AddKvpItems in this snippet:

System.Management.ManagementBaseObject kvpMgmtObj = kvpItem.LateBoundObject;
System.Management.ManagementPath jobPath;
String kvpStr = kvpMgmtObj.GetText(System.Management.TextFormat.CimDtd20);
uint ret_val = vmMgmtSvc.AddKvpItems(new String[] { kvpStr }, 
                                     vm.Path, out jobPath);

Again, C# wrappers hide much complexity.

Adding a KVP item with a Source '0' item will cause the Hyper-V host to send data; however, the Guest needs to be setup to receive KVP data.


hv_kvp_daemon the KVP Daemon

KVP data is transfered to the file system through the collaboration of a kernel driver and a user mode daemon.

The KVP driver code, hv_kvp.c (http://lxr.linux.no/linux+v3.11/drivers/hv/hv_kvp.c), is compiled into the hv_utils kernel module (Source http://lxr.linux.no/linux+v3.11/drivers/hv/Makefile).
Since the driver is part of the Linux kernel code, it is provided by default with recent versions of common Linux distributions.  E.g.

[root@centos6-4-hv ~]# cat /etc/*-release
CentOS release 6.4 (Final)
CentOS release 6.4 (Final)
CentOS release 6.4 (Final)
[root@centos6-4-hv ~]# modinfo -F filename hv_utils
/lib/modules/2.6.32-358.el6.i686/kernel/drivers/hv/hv_utils.ko

However, it is the usermode daemon, hv_kvp_daemon, that copies KVP data to the system.  On startup, hv_kvp_daemon creates files to store kvp data under
/var/lib/hyperv (source http://lxr.linux.no/linux+v3.11/tools/hv/hv_kvp_daemon.c).
Each file is known as a 'pool', and there is a file for each data pool.  E.g.

[root@centos6-4-hv hyperv]# ls -al /var/lib/hyperv/
total 36
drwxr-xr-x.  2 root root  4096 Sep 11 21:33 .
drwxr-xr-x. 16 root root  4096 Sep 10 13:59 ..
-rw-r--r--.  1 root root  2560 Sep 10 17:05 .kvp_pool_0
-rw-r--r--.  1 root root     0 Sep 10 14:02 .kvp_pool_1
-rw-r--r--.  1 root root     0 Sep 10 14:02 .kvp_pool_2
-rw-r--r--.  1 root root 28160 Sep 10 14:02 .kvp_pool_3
-rw-r--r--.  1 root root     0 Sep 10 14:02 .kvp_pool_4


The prefix of each file is the pool number.  This correspoinds to the KVP source.  E.g. remember that source '0' is used for transmitting data from host to guest?  That means our KVP data is in /var/lib/hyperv/.kvp_pool_0.  E.g.

[root@centos6-4-hv hyperv]# cat /var/lib/hyperv/.kvp_pool_0
cloudstack-vm-userdatausername=root;password=1pass@word1[root@centos6-4-hv hyperv]#


Aside:  there are five pools (source http://lxr.linux.no/linux+v3.11/include/linux/hyperv.h), but only four sources listed for KVP data exchange.
It appears that pool '3' contains predefined KVPs sent by the Host such as the host machine's name.

With this in mind, it is important that hv_kvp_daemon be installed and an init script added.

Microsoft provides the daemon, hv_kvp_daemon, in a package suited for RHEL (http://dlafferty.blogspot.co.uk/2013/07/installing-hyper-v-pv-drivers-for-linux.html).

However, it may be easier to search for a package specific to your distro.  E.g. using rpmfind.net I found hv_kvp_daemon in the hypervkvpd package.  E.g. for CentOS 6.4 simply call:
yum install hypervkvpd


Unfortunately, I have not found a package for Debian.  An alternative is to compile the source and write an init script.  There is a sample here.

Once you are able to create the file, you need to be able to parse it...


Harvesting Hyper-V KVP Data in Linux

KVP data files contains an array of key / value pairs.  Each is a byte array of a fixed size.  Source 

/*
 * Maximum key size - the registry limit for the length of an entry name
 * is 256 characters, including the null terminator
 */
#define HV_KVP_EXCHANGE_MAX_KEY_SIZE            (512)

/*
 * bytes, including any null terminators
 */
#define HV_KVP_EXCHANGE_MAX_VALUE_SIZE          (2048)

The byte array contains a UTF-8 encoded string, which is padded out to the max size with null characters.  However, Null string termination is not guaranteed (see kvp_send_key).

Provided there is only one key and the key name known, the easiest way to parse the file is to use sed.  To remove null characters and the key name used in our example, you would use the following:

[root@centos6-4-hv hyperv]# cat /var/lib/hyperv/.kvp_pool_0 | sed 's/\x0//g' | sed 's/cloudstack-vm-userdata//g' > userdata
[root@centos6-4-hv hyperv]# more userdata
username=root;password=1pass@word1


Wednesday, August 14, 2013

Building Your Microsoft Solution with Mono

Background:

The agent for the CloudStack Hyper-V plugin was written in C# using the Microsoft Visual Studio tool chain.

However, Apache CloudStack source should be able to be built using open source tools.

Problem:

How do you compile an ASP.NET MVC4 web app written in C# using an open source tool chain?

Solution:

The latest version of Mono include a tools called Xbuild, which can consume the .sln and .csproj files that Visual Studio generates for your solution.

However, you will have to make some updates to the way your project fetches NuGet dependencies, and the projects that get built.

Let's cover all the steps involved one at a time.

Install Mono

First, install the latest release of Mono 3.x.  This version introduces the support for C# 5.0 and ships with the ASP.NET WebStack (Release Notes)

Although a package for this version is not advertised on the Mono Downloads page, the
mono archives include a 3.0.10 Windows .msi

Alternatively, there is a 3.x Debian package available.  For this package, update your apt sources and use apt-get to install the mono-complete package, which contains the tool chain.  E.g.
sed -e "\$adeb http://debian.meebey.net/experimental/mono /" -i /etc/apt/sources.list
apt-get update
apt-get install mono-complete

Mono ships with an empty certificate store.  The store needs to be populated with common certificates in order for HTTPS to work.  Use the mozroots tool to do this.  E.g.
root@mgmtserver:~/github/cshv3/plugins/hypervisors/hyperv/DotNet# mozroots --import --sync --machine
Mozilla Roots Importer - version 3.0.6.0
Download and import trusted root certificates from Mozilla's MXR.
Copyright 2002, 2003 Motus Technologies. Copyright 2004-2008 Novell. BSD licensed.

Downloading from 'http://mxr.mozilla.org/seamonkey/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1'...
Importing certificates into user store...
Import process completed.

NB: whether you add the certs to your user ( "mozroots --import --sync) or the machine (mozroots --import --sync --machine) depends on what user is used to run web requests.  On Debian 7.0, I found that the machine certificate store had to be updated.

Understand NuGet Packages

NuGet is a package manager for the .NET platform.  These packages consist of assemblies used at compile time and runtime.  The packages are stored in perpetuity on the NuGet website, and fetched by a similarly named command line tool.

Each VisualStudio project lists its NuGet dependencies in the packages.config file, which is in same folder as the project's .csproj file.  E.g.
Administrator@cc-svr10 ~/github/cshv3/plugins/hypervisors/hyperv/DotNet/ServerResource
$ cat ./HypervResource/packages.config
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="AWSSDK" version="1.5.23.0" targetFramework="net45" />
  <package id="DotNetZip" version="1.9.1.8" targetFramework="net45" />
  <package id="log4net" version="2.0.0" targetFramework="net45" />
  <package id="Newtonsoft.Json" version="4.5.11" targetFramework="net45" />
</packages>

By default, your Visual Studio project will search for these assemblies in the packages directory in the folder containing the .sln file.  E.g.
$ cat ./HypervResource/HypervResource.csproj
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
  <ItemGroup>
    <Reference Include="AWSSDK">
      <HintPath>..\packages\AWSSDK.1.5.23.0\lib\AWSSDK.dll</HintPath>
    </Reference>
...
</Project>

Create a NuGet target

A NuGet target is a build script that downloads packages to the packages folder in advance of compile step of your build.  After this step, the compiler will be able to resolve the assembly dependencies.

Using a NuGet target to automate downloading is a better solution than adding assemblies to your source code assembly or creating a custom script for download (the .sln and .csproj files).  The NuGet target keeps package dependencies at arms length. NuGet carries the legal risk of redistributing other organisations' binaries.  Also, the NuGet target requires much less maintenance work.
Once setup, the NuGet target the list of files to download list the projects being compiled.

To create the target, update your .sln and .csproj files with the NuGet-related tags using Visual
Studio.  Simply open the .sln, and select Project -> Enable NuGet Package Restore.  This creates a .nuget folder with an msbuild task in the NuGet.targets file, its configuration in NuGet.Config, and the command line tool NuGet.exe.  E.g.
Administrator@cc-svr10 ~/github/cshv3/plugins/hypervisors/hyperv/DotNet/ServerResource
$ find ./ | grep NuGet
./.nuget/NuGet.Config
./.nuget/NuGet.exe
./.nuget/NuGet.targets

You will want to add the build script (NuGet.targets) and its configuration (NuGet.Config) to your source code repository.  NuGet.exe should be downloaded separately, as we will explain next.

Update the NuGet target to run with XBuild

Although Mono's Xbuild toll will execute NuGet.targets automatically, Xbuild does not support enough MSBuild tasks to allow it to executed the default NuGet.targets script.

First, you will have to create a script to download NuGet.exe, because Xbuild does not yet support the embedded Task tag (Source).  E.g. if wget is installed, you could use the following instructions:
wget http://nuget.org/nuget.exe
cp nuget.exe ./.nuget/NuGet.exe
chmod a+x ./.nuget/NuGet.exe
Note the change of case.  The code generated NuGet.target expects NuGet.exe

Secondly, if you plan to build on a Windows OS, you will have to update NuGet.targets
to bypass unsupported tags.  Normally, NuGet.targets uses the OS property to bypass unsupported tags automatically.  However, this only works when Mono is used on Linux.  E.g. in the example below an unsupported property function is avoided when the OS is not "Window_NT".
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT'">
    <!-- Windows specific commands -->
    <NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath>
    <PackagesConfig>$([System.IO.Path]::Combine($(ProjectDir), "packages.config"))</PackagesConfig>
</PropertyGroup>

<PropertyGroup Condition=" '$(OS)' != 'Windows_NT'">
    <!-- We need to launch nuget.exe with the mono command if we're not on windows -->
    <NuGetToolsPath>$(SolutionDir).nuget</NuGetToolsPath>
    <PackagesConfig>packages.config</PackagesConfig>
</PropertyGroup>

To be able to build on Windows, you can either delete the Windows version or revise the conditionals to use a custom property to determine if xbuild is being used.  E.g. in the example below, we
bypass the unsupported function property using a test for the property BuildWithMono.
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT'">
    <!-- Windows specific commands -->
    <!-- <NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath> -->
    <NuGetToolsPath>$(SolutionDir).nuget</NuGetToolsPath>
    <!--<PackagesConfig>$([System.IO.Path]::Combine($(ProjectDir), "packages.config"))</PackagesConfig> -->
    <PackagesConfig>packages.config</PackagesConfig>
</PropertyGroup>

<PropertyGroup Condition=" '$(OS)' != 'Windows_NT'">
    <!-- We need to launch nuget.exe with the mono command if we're not on windows -->
    <NuGetToolsPath>$(SolutionDir).nuget</NuGetToolsPath>
    <PackagesConfig>packages.config</PackagesConfig>
</PropertyGroup>

To trigger the bypass, we set buildWithMono when calling Xbuild.  E.g.
xbuild /p:BuildWithMono="true" ServerResource.sln

What code needs bypassing?

  1. Mono Xbuild cannot interpret property functions.  These are properties whose values are results of executing an inline function call. E.g.
  2. <!-- Windows specific commands -->
    <NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath>
    <PackagesConfig>$([System.IO.Path]::Combine($(ProjectDir), "packages.config"))</PackagesConfig>
  3. Mono does not implement all tasks precisely.  E.g. the Exec tag is missing the LogStandardErrorAsError property available with .NET.  Thus,
    <Exec Command="$(RestoreCommand)"
          LogStandardErrorAsError="true"
          Condition="'$(OS)' == 'Windows_NT' And Exists('$(PackagesConfig)')" />
    causes
    Error executing task Exec: Task does not have property "LogStandardErrorAsError" defined
  4. Xbuild must call NuGet.exe through mono.  E.g.
  5. <NuGetCommand Condition=" '$(OS)' == 'Windows_NT'">"$(NuGetExePath)"</NuGetCommand>
    <NuGetCommand Condition=" '$(OS)' != 'Windows_NT' ">mono --runtime=v4.0.30319 $(NuGetExePath)</NuGetCommand>

Skip unsupported projects

Xbuild cannot compile projects that require additional proprietary assemblies not available through NuGet or the Mono implementation.  For example, unit tests created using Visual Studio Test Tools make use of Visual Studio-specific assemblies.   E.g.
xbuild ServerResource.sln
...
HypervResourceControllerTest.cs(18,17): error CS0234: The type or namespace name `VisualStudio' does not exist in the namespace `Microsoft'. Are you missing an assembly reference?
In this case, HypervResourceControllerTest.cs(18,17) is a reference to Visual Studio test tools:
using Microsoft.VisualStudio.TestTools.UnitTesting;
To create new configuration in you solution that skips compiling these projects follow these steps:
  1. In Visual Studio, create a new configuration that excludes the projects you're not interested in. -(Build -> Configuration Manager..., select on the Active solution platform: drop down list)
  2. Using the Configuration Manager, remove unwanted solutions from the configuration.
  3. Next, close VisualStudio, which will save changes to the .sln and .csproj The .sln will record which projects are associated with the configuration. The .csproj will record the settings of the configuration, such as whether TRACE or DEBUG is defined.
  4. Finally, when calling xbuild assign your configuration's name to the Configuration property.

E.g.
xbuild /p:Configuration="NoUnitTests" /p:BuildWithMono="true" ServerResource.sln
The above will build the projects associated with the NoUnitTests configuration. Source

Final Remarks:

Mono's support keeps getting better with every release.  The quirks discussed in this post may have been addressed by the time you read it.


Tuesday, July 09, 2013

Installing Hyper-V PV Drivers for Linux Guests

Background 


PV, or paravirtualization improves VM performance bypassing hardware emulation where the emulator would introduce significant and unnecessary overhead.  A good example is a VMs networking stack, which exists twice.  Once in the VM, and again in the parent operating system running on the hypervisor.

PV drivers allow a guest O/S to take advantage of optimisations offered by PV.  Using drivers is an easy way to allow the kernel to take advantage of PV facilities without requiring that the kernel by recompiled with hypervisor PV APIs in mind.  E.g. externally, the networking PV driver can be used by an unchanged kernel, and internally it is a thin wrapper over the hypervisor's networking API.


Problem


Turning to Hyper-V, it's PV drivers and accompanying utils are referred to as "Linux Integration Services" aka LIS.  The latest versions are available for download.  This gives you an ISO for RPMs for supported Linux distributions.

The difficulty for me is that the ISO contains only RPMs, and they target the older linux kernel typical of even recent versions of RHEL.  LIS is not compatible with CloudStack's internal services that are implemented by VMs running Debian Wheezy...

# uname -r
3.2.0-4-686-pae

# cat /etc/*-release
Cloudstack Release 4.2.0 Fri Jul  5 04:16:17 UTC 2013
PRETTY_NAME="Debian GNU/Linux 7.0 (wheezy)"
NAME="Debian GNU/Linux"
VERSION_ID="7.0"
VERSION="7.0 (wheezy)"
ID=debian
ANSI_COLOR="1;31"
HOME_URL="http://www.debian.org/"
SUPPORT_URL="http://www.debian.org/support/"
BUG_REPORT_URL="http://bugs.debian.org/"

Solution


Fortunately, Debian Wheezy was compiled with the Hyper-V drivers.  You can verify the presence of the kernel modules using using modinfo  E.g.

# modinfo -F filename hid-hyperv hv_storvsc hv_netvsc hv_vmbus hv_utils
/lib/modules/3.2.0-4-686-pae/kernel/drivers/hid/hid-hyperv.ko
/lib/modules/3.2.0-4-686-pae/kernel/drivers/scsi/hv_storvsc.ko
/lib/modules/3.2.0-4-686-pae/kernel/drivers/net/hyperv/hv_netvsc.ko
/lib/modules/3.2.0-4-686-pae/kernel/drivers/hv/hv_vmbus.ko
/lib/modules/3.2.0-4-686-pae/kernel/drivers/hv/hv_utils.ko

NB:  As of linux kernel 3.2, hv_timesource is no longer an independent kernel module (source).

However I still need the user mode daemons that make up LIS.  

Hyper-V Key Value Pair (KVP) Component


The KVP component that is responsible for transferring data in the form of key value pairs between the host and the guest O/S.  It also performs other tasks.

Using rpm2cpio, you can extract the daemon and service controller from the RPM.  rpm2cpio returns the .cpio, which a. tar, is a concatenation of files.    This guide explains how to get the actual contents.  E.g. 

# rpm2cpio microsoft-hyper-v-rhel63.3.4-1.20120727.i686.rpm | cpio -idmv
./etc/init.d/hv_kvp_daemon
./etc/modprobe.d/hyperv_pvdrivers.conf
./usr/sbin/hv_kvp_daemon
28 blocks


Final Remarks


The file ./etc/init.d/hv_kvp_daemon provides only an example of how to start the daemon, but it cannot be used directly.  The  script is not tailored to Debian, because it uses distro-specific methods from /etc/init.d/functions such as daemonDebian offers equivalent functionality in the start-stop-daemon function (source).

Finally, the licensing for the binary fo daemon built by MSFT is ambiguous.  Unlike the drivers, which modinfo lists as GPL, the binary makes no statement on its terms of licensing.