Пишем само-восстанавливающийся сервер на IIS

Я решил поделиться с Хаброюзерами полезной на мой взгляд программкой, многие сталкивались в работе IIS когда возникает какой то сбой и не всегда можно определить чем он вызван, но сервер перестает работать, возвращает ошибку 500 или еще что то в этом духе, обычно такое лечится перезапуском IIS, но не всегда рядом с сервером есть человек, который может это сделать, в связи с чем возникла идея, почему бы не делать это автоматически, в таком случае восстановление заняло бы максимум 5-10 минут и без присутствия администратора.
Такой подход сработает со многими продуктами, такими как Sharepoint, Webtutor (кто видел тот меня поймет) и любыми другими платформами на IIS, после переделки можно работать и с другими службами, если это нужно.
Описывать код, листинг которого не вижу особого смысла, т.к. его полностью прокомментировал, разберется любой кто хоть немного знаком с C#.
Надеюсь данное приложение поможет кому то в его работе. Буду рад услышать комментарии и пожелания.
P.S. если нужен готовый исходник пишите e-mail в комментариях.
Листинг программы под спойлером
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Net;
using System.ServiceProcess;
using System.Text;
using System.Threading;
//using System.Threading.Tasks;
namespace WebTutorMonitor
{
public partial class Service1 : ServiceBase
{
// Коды, ничего не значат, просто цифры
private int Code_Init=101;
private int Code_Start=102;
private static int Code_CreateLog=103;
private int Code_Stop=104;
private int Code_Resume=105;
private int Code_Pause=106;
private static int Code_DoneOneTask=107;
private int Code_TascControllerStarted=108;
private static int Code_TastWTMonitoringRun=109;
static bool Testing = true;//Если true сервис будет работать с тестом, не с боем
static bool DoStopStart = false;//Если false реально запуска и остановки не будет, только для проверки
static string testserverurl = @"http://webtutor.orient.root.biz";
static string prodserverurl = @"http://wtapp01";
static string testservername = @"webtutor.orient.root.biz";
static string prodservername = @"wtapp01";
public Service1()
{
EventLog ev = new EventLog("Application", System.Environment.MachineName, "WebTutorMonitor");
//Очистка журнала событий, раскоментировать если нужно почистить журнал
//EventLog.Clear();
InitializeComponent();
CreateLog("Служба WebTutorMonitor инициaлизируется.",EventLogEntryType.Information,Code_Init );
if (Testing)//Старт в режиме приложения, для Debug'a, произойдет только если Testing=True
{
OnStart(null);
}
}
//Действия при запуске службы
protected override void OnStart(string[] args)
{
//Запуск службы
CreateLog("Служба WebTutorMonitor запущена успешно.", EventLogEntryType.Information,Code_Start);
MainThread(); //Главный процесс службы
}
//Запись события в журнал событий
static void CreateLog(string text, EventLogEntryType Type,int Code)
{
Thread.Sleep(1500);
if (!(EventLog.SourceExists("WebTutorMonitor", System.Environment.MachineName)))
{
EventLog.CreateEventSource("WebTutorMonitor", "WebTutorLog", System.Environment.MachineName);
CreateLog("Создан журнал событий WebTutorMonitor", EventLogEntryType.Information ,Code_CreateLog);
}
EventLog ev = new EventLog("Application", System.Environment.MachineName, "WebTutorMonitor");
ev.WriteEntry(text, Type, Math.Abs (Code));
ev.Close();
GC.Collect();
}
//Действия при остановке службы
protected override void OnStop()
{
CreateLog("Служба WebTutorMonitor остановлена.", EventLogEntryType.Warning ,Code_Stop);
TimerCanceled = true;
}
//Действия при возобновлении работы службы
protected override void OnContinue()
{
CreateLog("Служба WebTutorMonitor запущена после паузы.",EventLogEntryType.Information ,Code_Resume);
base.OnContinue();
}
//Действия при паузе службы
protected override void OnPause()
{
CreateLog("Служба WebTutorMonitor приостановлена.", EventLogEntryType.Warning,Code_Pause );
base.OnPause();
}
//Главный поток службы
void MainThread()
{
//Запуск обработки заданий в отдельном потоке, чтобы освободить текущий поток для диспетчера служб
System.Threading.Thread tr = new System.Threading.Thread(new System.Threading.ThreadStart (TimerTask));//Работа потока проходит в процедуре TimerTask
tr.Start();//Запуск процедуры TimerTask в отдельном потоке.
CreateLog("Менеджер заданий запущен.", EventLogEntryType.Information , Code_TascControllerStarted);
}
static bool TimerCanceled = false;//Если True поток сервиса получает комманду на остановку
//Основная процедура службы
static private void TimerTask()
{
CreateLog("Задача монитор WT запущена.", EventLogEntryType.Information, Code_TastWTMonitoringRun);
rr:
rcl++;
if (TimerCanceled)//Если получен True выкодим из потока
{
CreateLog("Логирование завершено " + DateTime.Now.ToString() + ". Выполнено " + rcl.ToString() + " итераций логирования.", EventLogEntryType.Information, Code_DoneOneTask);
goto ext;
}
else
{
StartLogIteration();//Если еще рано выходить запускаем итерацию рабочего процесса
}
Thread.Sleep(100);
goto rr;
ext:
return; //Выход из потока
}
//Выключить себя, можно применять для завершения работы службы
static void ShutdownMeNow(string reason)
{
CreateLog("Завершение работы службы: " + reason, EventLogEntryType.Information, Code_DoneOneTask);
ServiceController sc = new ServiceController("Webtutormonitor", System.Environment.MachineName);//Webtutormonitor это название службы
if (sc.CanStop == true)
{
sc.Stop();//Посылаем команду на отключение службы на текущем компьютере
}
}
//Выполняем действие (сканируем или что либо другое)
static void StartLogIteration()
{
RunNewScan(); //Сканируем сервер на работоспособность
//CreateLog("Работа выполнена " + DateTime.Now.ToString() , EventLogEntryType.Information, Code_DoneOneTask); //Можем написать что бы сделали действие в лог
System.Threading.Thread.Sleep(5000);//Поспим, чтобы не слишком часто работать
}
//Выполняем сканирование сервера
static void RunNewScan()
{
string statdisk;//Переменная для хранения коментария ответа от сервера
string urlparam;//Переменная для хранения адреса сервера
if (Testing)//Если тестируем то тестовый сервер, если нет то боевой
{
urlparam = testserverurl;//тестовый
}
else { urlparam = prodserverurl; }//боевой
int stat = ServerStat(out statdisk, new Uri(urlparam));//Получаем данные от сервера, его текущее состояние
if (stat!=200)//Если не 200 значит у нас проблемы
{
CreateLog("Ошибка в работе WT: " + statdisk, EventLogEntryType.Error , stat);
if (stat==503)//Если 503 значит он в процессе перезапуска либо у него проблема с учетной записью
{
DoRestartIIS();//Если первое проверим, перезапустим если нужно
}
else//другие ошибки
{
if (stat==404)//Сервер выключен, пробуем включить
{
if (Testing)//Если тестируем то тестовый сервер, если нет то боевой
{
StartIISWTNow(testservername);//тестовый
}
else { StartIISWTNow(prodservername); }//боевой
}
}
}
}
//процедура перезапуска IIS
static void DoRestartIIS()
{
ServiceController sc;//Контроллер сервисов
//Инициируем контроллер для нужного нам сервиса
if (Testing)//Если тестируем то тестовый сервер, если нет то боевой
{
sc = new ServiceController("W3SVC", testservername);//тестовый
}
else
{
sc = new ServiceController("W3SVC", prodservername);//боевой
}
CreateLog("Предпринимаю попытку восстановить сервер путем перезапуска IIS!", EventLogEntryType.Error, 1001);
//Пробуем выключить службу веб-публикаций на сервере
if (Testing)//Если тестируем то тестовый сервер, если нет то боевой
{
CreateLog("Типа выключаю сервис!", EventLogEntryType.Error, 1202);
ShutdownIISWTNow(testservername);//тестовый
}
else { ShutdownIISWTNow(prodservername); }//боевой
//Если мы тут, то мы смогли выключить службу, иначе до сих пор долбимся в попытках ее отключить
//Служба отключена- включаем ее обратно
if (Testing)//Если тестируем то тестовый сервер, если нет то боевой
{
CreateLog("Типа включаю сервис!", EventLogEntryType.Error, 1201);
StartIISWTNow(testservername);//тестовый
}
else { StartIISWTNow(prodservername); }//боевой
//Если мы тут, значит сервер сказал что служба запущена и работает
Thread.Sleep(5000);//Дадим немного времени
sc.Refresh();//Проверим, может врет?
//Выводим текущее состояние в лог
CreateLog("Сервер в состоянии " + sc.Status.ToString(), EventLogEntryType.Error, 1301);
if (sc.Status == ServiceControllerStatus.Running )//Если сработало пишем соответствующую запись в лог, если нет тоже
{
CreateLog("Попытка перезапутить IIS Удалася!:) ", EventLogEntryType.Error, 1101);
}
else
{
CreateLog("Не могу сделать рестарт IIS Функция отключена:( ", EventLogEntryType.Error, 1101);
}
}
//Процедура выключения IIS на сервере, которая используется в процедуре DoRestartIIS
static void ShutdownIISWTNow(string compname = testservername)
{
ServiceController sc = new ServiceController("W3SVC", compname);//Инициализируем контроллер для данного сервера, W3SVC это имя службы веб публикаций на сервере Windows Server 2008 R2
gg: sc.Refresh();//Тут точка возврата, будем возвращаться сюда, если не получилось выключить службу
CreateLog("Попытка завершения службы на сервере " + compname, EventLogEntryType.Information, Code_DoneOneTask);
if (sc.CanStop == true)//Спрашиваем у сервере, можем ли мы отключить сейчас iis
{
try //Если да, то пробуем это сделать
{
if (DoStopStart)//Если false не будем реально ничего делать, просто сделаем вид что пытались
{
sc.Stop();//Посылаем комманду на отключение службы
CreateLog("Сервер получил команду на остановку службы :)", EventLogEntryType.Error, 1004);
}
else
{
CreateLog("Сервер не получил команду на остановку службы потому что приложение работает в демо режиме", EventLogEntryType.Error, 1204);
}
}
catch (Exception eee)//Если возникла ошибка пишем ее в лог
{
CreateLog("Не получилось остановить службу :( " + eee.Message , EventLogEntryType.Error , 1005);
}
}
else//Если сервер говорит, что нельзя останавливаться идем сушить бублики до следующего раза
{
CreateLog("Её нельзя сейчас останавливать :( Статус сервера: " + sc.Status.ToString(), EventLogEntryType.Error, 1003);
}
if (sc.Status == ServiceControllerStatus.Stopped)//Если сервер говорит что служба остановлена радуемся, у нас получилось
{
CreateLog(@"Я остановил службу!!! =8 Ха! Ха! хА! =\" + sc.Status.ToString(), EventLogEntryType.Warning, 1043);
return;//Выходим, чтобы попробовать запустить ее снова.
}
Thread.Sleep(10000);//если мы пришли сюда значит сервер еще не остановился, а значит у нас почему то не получилось его остановить,
//может быть он просто сейчас пытается остановиться но еще не успел закончить, подождем 10 секунд и попробуем снова
//так как не остановив запускать его бессмысленно будем долбиться пока не сможем его выключить
goto gg;//пробуем снова
}
//Запуск IIS все по аналогии с остановкой.
static void StartIISWTNow(string compname = testservername)
{
ServiceController sc = new ServiceController("W3SVC", compname);
gg: sc.Refresh();
CreateLog("Запуска службы на сервере " + compname, EventLogEntryType.Information, Code_DoneOneTask);
if (sc.Status == ServiceControllerStatus.Stopped)
{
try
{
if (DoStopStart)
{
sc.Start();
sc.Refresh();
sc.WaitForStatus(ServiceControllerStatus.Running , new TimeSpan(0, 0, 10));
CreateLog("Сервер получил команду на запуск службы :)", EventLogEntryType.Warning, 1211);
}
else
{
CreateLog("Сервер не получил команду на запуск службы потому что приложение работает в демо режиме", EventLogEntryType.Error, 1204);
}
}
catch (Exception eee)
{
CreateLog("Не получилось запустить службу :( " + eee.Message, EventLogEntryType.Error, 1012);
}
} else
{
CreateLog("Её нельзя сейчас запускать :( Статус сервера: " + sc.Status.ToString(), EventLogEntryType.Error, 1013);
}
if (sc.Status == ServiceControllerStatus.Running )
{
CreateLog("У меня получилось запустить службу:)" , EventLogEntryType.Warning , 1023);
return;
}
Thread.Sleep(10000);
goto gg;
}
//Определяем статус сервера в данный момент, descript это дополнительная информация о состоянии, текст ошибки например, url-это адрес узла сервера для проверки
static int ServerStat(out string descript, Uri url)
{
int statusCode=-1;
var myCred;//учетные данные, т.к. мы будем входить на сервер IIS под NTLM, передалать на другие типы авторизации проще
if (Testing)//Если тестируем то тестовый сервер, если нет то боевой
{
myCred = new NetworkCredential("vipuhov", "123456", "ORIENT");//тестовый тут мы используем такие учетные данные
}
else { myCred = CredentialCache.DefaultNetworkCredentials; }//боевой тут мы настроили под какими учетными данными будет запускаться служба
Uri urll = url;
var myCache = new CredentialCache { { urll, "NTLM", myCred } };//тут мы привязываем учетные данные к адресу сайта, чтобы установить NTLM авторизацию
try
{
var request = (HttpWebRequest)WebRequest.Create(urll);//создаем объект веб запроса
request.Timeout = 50000;//устанавливаем таймаут соединения, 50 секунд это перебор для любого сайта, можно говорить что ему плохо если он минуту страницу показать не может
//настраиваем учетные данные и метод, нам сама страница не нужна, поэтому берем только заголовок
request.Credentials = myCache;
request.Method = WebRequestMethods.Http.Head;
request.Accept = @"*/*";
var response = (HttpWebResponse)request.GetResponse();//делаем запрос
statusCode = (int)response.StatusCode;//получаем код ответа от сервера
response.Close();//закрываем запрос, нам он больше не нужен
GC.Collect();//убираем мусор:)
descript = "OK";//если мы тут значит все ОК:)
return statusCode;//возвращаем код, скорее всего 200
}
catch (WebException ex)//тут ловим ошибки
{
if (ex.Response == null)//если нет ответа от сервера скорее всего ошибка 404 сервер недоступен
{
descript = ex.Message;//возвращаем текст ошибки
}
else
{
statusCode = (int)((HttpWebResponse)ex.Response).StatusCode;//если запрос прошел но сервер возвратил ошибку пишем ошибку возвращаем его код
descript = ((HttpWebResponse)ex.Response).StatusDescription;//пишем сообщение которое нам прислал сервер вместе с ошибкой
}
GC.Collect();//чистим мусор:)
return statusCode;//возращаем код ошибки
}
}
}
}