In this series of post’s let’s start to explorer the Citrix XenServer SDK and how we can start to use it in different types of applications to build interesting solutions.
So, Let’s set the stage
.
To start delving into the SDK, lets create a SharePoint webpart that will list all virtual machines on a given XenServer and allow the user to stop, start and pause each virtual machine through the webpart UI.
Here is a screenshot of what the part will look like when finished. You can also get the WSP file below if you want to install it into your SharePoint rig to play around with it. Note that this just a prototype app, but I intend on extending it to additional functionality. If you want to download the SharePoint solution file you can get it here.
In order to make this web part more re-usable we are going to have to be able to set some properties specific to our environment. We can do that enabling custom properties on the webpart. Below is a screen shot of the properties window of the web part. This allows you to add a XenServer and the XenServer information so the webpart can be used with multiple XenServers.
So, in order to build this webpart we are going to use a couple of different technologies.
1) jQuery (This will allow us to make some ajax calls to a wcf service that return XenServer information.
2) WCF (Windows Communication Foundation). This is a really simple WCF get enabled service to return different XenServer information.
3) SharePoint WebPart Development. (I wont get to detailed in this, but we will be using WSPBuilder to package up the SharePoint solution and a feature receiver to create some needed files upon activation of the web part.
WCF Implementation
While we could strictly put all the XenServer SDK code directly in the web part, this would make it more difficult for perform ajax based query’s and make the interface much more post back based. To eliminate this, we are going to implement the XenServer API in a service layer (Seperate DLL), and then package that up in our solution deployment. Remember this is a sample app, so the service implementation is pretty bare bones (Something I plan on adding to). Side Note: In order to get sharepoint to host WCF services I have included in the solution a DLL that is a virtual path provide to help with the hosting.
As with any WCF implementation, the first this we need to define is the interface for implementation. In this simple implementation all method will be REST aware and will return either a string or an XML response.
There are four main methods here that are of interest
1) GetVirtualMachines – This method will return a list of VirtualMachine objects and will accept parameters for the host, port, username and password
2) StopVM – This method returns a Boolean stating weather or not the call was sucessful and will accept parameters for the host, port, username, password, and VM UUID.
3) StartVM – This method returns a Boolean stating weather or not the call was sucessful and will accept parameters for the host, port, username, password, and VM UUID.
4) PauseVM – This method returns a Boolean stating weather or not the call was sucessful and will accept parameters for the host, port, username, password, and VM UUID.
The definition of the Interface is below.
1: [ServiceContract]
2: public interface IXenServer
3: {
4: [OperationContract]
5: [WebGet(ResponseFormat = WebMessageFormat.Xml)]
6: string ServiceStatus();
7: [OperationContract]
8: [WebGet(ResponseFormat = WebMessageFormat.Xml)]
9: List<Classes.VirtualMachine> GetVirtualMachines(string host, string port, string username, string password);
10: [OperationContract]
11: [WebGet(ResponseFormat = WebMessageFormat.Xml)]
12: bool StopVM(string host, string port, string username, string password, string XenServerVMUUID);
13: [OperationContract]
14: [WebGet(ResponseFormat = WebMessageFormat.Xml)]
15: bool StartVM(string host, string port, string username, string password, string XenServerVMUUID);
16: [OperationContract]
17: [WebGet(ResponseFormat = WebMessageFormat.Xml)]
18: bool PauseVM(string host, string port, string username, string password, string XenServerVMUUID);
19: [OperationContract]
20: [WebGet(ResponseFormat = WebMessageFormat.Xml)]
21: string GetVMStatus(string host, string port, string username, string password, string XenServerVMUUID);
22:
23: }
Now, in our Interface definition method GetVirtualMachine we specify that this method will return a list of VirtualMachine objects. This virtual machine object is simply a class that we need to define that has some properties on it, specifically a Name property, a description property, a UUID property and a Status property. The class definition with the properties is defined below. Remember since this is a WCF return class, we need to decorate the class with the datacontract attribute and for each property we want returned, they need to be decorated with the DataMember attribute.
1: [DataContract]
2: public class VirtualMachine
3: {
4: [DataMember]
5: public string Name { get; set; }
6: [DataMember]
7: public string Description { get; set; }
8: [DataMember]
9: public string UUID { get; set; }
10: [DataMember]
11: public string Status { get; set; }
12: }
So now we have our Interface definition and our return class definition, now we can implement our class that adheres to the Interface definition.
1: public class XenServer : IXenServer
2: {
3: internal XenAPI.Session GetSession(string host, int port, string username, string password)
4: {
5: try
6: {
7: XenAPI.Session _session = new XenAPI.Session(host, port);
8: _session.login_with_password(username, password);
9:
10: return _session;
11: }
12: catch (System.Exception loginError)
13: {
14: return null;
15: }
16: }
17: internal bool SessionLogout(XenAPI.Session Session)
18: {
19: Session.logout();
20: return true;
21: }
22:
23: public List<Classes.VirtualMachine> GetVirtualMachines(string host, string port, string username, string password)
24: {
25: System.Collections.Generic.List<Classes.VirtualMachine> _returnVirtualMachines = new System.Collections.Generic.List<Classes.VirtualMachine>();
26:
27: XenAPI.Session _session = this.GetSession(host, Convert.ToInt32(port), username, password);
28:
29: System.Collections.Generic.List<XenAPI.XenRef<XenAPI.VM>> _allVMs = XenAPI.VM.get_all(_session);
30: foreach (XenAPI.XenRef<XenAPI.VM> _xenRefVm in _allVMs)
31: {
32: XenAPI.VM _vm = XenAPI.VM.get_record(_session, _xenRefVm);
33: if (!_vm.is_a_template && !_vm.is_control_domain)
34: {
35: Classes.VirtualMachine _newVm = new Classes.VirtualMachine();
36: _newVm.Name = _vm.name_label;
37: _newVm.Description = _vm.name_description;
38: _newVm.UUID = _vm.uuid;
39: switch (_vm.power_state)
40: {
41: case XenAPI.vm_power_state.Halted:
42: _newVm.Status = "Halted";
43: break;
44: case XenAPI.vm_power_state.Paused:
45: _newVm.Status = "Paused";
46: break;
47: case XenAPI.vm_power_state.Running:
48: _newVm.Status = "Running";
49: break;
50: case XenAPI.vm_power_state.Suspended:
51: _newVm.Status = "Suspended";
52: break;
53: case XenAPI.vm_power_state.Unknown:
54: _newVm.Status = "Unknown";
55: break;
56: }
57: _returnVirtualMachines.Add(_newVm);
58: _newVm = null;
59: }
60: }
61: this.SessionLogout(_session);
62: _session = null;
63: return _returnVirtualMachines;
64: }
65:
66: public bool StopVM(string host, string port, string username, string password, string XenServerVMUUID)
67: {
68: XenAPI.Session _session = this.GetSession(host, Convert.ToInt32(port), username, password);
69:
70: XenAPI.XenRef<XenAPI.VM> virtualMachine = XenAPI.VM.get_by_uuid(_session, XenServerVMUUID);
71: if (virtualMachine != null)
72: {
73: XenAPI.VM.hard_shutdown(_session, virtualMachine);
74: }
75:
76: this.SessionLogout(_session);
77: _session = null;
78:
79: return true;
80: }
81:
82: public bool StartVM(string host, string port, string username, string password, string XenServerVMUUID)
83: {
84: XenAPI.Session _session = this.GetSession(host, Convert.ToInt32(port), username, password);
85:
86: XenAPI.XenRef<XenAPI.VM> virtualMachine = XenAPI.VM.get_by_uuid(_session, XenServerVMUUID);
87: if (virtualMachine != null)
88: {
89: XenAPI.VM.start(_session, virtualMachine, false, true);
90: }
91:
92: this.SessionLogout(_session);
93: _session = null;
94:
95: return true;
96: }
97:
98: public bool PauseVM(string host, string port, string username, string password, string XenServerVMUUID)
99: {
100: XenAPI.Session _session = this.GetSession(host, Convert.ToInt32(port), username, password);
101:
102: XenAPI.XenRef<XenAPI.VM> virtualMachine = XenAPI.VM.get_by_uuid(_session, XenServerVMUUID);
103: if (virtualMachine != null)
104: {
105: XenAPI.VM.suspend(_session, virtualMachine);
106: }
107:
108: this.SessionLogout(_session);
109: _session = null;
110:
111: return true;
112: }
113:
114: public string GetVMStatus(string host, string port, string username, string password, string XenServerVMUUID)
115: {
116: string _tmpStatus = null;
117: XenAPI.Session _session = this.GetSession(host, Convert.ToInt32(port), username, password);
118:
119: XenAPI.XenRef<XenAPI.VM> virtualMachineObject = XenAPI.VM.get_by_uuid(_session, XenServerVMUUID);
120: if (virtualMachineObject != null)
121: {
122: XenAPI.VM virtualMachine = XenAPI.VM.get_record(_session, virtualMachineObject);
123: switch (virtualMachine.power_state)
124: {
125: case XenAPI.vm_power_state.Halted:
126: _tmpStatus = "Halted";
127: break;
128: case XenAPI.vm_power_state.Paused:
129: _tmpStatus = "Paused";
130: break;
131: case XenAPI.vm_power_state.Running:
132: _tmpStatus = "Running";
133: break;
134: case XenAPI.vm_power_state.Suspended:
135: _tmpStatus = "Suspended";
136: break;
137: case XenAPI.vm_power_state.Unknown:
138: _tmpStatus = "Unknown";
139: break;
140: }
141: }
142:
143: this.SessionLogout(_session);
144: _session = null;
145:
146: return _tmpStatus;
147: }
148:
149: public string ServiceStatus()
150: {
151: return "Running";
152: }
153:
154: }
Lets examine the GetVirtualMachinesmethod here start to dig into the XenServer API.
The first thing we need to do is to get a session to the XenServer server. Everything in Xen works on the session so we need to create/attach to one
The following line will do that (along with the internal function).
internal XenAPI.Session GetSession(string host, int port, string username, string password)
{
try
{
XenAPI.Session _session = new XenAPI.Session(host, port);
_session.login_with_password(username, password);
return _session;
}
catch (System.Exception loginError)
{
return null;
}
}
XenAPI.Session _session = this.GetSession(host, Convert.ToInt32(port), username, password);
System.Collections.Generic.List<XenAPI.XenRef<XenAPI.VM>> _allVMs = XenAPI.VM.get_all(_session);
foreach (XenAPI.XenRef<XenAPI.VM> _xenRefVm in _allVMs)
{
XenAPI.VM _vm = XenAPI.VM.get_record(_session, _xenRefVm);
if (!_vm.is_a_template && !_vm.is_control_domain)
{
Classes.VirtualMachine _newVm = new Classes.VirtualMachine();
_newVm.Name = _vm.name_label;
_newVm.Description = _vm.name_description;
_newVm.UUID = _vm.uuid;
switch (_vm.power_state)
{
case XenAPI.vm_power_state.Halted:
_newVm.Status = "Halted";
break;
case XenAPI.vm_power_state.Paused:
_newVm.Status = "Paused";
break;
case XenAPI.vm_power_state.Running:
_newVm.Status = "Running";
break;
case XenAPI.vm_power_state.Suspended:
_newVm.Status = "Suspended";
break;
case XenAPI.vm_power_state.Unknown:
_newVm.Status = "Unknown";
break;
}
_returnVirtualMachines.Add(_newVm);
_newVm = null;
}
}
JQuery Implementation:
Using JQuery in a SharePoint web part is pretty simple, well, simple with a few helper functions
. The basic jist here is you will have to inject the JQuery script tag into the sharepoint page that the webpart is on from the webpart code. Use can use this same method to include any JQuery plugin’s that you want to use in your webpart. In our example we are going to use the following JQuery files.
1) JQuery-1.3.2.js (Main JQuery file)
2) impromptu.js ( JQuery plugin that enables a login box to appear when clicking on the actions for each VM)
3) jquery.datatable.js (JQuery plugin that enables a databound datagrid)
4) XenServerVMManegment.js (WebPart JavaScript responsible for making all the AJAX calls to the SCF services, etc)
Once you have the file in your WSPBuilder project, you are going to have to write code to inject the script tags into the sharepoint page where the part lives on I use two helper functions that allow me to add both JS files and CSS files dynamically from code. They are as follows.
Helper function to add JavaScript files to the SharePoint page.
public static void RegisterScript(string scriptPath, WebPart webpart)
{
string IncludeScriptFormat = @"<script language=""{0}"" src=""{1}""></script>";
if (!webpart.Page.ClientScript.IsClientScriptBlockRegistered(scriptPath))
{
string includescript = String.Format(IncludeScriptFormat, "javascript", scriptPath);
webpart.Page.ClientScript.RegisterClientScriptBlock(webpart.GetType(), scriptPath, includescript);
}
}
Helper function to add CSS files to the SharePoint Page.
public static void RegisterCSS(string cssPath, WebPart webpart)
{
ContentPlaceHolder _Header = (ContentPlaceHolder)webpart.Page.Master.FindControl("PlaceHolderAdditionalPageHead");
if (_Header != null)
{
CssRegistration cssControls = new CssRegistration();
cssControls.Name = cssPath;
_Header.Controls.Add(cssControls);
}
}
Once you have these two helper functions in your webpart code, you can add the following code to the CreadChildControls method in your webpart code. By using the methods above, it will allow us to register our scripts and CSS so we can then start using them within the sharepoint webpart.
RegisterScript(this.StripHTTPAndServer(_classResourcePath) + @"\JS\jquery-1.3.2.js", this);
RegisterScript(this.StripHTTPAndServer(_classResourcePath) + @"\JS\jquery.dataTables.js", this);
RegisterScript(this.StripHTTPAndServer(_classResourcePath) + @"\JS\impromptu.js", this);
RegisterScript(this.StripHTTPAndServer(_classResourcePath) + @"\JS\jquery.throbber.js", this);
RegisterScript(this.StripHTTPAndServer(_classResourcePath) + @"\JS\XenServerVMManagement.js?nocache=" + System.DateTime.Now.ToString("MMddyyyyhhmmss"), this);
So after the webpart has been loaded and shows up in the sharepoint page, the JavaScript/JQuery will now kick off.
//JQuery, when the DOM document is ready call a specific function.
$(document).ready(customReadyFunction);
//Document ready function. This is the function we tell JQuery to execute when the DOM is ready to reaverse.
function customReadyFunction() {
//Create the data table.
xenServerVMDataTable = $("#xenServerVMMachines").dataTable();
//call the WCF service to get all VM's
var serviceURL = "/_XenServerWCF/XenServer.svc/GetVirtualMachines?host=" + xsServer + "&port=" + xsPort + "&password=" + xsPassword + "&username=" + xsUsername;
$.ajax({
url: serviceURL,
type: 'GET',
dataType:'xml',
success:getXenServersSuccessCallback,
failure:getXenServersFailureCallback
});
}
The JQuery/JavaScript above sets up and calls a custom function when the DOM is ready. This custom function is responsible for calling the WCF XenServer Service via an AJAX call.
SharePoint:
While the majority of the code resides in the JavaScript files, there are a couple of items we can touch on related to sharepoint.
The first is we have defined several custom properties that will allow the users to change XenServer settings. Like the following.
[Personalizable(PersonalizationScope.Shared)]
[WebBrowsable(true)]
[System.ComponentModel.Category("XenServer Settings")]
[WebDisplayName("XenServer Host Name (or IP)")]
[WebDescription("This field should be the netbios name or IP address of you XenServer")]
public string XenServerHost
[Personalizable(PersonalizationScope.Shared)]
[WebBrowsable(true)]
[System.ComponentModel.Category("XenServer Settings")]
[WebDisplayName("XenServer Host Port")]
[WebDescription("This is port or your XenServer that is listening for requests (Typically 80/443)")]
public int XenServerPort
[Personalizable(PersonalizationScope.Shared)]
[WebBrowsable(true)]
[System.ComponentModel.Category("XenServer Settings")]
[WebDisplayName("XenServer Username")]
[WebDescription("This should be a username on your XenServer that will be used to traverse and obtaing a list of Virtual Machines.")]
public string XenServerUsername
[Personalizable(PersonalizationScope.Shared)]
[WebBrowsable(true)]
[System.ComponentModel.Category("XenServer Settings")]
[WebDisplayName("XenServer Password")]
[WebDescription("This should be the password to the above XenServer username.")]
public string XenServerPassword
Along with these, we also have a feature receiver, which take on the function of creating some specific directories and files to enable the WCF services and registering some http models.
Ok, that’s a brief overview of the webpart, but i am going to break each part of this post out into more detailed posts each one can be targeted at each technology.
Feel free to comment or contact be via twitter (twitter.com/johnmcbride) and through comments on this blog.
