diff --git a/XenModel/XenAPI-Extensions/Pool.cs b/XenModel/XenAPI-Extensions/Pool.cs
index e222e0cf4..40e325e88 100644
--- a/XenModel/XenAPI-Extensions/Pool.cs
+++ b/XenModel/XenAPI-Extensions/Pool.cs
@@ -462,6 +462,7 @@ namespace XenAPI
public const string NEW_UPLOAD_REQUEST = "NewUploadRequest";
public const string HEALTH_CHECK_PIPE = "HealthCheckServicePipe";
public const string HEALTH_CHECK_PIPE_END_MESSAGE = "HealthCheckServicePipe";
+ public const string UPLOAD_UUID = "UploadUuid";
public CallHomeSettings(CallHomeStatus status, int intervalInDays, DayOfWeek dayOfWeek, int timeOfDay, int retryInterval)
{
diff --git a/XenServerHealthCheck/XenServerHealthCheck.csproj b/XenServerHealthCheck/XenServerHealthCheck.csproj
index 397c1e807..3844b0173 100755
--- a/XenServerHealthCheck/XenServerHealthCheck.csproj
+++ b/XenServerHealthCheck/XenServerHealthCheck.csproj
@@ -48,6 +48,7 @@
+
@@ -55,14 +56,16 @@
-
+
True
True
Settings.settings
-
+
+
+
Component
diff --git a/XenServerHealthCheck/XenServerHealthCheckBundleUpload.cs b/XenServerHealthCheck/XenServerHealthCheckBundleUpload.cs
new file mode 100644
index 000000000..b2f18bd79
--- /dev/null
+++ b/XenServerHealthCheck/XenServerHealthCheckBundleUpload.cs
@@ -0,0 +1,210 @@
+/* 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.Collections.Generic;
+using System.IO;
+using System.Threading;
+using XenAdmin.Core;
+using XenAdmin.Network;
+using XenAPI;
+
+
+namespace XenServerHealthCheck
+{
+ public class XenServerHealthCheckBundleUpload
+ {
+ public XenServerHealthCheckBundleUpload(IXenConnection _connection)
+ {
+ connection = _connection;
+ }
+
+ private IXenConnection connection;
+
+ private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
+ public const int TIMEOUT = 24 * 60 * 60 * 1000;
+ public const int VERBOSITY_LEVEL = 2;
+
+ public void runUpload()
+ {
+ DateTime startTime = DateTime.UtcNow;
+ string uploadToken = "";
+ Session session = new Session(connection.Hostname, 80);
+ session.APIVersion = API_Version.LATEST;
+
+ try
+ {
+ session.login_with_password(connection.Username, connection.Password);
+ connection.LoadCache(session);
+ var pool = Helpers.GetPoolOfOne(connection);
+ if (pool != null)
+ {
+ uploadToken = pool.CallHomeSettings.GetSecretyInfo(connection, CallHomeSettings.UPLOAD_TOKEN_SECRET);
+ }
+
+ if (string.IsNullOrEmpty(uploadToken))
+ {
+ if (session != null)
+ session.logout();
+ session = null;
+ log.ErrorFormat("The upload token is not retrieved for {0}", connection.Hostname);
+ updateCallHomeSettings(false, startTime);
+ return;
+ }
+
+ }
+ catch (Exception e)
+ {
+ if (session != null)
+ session.logout();
+ session = null;
+ log.Error(e, e);
+ updateCallHomeSettings(false, startTime);
+ return;
+ }
+
+ try
+ {
+ CancellationTokenSource cts = new CancellationTokenSource();
+ Func upload = delegate()
+ {
+ try
+ {
+ return bundleUpload(connection, session, uploadToken, cts.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ return "";
+ }
+ };
+ System.Threading.Tasks.Task task = new System.Threading.Tasks.Task(upload);
+ task.Start();
+ if (!task.Wait(TIMEOUT))
+ {
+ cts.Cancel();
+ updateCallHomeSettings(false, startTime);
+ task.Wait();
+ return;
+ }
+
+ if (task.Status == System.Threading.Tasks.TaskStatus.RanToCompletion)
+ {
+ string upload_uuid = task.Result;
+ if(!string.IsNullOrEmpty(upload_uuid))
+ updateCallHomeSettings(true, startTime, upload_uuid);
+ else
+ updateCallHomeSettings(false, startTime);
+ }
+ else
+ updateCallHomeSettings(false, startTime);
+ }
+ catch (Exception e)
+ {
+ if (session != null)
+ session.logout();
+ session = null;
+ log.Error(e, e);
+ updateCallHomeSettings(false, startTime);
+ }
+
+ }
+
+ public void updateCallHomeSettings(bool success, DateTime time, string uploadUuid = "")
+ {
+ Session session = new Session(connection.Hostname, 80);
+ session.login_with_password(connection.Username, connection.Password);
+ connection.LoadCache(session);
+
+ // Round-trip format time
+ DateTime rtime = DateTime.SpecifyKind(time, DateTimeKind.Utc);
+ string stime = rtime.ToString("o");
+
+ // record upload_uuid,
+ // release the lock,
+ // set the time of LAST_SUCCESSFUL_UPLOAD or LAST_FAILED_UPLOAD
+ Dictionary config = Pool.get_health_check_config(session, connection.Cache.Pools[0].opaque_ref);
+ config[CallHomeSettings.UPLOAD_LOCK] = "";
+ if (success)
+ {
+ config[CallHomeSettings.LAST_SUCCESSFUL_UPLOAD] = stime;
+ config[CallHomeSettings.UPLOAD_UUID] = uploadUuid;
+ }
+ else
+ config[CallHomeSettings.LAST_FAILED_UPLOAD] = stime;
+ Pool.set_health_check_config(session, connection.Cache.Pools[0].opaque_ref, config);
+
+ if (session != null)
+ session.logout();
+ session = null;
+ }
+
+ public string bundleUpload(IXenConnection connection, Session session, string uploadToken, System.Threading.CancellationToken cancel)
+ {
+ // Collect the server status report and generate zip file to upload.
+ XenServerHealthCheckBugTool bugTool = new XenServerHealthCheckBugTool();
+ try
+ {
+ bugTool.RunBugtool(connection, session);
+ }
+ catch (Exception e)
+ {
+ if (session != null)
+ session.logout();
+ session = null;
+ log.Error(e, e);
+ return "";
+ }
+
+ string bundleToUpload = bugTool.outputFile;
+ if(string.IsNullOrEmpty(bundleToUpload) || !File.Exists(bundleToUpload))
+ {
+ log.ErrorFormat("Server Status Report is NOT collected");
+ return "";
+ }
+
+ // Upload the zip file to CIS uploading server.
+ XenServerHealthCheckUpload upload = new XenServerHealthCheckUpload(uploadToken, VERBOSITY_LEVEL);
+ string upload_uuid = upload.UploadZip(bundleToUpload, cancel);
+ if (File.Exists(bundleToUpload))
+ File.Delete(bundleToUpload);
+
+ // Return the uuid of upload.
+ if(string.IsNullOrEmpty(upload_uuid))
+ {
+ // Fail to upload the zip to CIS server.
+ log.ErrorFormat("Fail to upload the Server Status Report {0} to CIS server", bundleToUpload);
+ return "";
+ }
+
+ return upload_uuid;
+ }
+ }
+}
diff --git a/XenServerHealthCheck/XenServerHealthCheckService.cs b/XenServerHealthCheck/XenServerHealthCheckService.cs
index 1fb891d16..efd0e6fad 100755
--- a/XenServerHealthCheck/XenServerHealthCheckService.cs
+++ b/XenServerHealthCheck/XenServerHealthCheckService.cs
@@ -132,9 +132,17 @@ namespace XenServerHealthCheck
session.login_with_password(server.UserName, server.Password);
connectionInfo.LoadCache(session);
if (RequestUploadTask.Request(connectionInfo, session) || RequestUploadTask.OnDemandRequest(connectionInfo, session))
- {
- //Create thread to do log uploading
- log.InfoFormat("Will upload report for XenServer {0}", connectionInfo.Hostname);
+ {
+ // Create a task to collect server status report and upload to CIS server
+ log.InfoFormat("Start to upload server status report for XenServer {0}", connectionInfo.Hostname);
+
+ XenServerHealthCheckBundleUpload upload = new XenServerHealthCheckBundleUpload(connectionInfo);
+ Action uploadAction = delegate()
+ {
+ upload.runUpload();
+ };
+ System.Threading.Tasks.Task task = new System.Threading.Tasks.Task(uploadAction);
+ task.Start();
}
session.logout();
session = null;
diff --git a/XenServerHealthCheck/XenServerHealthCheckUpload.cs b/XenServerHealthCheck/XenServerHealthCheckUpload.cs
new file mode 100644
index 000000000..66e755e6a
--- /dev/null
+++ b/XenServerHealthCheck/XenServerHealthCheckUpload.cs
@@ -0,0 +1,216 @@
+/* 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.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+using System.Net;
+using System.Web.Script.Serialization;
+
+
+namespace XenServerHealthCheck
+{
+ public class XenServerHealthCheckUpload
+ {
+ private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
+ public const string UPLOAD_URL = "https://rttf-staging.citrix.com/feeds/api/";
+ public const int CHUNK_SIZE = 1 * 1024 * 1024;
+ private JavaScriptSerializer serializer;
+ private int verbosityLevel;
+
+ private string uploadToken;
+
+ public XenServerHealthCheckUpload(string token, int verbosity)
+ {
+ uploadToken = token;
+ verbosityLevel = verbosity;
+ serializer = new JavaScriptSerializer();
+ serializer.MaxJsonLength = int.MaxValue;
+ }
+
+ // Request an upload and fetch the uploading id from CIS.
+ public string InitiateUpload(long size)
+ {
+ // Request a new bundle upload to CIS server.
+ Uri uri = new Uri(UPLOAD_URL + "bundle/?size=" + size.ToString());
+ HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
+ if (uploadToken != null)
+ {
+ request.Headers.Add("Authorization", "BT " + uploadToken);
+ }
+
+ request.Method = "POST";
+ request.ContentType = "application/json";
+ request.ServicePoint.Expect100Continue = false;
+
+ using (var streamWriter = new StreamWriter(request.GetRequestStream()))
+ {
+ // Add the log verbosity level in json cotent.
+ string verbosity = "{\"verbosity\":\"2\"}";
+ streamWriter.Write(verbosity);
+ }
+
+ Dictionary res = new Dictionary();
+ try
+ {
+ HttpWebResponse response = (HttpWebResponse)request.GetResponse();
+ using (StreamReader reader = new StreamReader(response.GetResponseStream()))
+ {
+ string respString = reader.ReadToEnd();
+ res = (Dictionary)serializer.DeserializeObject(respString);
+ }
+ response.Close();
+ }
+ catch (WebException e)
+ {
+ log.ErrorFormat("Exception while requesting a new CIS uploading: {0}", e);
+ return "";
+ }
+
+ if (res.ContainsKey("id"))
+ {
+ // Get the uuid of uploading
+ return (string)res["id"];
+ }
+ else
+ {
+ // Fail to initialize the upload request
+ return "";
+ }
+ }
+
+ // Upload a chunk.
+ public bool UploadChunk(string address, string filePath, long offset, long thisChunkSize, string uploadToken)
+ {
+ Uri uri = new Uri(address);
+ HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
+ req.Method = "POST";
+ req.ContentType = "application/octet-stream";
+ req.Headers.Add("Authorization", "BT " + uploadToken);
+
+ using (Stream destination = req.GetRequestStream())
+ {
+ using (FileStream source = File.Open(filePath, FileMode.Open))
+ {
+ source.Position = offset;
+ int bytes = Convert.ToInt32(thisChunkSize);
+ byte[] buffer = new byte[CHUNK_SIZE];
+ int read;
+ while (bytes > 0 &&
+ (read = source.Read(buffer, 0, Math.Min(buffer.Length, bytes))) > 0)
+ {
+ destination.Write(buffer, 0, read);
+ bytes -= read;
+ }
+ }
+ HttpWebResponse resp = req.GetResponse() as HttpWebResponse;
+ HttpStatusCode statusCode = resp.StatusCode;
+
+ // After sending every chunk upload request to server, response will contain a status indicating if it is complete.
+ using (StreamReader reader = new StreamReader(resp.GetResponseStream()))
+ {
+ string respString = reader.ReadToEnd();
+ Dictionary res = (Dictionary)serializer.DeserializeObject(respString);
+ log.InfoFormat("The status of chunk upload: {0}", res["status"]);
+ }
+
+ resp.Close();
+
+ if (statusCode == HttpStatusCode.OK)
+ return true;
+ else
+ return false;
+ }
+ }
+
+ // Upload the zip file.
+ public string UploadZip(string fileName, System.Threading.CancellationToken cancel)
+ {
+ FileInfo fileInfo = new FileInfo(fileName);
+ long size = fileInfo.Length;
+
+ // Fetch the upload UUID from CIS server.
+ string uploadUuid = InitiateUpload(size);
+ if (string.IsNullOrEmpty(uploadUuid))
+ {
+ log.ErrorFormat("Cannot fetch the upload UUID from CIS server");
+ return "";
+ }
+
+ // Start to upload zip file.
+ log.InfoFormat("Upload ID: {0}", uploadUuid);
+ StringBuilder url = new StringBuilder(UPLOAD_URL + "upload_raw_chunk/?id=" + uploadUuid);
+
+ long offset = 0;
+ while (offset < size)
+ {
+ url.Append(String.Format("&offset={0}", offset));
+ long remainingSize = size - offset;
+ long thisChunkSize = (remainingSize > CHUNK_SIZE) ? CHUNK_SIZE : remainingSize;
+
+ try
+ {
+ for (int i = 0; i < 3; i++)
+ {
+ if (cancel.IsCancellationRequested)
+ {
+ return "";
+ }
+
+ if (UploadChunk(url.ToString(), fileName, offset, thisChunkSize, uploadToken))
+ {
+ // This chunk is successfully uploaded
+ offset += thisChunkSize;
+ break;
+ }
+
+ // Fail to upload the chunk for 3 times so fail the bundle upload.
+ log.ErrorFormat("Fail to upload the chunk");
+ return "";
+ }
+ }
+ catch (Exception e)
+ {
+ log.Error(e, e);
+ return "";
+ }
+
+ }
+
+ log.InfoFormat("Succeed to upload bundle {0}", fileName);
+ return uploadUuid;
+
+ }
+
+ }
+}
diff --git a/XenServerHealthCheck/app.config b/XenServerHealthCheck/app.config
index 85260dc05..4df9082cb 100755
--- a/XenServerHealthCheck/app.config
+++ b/XenServerHealthCheck/app.config
@@ -1,21 +1,22 @@
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
@@ -23,4 +24,19 @@
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file