Virtual Server 2005
Program Customized Testing Environments Without Trashing Your Machine
Ben Waldron
This article discusses:
|
This article uses the following technologies: C#, Virtual Server, COM |
Code download available at:VirtualServer2005.exe(677 KB)
Contents
Introducing Virtual Server 2005
Using the COM API
Application Testing Scenario
Design of the Rapid Test Application
Virtual Server .NET Wrapper
Rapid Test Windows Form Application
VMRC ActiveX Control
Conclusion
Often when I walk by the desk of a software developer or tester I notice the arsenal of hardware used to test applications on different operating system versions or configurations. Testing in different configurations is an integral part of the development process, but the time and energy consumed configuring and managing these environments can be taxing. This ultimately comes at the expense of application quality because less time is spent testing while more time is spent building or reconfiguring machines. Microsoft® Virtual Server 2005 improves developer productivity by simplifying the provisioning and management of testing environments. This article demonstrates how the extensive Virtual Server COM API can be used to create an automated application testing environment.
In the context of this article, a virtual machine can be best described as an isolated computer implemented entirely in software, running inside a host hardware-based computer. This makes it possible to have multiple operating systems running simultaneously on a single machine. Windows® 95 and above and even the next version of Windows, code-named "Longhorn," can run concurrently on a single piece of hardware using Virtual Server. Non-Microsoft operating systems can also run inside a virtual machine. Applications running in a virtual machine behave as if they were running on their own physical system. The virtual machine concept has been around for more than 30 years, but until the advent of products like Virtual Server 2005 it has not been an affordable or manageable solution for application developers and testers.
Introducing Virtual Server 2005
Microsoft offers two products that employ virtual machine technology: Virtual PC 2004 and Virtual Server 2005. Virtual Server builds upon Virtual PC, adding improved resource utilization and an extensive API to automate deployment and management. The API is most significant for developers and is largely the subject of this article, but before looking at the API I'll take a moment to describe the architecture of Virtual Server 2005.
Figure 1 shows a high-level architecture of Virtual Server. Virtual machines are isolated from the host operating system by running atop a virtualization layer that emulates industry-standard hardware. The host, running Windows Server™ 2003, creates the "Virtual Service" Windows service responsible for isolating and managing virtual machines as part of the Virtual Server installation. It also houses the COM interface, event logging, resource management, and performance counters. IIS plays an important role because it acts as the host for the Virtual Server Web application—the primary interface for managing Virtual Server and its virtual machines.
Figure 1** Architecture **
In addition, the Virtual Service runs the Virtual Machine Remote Control (VMRC) server, which clients access in order to control virtual machines remotely. VMRC is also the name of the protocol used by the server to talk to client applications. The VMRC protocol is an extended form of the standard Virtual Network Computing (VNC) protocol. It uses an enhanced secure version of the Remote Frame Buffer (RFB) interface.
Users access virtual machines by using the rich VMRC client or Web browser via the VMRC ActiveX® control. If a virtual machine's configuration is such that it can be accessed directly from the network, then it is possible to use Remote Desktop Protocol (RDP) to connect to it and to control it remotely.
Virtual machines themselves can be thought of as traditional workstations or servers. They have the common peripherals that you would expect in a modern hardware configuration with the exception of USB and FireWire devices as well as PCI cards. Virtual machines can be configured to run multiple hard drives (IDE or SCSI), DVD-ROMs, network adapters, and other devices. Although the virtual machines see them as normal devices, hard disks are actually files on the host system and other devices are created virtually in the software by the Virtual Service.
Although I'll discuss the basic tools you'll need in order to develop for Virtual Server, you can find more information on the Virtual Server Architecture at Microsoft Virtual Server 2005.
Using the COM API
As you may have noticed, there has been a move towards products that are designed with customization in mind and away from shipping applications with limited programmatic access. Microsoft products like Office have full APIs that allow developers to do anything that can be done in the core product and provide many additional options. This shift is great news because it means less time searching for workarounds and more time developing solutions.
Virtual Server uses a COM API that can be accessed from the Windows scripting environment and from applications written in managed .NET code, Visual Basic® 6.0, or unmanaged C++. I'll use C# to build a Windows Forms app that shows the core functions of the API. The COM interface is contained within the Virtual Service (vssvrc.exe), and it supports DCOM to enable remote programmatic access. The best reference for the API is in the documentation delivered as part of the installation.
The most important interfaces and classes in the COM API are IVMVirtualServer and IVMVirtualMachine and their respective coclasses VMVirtualServer and VMVirtualMachine. All other classes, events, and enumerations support these two interfaces. The VMVirtualServer is the core of the API and its functionality can be summed up by the functions listed in Figure 2. Once virtual machines are provisioned (using a VMVirtualServer), they can be managed using instances of the VMVirtualMachine class, also described in Figure 2.
figure 2 VMVirtualServer and VMVirtualMachine
VMVirtualServer Functions | Description |
---|---|
Retrieve host information | Returns information about the host machine running Virtual Server. Host information includes operating system version, processor speed, and available memory to assist in sizing and scaling. |
Provision virtual machines | Able to create, delete, find, or register virtual machines. |
Manage peripherals | Create, configure, and remove peripherals such as hard disks, network adapters, or DVD-ROM drives. |
Security | Configure access rights through the COM API to virtual machines. |
VMRC | Manages VMRC service. Administrators can enable or disable VMRC and access properties such as encryption type, VMRC port, and screen resolution. |
VMVirtualMachine Functions | Description |
Peripheral management | Add or remove hard disks, network adapters, floppy drives, and other peripherals. |
Virtual machine actions | Able to start, shut down, save, reset, pause, and resume virtual machines. |
Virtual machine information | Manage machine properties such as BIOS, base board, and chassis. |
Once virtual machines are provisioned using the VMVirtualServer class, the VMVirtualMachine class is used to manage virtual machines. The VMVirtualMachine class performs the functions that are described at the bottom of Figure 2.
Application Testing Scenario
As mentioned earlier, one of the most important capabilities of Virtual Server is that it allows a developer or tester to deploy and test applications quickly in a variety of operating systems and configurations. After developing Windows Forms applications and performing unit testing, I find myself creating installation packages that will need to be installed on many different versions of Windows and on a variety of service pack levels. This is the part of the development cycle that I like least because it is less creative and sometimes tedious, though still important. I invariably end up having to image machines over and over until I am comfortable with the installation package and application.
Fortunately, there is one feature of Virtual Server that saves a lot of time in this scenario—undoable disks. This feature allows any changes made to the virtual machine to remain uncommitted so that when the virtual machine is stopped, it will not persist any transactions to the disk. This means that if you encounter an error, you can simply roll back the disk without having to completely re-image the virtual machine.
I've created an application called Rapid Test to automate the management of virtual machines and to quickly and easily deploy new applications for testing. The goal of the Rapid Test application is to take a newly developed piece of software and run it quickly in many different environments. Imagine taking a setup program that you just created and installing it on four or five operating systems in less than five minutes. This is the main task that I built Rapid Test to perform.
Rapid Test enables you to create new virtual machines from an existing virtual hard disk or from a new build image that has been encapsulated in a virtual disk. An existing virtual hard disk is a file on the host system that has been preconfigured within Virtual Server or Virtual PC and which can also be attached to and used in a new system. The build image approach takes a fresh copy of a configuration that has been constructed using deployment automation techniques like SysPrep or Windows Automated Deployment Services. With either approach, it is possible to create a new virtual machine in just a matter of minutes.
The other important ability of Rapid Test is that it lets you deploy applications to virtual machines quickly. If virtual machines have network adapters configured for client access, application deployment can be performed over the network. I chose not to assume that the network was configured, so I took a different approach to deploying applications. Instead of deploying over the network, the user selects a directory to deploy to the virtual machines and an ISO DVD-ROM image is created and copied to the host server. Virtual machine DVD-ROM devices can be bound to either a physical DVD-ROM drive on the host or an ISO file image that mimics DVD-ROM media. This way of deploying the app is analogous to a user installing an application from a CD or DVD.
When the virtual machine is started, it recognizes that it needs to bind one of its DVD-ROM drives to the ISO image. When the machine boots or comes out of a saved state, the DVD-ROM is recognized and the application is available to install and test. Using an autorun.inf file within the ISO image can eliminate the need to run the app manually; the installation will start automatically.
Design of the Rapid Test Application
There are two main components that comprise the Rapid Test application: the Windows Forms application and my Virtual Server .NET wrapper classes. The high-level design of the application is shown in Figure 3.
Figure 3** Rapid Test Architecture **
The Virtual Server wrapper exposes the VMVirtualServer and VMVirtualMachine COM classes in .NET managed code. It has two purposes. First, it allows easier consumption from managed applications by abstracting the COM interface. Second, it allows other applications to take advantage of the library or extend it. For example, a great enhancement to the solution would be to develop a Web service layer that consumes the wrapper. The wrapper does not expose all the functionality contained within the COM API, but it does expose the core properties, methods, and events needed for the Rapid Test solution.
Virtual Server .NET Wrapper
All classes within the Virtual Server .NET wrapper use a naming convention that starts with the letters VSW. The wrapper class for the VMVirtualServer class is VSWVirtualServer. The code in Figure 4 shows the constructors for the VMVirtualServer class. You can use the default constructor if you are connecting to a local virtual server while you can provide the name or address of a remote server to the other constructor. If you specify a remote server, DCOM is used to connect to the Virtual Server remotely.
figure 4 VSWVirtual Server Constructors
/// <summary> /// Create a Virtual Server object connected to the local server /// </summary> public VSWVirtualServer() { virtualServerCOM = new VMVirtualServerClass(); } /// <summary> /// Create a Virtual Server object connected to a remote server /// </summary> /// <param name="server">Name or address of remote server</param> public VSWVirtualServer(string server) { Type VMVirtualServerClassType = typeof(VMVirtualServerClass); // create remote type from class identifier Type DCOMType = Type.GetTypeFromCLSID(VMVirtualServerClassType.GUID, server, true); object DCOMObject = Activator.CreateInstance(DCOMType); // create local object from remote object virtualServerCOM = (VMVirtualServerClass) Marshal.CreateWrapperOfType(DCOMObject, VMVirtualServerClassType); }
The VSWVirtualServer class exposes methods for creating and removing virtual machines. The creation of a new virtual machine requires you to use the COM API method CreateVirtualMachine (see Figure 5) and pass it the name of the new virtual machine and the location to create configuration files. Once it is created, the wrapper adds a hard disk, DVD-ROM, and then configures the amount of memory.
figure 5 Creating a New Virtual Machine
/// <summary> /// Create a new virtual machine /// </summary> /// <param name="virtualMachineName">Name of Virtual Machine to /// create</param> /// <param name="hardDrivePath">Path to hard disk</param> /// <param name="amountMemory">Amount of memory</param> /// <returns>.NET wrapped Virtual Machine</returns> public VSWVirtualMachine CreateVirtualMachine(string virtualMachineName, string hardDrivePath, int amountMemory) { VMVirtualMachine virtualMachineCOM = virtualServerCOM.CreateVirtualMachine(virtualMachineName, virtualServerCOM.DefaultVMConfigurationPath); // add the initial hard drive - assumes IDE (bus 1, device 0) virtualMachineCOM.AddHardDiskConnection(hardDrivePath, VMDriveBusType.vmDriveBusType_IDE, 1, 0); // add a DVD drive - DVD always IDE (bus 1, device 1) virtualMachineCOM.AddDVDROMDrive(VMDriveBusType.vmDriveBusType_IDE, 1, 1); virtualMachineCOM.Memory = amountMemory; VSWVirtualMachine vswVirtualMachine = new VSWVirtualMachine(virtualMachineCOM); vmCollection.Add(vswVirtualMachine.Name, vswVirtualMachine); return vswVirtualMachine; }
Deleting a virtual machine is easier than creating one. The COM API DeleteVirtualMachine method simply takes a VMVirtualMachine object and deletes it. For some added value, the wrapper ensures that the virtual machine is in the proper state to be removed. If it is in a state in which it cannot be removed, for instance, if it is paused or running, the method will throw an exception detailing why it could not be removed at that particular time, as shown in Figure 6.
figure 6 Removing a Virtual Machine
/// <summary> /// Delete Virtual Machine /// </summary> /// <param name="virtualMachine">Virtual Machine to remove</param> /// <exception cref="VSWVirtualMachineException">Thrown if Virtual Machine /// cannot be removed in present state</exception> public void RemoveVirtualMachine(VSWVirtualMachine vswVirtualMachine) { if (vswVirtualMachine.COMVirtualMachine.State == VMVMState.vmVMState_Paused || vswVirtualMachine.COMVirtualMachine.State == VMVMState.vmVMState_Running) { throw new VSWVirtualMachineException( "Cannot Remove Virtual Machine while in a " + vswVirtualMachine.State + " state."); } COMVirtualServer.DeleteVirtualMachine( vswVirtualMachine.COMVirtualMachine); vmCollection.Remove(vswVirtualMachine.Name); }
The VSWVirtualServer class contains a Hashtable of .NET-wrapped virtual machine objects. This property allows clients to quickly gain access to the virtual machines registered to the Virtual Server (see Figure 7). Each time the property is accessed, the COM API VirtualMachines property is used to return a collection of the virtual machines. If the virtual machine does not exist in the Hashtable, then a .NET virtual machine object is created and added to the Hashtable. Likewise, if the .NET virtual machine object has been deleted, it will be removed from the list.
figure 7 Virtual Machine Collection
/// <summary> /// Hashtable of Virtual Machine where key is virtual machine name /// </summary> public Hashtable VMCollection { get { StringCollection virtualServerVMs = new StringCollection(); // add dictionaries that are not already in hashtable foreach ( VMVirtualMachine virtualMachineCOM in virtualServerCOM.VirtualMachines ) { virtualServerVMs.Add(virtualMachineCOM.Name); if (!vmCollection.ContainsKey(virtualMachineCOM.Name)) { vmCollection.Add(virtualMachineCOM.Name, new VSWVirtualMachine(virtualMachineCOM)); } } // remove virtual machines that no longer exist from hashtable foreach (string vmName in vmCollection.Keys) { if (!virtualServerVMs.Contains(vmName)) { vmCollection.Remove(vmName); } } return vmCollection; } }
The VSWVirtualMachine class provides the .NET wrapper for the COM VMVirtualMachine class. The class is responsible for attaching virtual devices to the virtual machine and controlling state and actions. It also exposes events so that a consuming client can react when virtual machines change state such as changing from "running" to "saved." The most interesting method of this class that I use for the Rapid Test application is the AttachISO method because it is responsible for binding the application to test to the DVD-ROM. The AttachISO method is shown in Figure 8.
figure 8 Attach an ISO Image to a Virtual Machine
/// <summary> /// Attach an ISO Image of DVDROM to Virtual Machine /// </summary> /// <param name="ISOImagePath">Path to ISO Image to attach</param> /// <exception cref="VSWVirtualMachineException">Thrown if Virtual /// Machine /// ISO could not be attached</exception> public void AttachISO( string ISOImagePath ) { try { // attach to first DVDROM if (virtualMachineCOM.DVDROMDrives.Count == 0) throw new VSWVirtualMachineException( "Virtual Server: Cannot attach ISO because there are no DVD Drives" ); VMDVDDrive dvdDrive = null; foreach (VMDVDDrive drive in virtualMachineCOM.DVDROMDrives) { dvdDrive = drive; break; } if ( dvdDrive.Attachment == VMDVDDriveAttachmentType.vmDVDDrive_HostDrive ) { dvdDrive.ReleaseHostDrive(); } else if ( dvdDrive.Attachment == VMDVDDriveAttachmentType.vmDVDDrive_Image ) { dvdDrive.ReleaseImage(); } dvdDrive.AttachImage( ISOImagePath ); } catch(Exception Ex) { throw new VSWVirtualMachineException( "Unable to attach ISO Image", Ex); } }
Rapid Test Windows Form Application
Rapid Test is a Windows Forms application that uses three forms: the main status form, the form to create new virtual machines, and the form that allows remote control of the virtual machines.
Before getting too far into the application, I should talk a little bit about COM security. Ultimately, the Windows Forms application accesses the Virtual Server API, which is a standard COM object. Before any Virtual Server interface is accessed, the COM interface layer is initialized. By default, COM uses the Identification security level, which is lower than the Impersonation security level required as a minimum by Virtual Server. This means that you have to specify the security level explicitly by calling the Win32® API methods CoInitializeEx and CoInitializeSecurity through P/Invoke before doing anything else in the application. Likewise, at application shutdown, you must also call the CoUninitialize method. All of these methods are contained in Ole32.dll. The code in Figure 9 shows how to set the proper security level to access the Virtual Server API. The main form in the Rapid Test application initializes and uninitializes COM security.
figure 9 COM Security Initialization
/// <summary> /// Initialize COM Security settings with threading option /// </summary> /// <param name="threadType"> /// Desired threading model /// </param> public static void Initialize(APARTMENTTYPE threadType) { uint coinit = COINIT_APARTMENTTHREADED; // if Multi Threaded Apartment (MTA) if (threadType == APARTMENTTYPE.Multi) coinit = COINIT_MULTITHREADED; // Initializes the COM library for use by the calling thread CoInitializeEx(IntPtr.Zero, coinit); // use SecurityImpersonation CoInitializeSecurity(IntPtr.Zero, 0, IntPtr.Zero, IntPtr.Zero, RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_IMPERSONATE, IntPtr.Zero, 0, IntPtr.Zero ); }
Users are able to create new virtual machines quickly and easily by selecting the "Create New VM" option in the file menu. The form has a simple UI for selecting the type of virtual machine to create and the amount of memory to use (see Figure 10). Virtual Server recommends the amount of memory to use for virtual machines through the SuggestedMaximumMemoryPerVM property, but the user can choose to ignore the recommendation. After the form makes the proper validation checks, a single call to the CreateVirtualMachine method in the VSWVirtualServer class is made to create the virtual machine.
Figure 10** Create New Virtual Machine **
The Rapid Test main form calls the VSWVirtualServer class to retrieve all of the virtual machines and lists them in the form. Users can also see how long the virtual machine has been running and the average CPU usage. These properties are available for the VMVirtualMachine class in the API. I assumed that users will want to see this type of information on a regular basis, so I implemented a simple timer that is user-configurable and that will regularly refresh the list of virtual machines.
With the list of virtual machines, users are able to perform actions such as start, remove, and save using a context menu. Below the list of virtual machines, a user can select the directory from which an ISO image should be created. If the user chooses to attach an ISO image, the directory is made into the ISO image using a command-line utility called mkisofs.exe that is included in the code download at the link at the top of this article. The CreateISOFile method, shown in Figure 11, in the Utility class is used to automate the creation of the ISO images.
figure 11 Create ISO Image
/// <summary> /// Creates an ISO Image for a given directory /// </summary> /// <param name="ISOFileName"></param> /// <param name="directoryToCreate"></param> /// <returns>name of resulting ISO file</returns> public static string CreateISOFile(string ISOFileName, string directoryToCreate) { //create a unique name for the ISO file string ISOName = ISOFileName + ".iso"; // create the ISO image from the selected directory Process process = new Process(); process.StartInfo.FileName = "mkisofs.exe"; // make joliet file system and call the image "RapidTest" process.StartInfo.Arguments = String.Format("-joliet -volid {0} -o \"{1}\" \"{2}\"", "RapidTest", ISOName, directoryToCreate); process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; process.Start(); process.WaitForExit(); return ISOName; }
The process to connect to the virtual machine is fairly simple, but there is a lot going on under the covers. For example, let's say that I have a virtual machine that is currently off. When I go to the virtual machines list and right-click on the Options context menu, I am presented with various choices. If I select the option to turn on the virtual machine, the code will first check to see if I chose to attach an ISO image to the virtual machine. Since I am testing an application, I have chosen to deploy the c:\MyApplications directory. The ISO image is created from the chosen directory and then copied to a location to which the Virtual Server has access. The location is configurable in the application's configuration file.
Once the file is successfully copied, a new form is created that is responsible for controlling the virtual machine remotely. The constructor for the class that creates this form takes a VSWVirtualMachine class as a parameter. This lets the form register for events and track the status of the virtual machine. Keep in mind that the virtual machine has not started yet. The StartVirtualMachine method of the Virtual Machine Display form is overloaded to accept either one or zero parameters. The overload that takes a string parameter then represents the path to an ISO image. If this path is truly a path to an ISO image, the image is attached right before starting the virtual machine. Once all of this has completed, the virtual machine starts and is ready to be controlled remotely.
VMRC ActiveX Control
The Virtual Machine Display form uses the VMRC ActiveX control to enable the user to control the application remotely. The VMRC ActiveX control has many properties and methods that make it ripe for automation. I will touch on some of the key properties, methods, and events that are used to connect to and display a virtual machine remotely.
There are three key pieces of information that are required for the VMRC ActiveX control to connect to a virtual machine. The name or IP address of the Virtual Server is set with the ServerAddress property and the port number of the VRMC is set with the ServerPort property. This is all the information needed to connect to the admin display. The admin display lists all of the virtual machines running on the Virtual Server and allows the user to select which one he wants to connect to. You may turn off the admin display capability in the ActiveX control.
The default port running VMRC is 5900, but it can be changed using the Virtual Server administrative options. You need the name of the virtual machine to connect directly to it, and this is set using the ServerDisplayName property. Once this information is set, calling the Connect method will establish the connection. I chose to subscribe to the events that the control exposes to let me know when the state of the connection changes.
You will notice in the code in Figure 12 that I use a class called VMTask when starting the virtual machine. This class enables you to track the status of long-running tasks using a status bar or any other user notification mechanism. This class comes in handy when virtual machines are coming out of a saved state. Depending on how much memory is allocated to a virtual machine, it could take a while for it to come out of hibernation. The VMTask is used to display how much longer it will take until the virtual machine is ready. Once the task is completed, the connection to the virtual machine is established.
figure 12 Connect to a Virtual Machine from the VMRC
public void StartVirtualMachine() { vm.vmStateChange +=new VSWVirtualMachineEventHandler(vm_vmStateChange); if (vm.State != "Running") { VMTask task = vm.COMVirtualMachine.Startup(); UpdateStatusBar(task); } axVMRCClientControl1.ServerAddress = virtualServerAddress axVMRCClientControl1.ServerPort = virtualServerPort axVMRCClientControl1.ServerDisplayName = vm.Name; axVMRCClientControl1.OnStateChanged += new OnStateChangedEventHandler(axVMRCClientControl1_OnConnectingStatus); axVMRCClientControl1.Connect(); axVMRCClientControl1.Visible = true; }
Conclusion
With relative ease, I have created a Windows Forms application that takes advantage of the extensive Virtual Server COM API to create a virtual machine test environment. I can create virtual machines from scratch and use them for testing in a matter of minutes. I can even deploy applications to those virtual machines by automatically creating and attaching an ISO image. I can do this without ever configuring the network on the virtual machine. This application saves development and testing time by quickly provisioning new machines to test.
Virtual Server 2005 is a great product for app developers and testers. It converts a single server into a virtual test lab to test apps on a variety of operating systems and configurations. The rich COM API and VMRC controls allow developers to automate deployments quickly and to test scenarios in a multitude of ways.
Ben Waldron Ben Waldron is a founding partner at Pollinate Media, an Interactive Branding and Innovation Company located in Portland, Oregon. Ben can be contacted for questions or opportunities via e-mail: ben.waldron@pollinatemedia.com.