MINOR: hlua: expose SERVER_STATE event

Exposing SERVER_STATE event in lua and updating the documentation.
diff --git a/doc/lua-api/index.rst b/doc/lua-api/index.rst
index d470491..333c351 100644
--- a/doc/lua-api/index.rst
+++ b/doc/lua-api/index.rst
@@ -946,10 +946,14 @@
     * **SERVER_DEL**: when a server is removed
     * **SERVER_DOWN**: when a server state goes from UP to DOWN
     * **SERVER_UP**: when a server state goes from DOWN to UP
+    * **SERVER_STATE**: when a server state changes
 
    .. Note::
-     You may also use **SERVER** in **event_types** to subscribe to all server
-     events types at once.
+     Use **SERVER** in **event_types** to subscribe to all server events types
+     at once. Note that this should only be used for testing purposes since a
+     single event source could result in multiple events types being generated.
+     (e.g.: SERVER_STATE will always be generated for each SERVER_DOWN or
+     SERVER_UP)
 
   The prototype of the Lua function used as argument is:
 
@@ -1522,6 +1526,111 @@
      (Will never be set for SERVER_DEL event since the server does not exist
      anymore)
 
+.. js:attribute:: ServerEvent.state
+
+  A :ref:`server_event_state_class`
+
+  .. Note::
+     Only available for SERVER_STATE event
+
+.. _server_event_checkres_class:
+
+ServerEventCheckRes class
+=========================
+
+.. js:class:: ServerEventCheckRes
+
+This class describes the result of a server's check.
+
+.. js:attribute:: ServerEventCheckRes.result
+
+  Effective check result.
+
+  Check result is a string and will be set to one of the following values:
+    - "FAILED": the check failed
+    - "PASSED": the check succeeded
+    - "CONDPASS": the check conditionally passed
+
+.. js:attribute:: ServerEventCheckRes.agent
+
+  Boolean set to true if the check is an agent check.
+  Else it is a health check.
+
+.. js:attribute:: ServerEventCheckRes.duration
+
+  Check's duration in milliseconds
+
+.. js:attribute:: ServerEventCheckRes.reason
+
+  Check's status. An array containing three fields:
+    - **short**: a string representing check status short name
+    - **desc**: a string representing check status description
+    - **code**: an integer, this extra information is provided for checks
+      that went through the data analysis stage (>= layer 5)
+
+.. js:attribute:: ServerEventCheckRes.health
+
+  An array containing values about check's health (integers):
+    - **cur**: current health counter:
+       - 0 to (**rise** - 1) = BAD
+       - **rise** to (**rise** + **fall** - 1) = GOOD
+    - **rise**: server will be considered as operational after **rise**
+      consecutive successful checks
+    - **fall**: server will be considered as dead after **fall** consecutive
+      unsuccessful checks
+
+.. _server_event_state_class:
+
+ServerEventState class
+======================
+
+.. js:class:: ServerEventState
+
+This class contains additional info related to **SERVER_STATE** event.
+
+.. js:attribute:: ServerEventState.admin
+
+  Boolean set to true if the server state change is due to an administrative
+  change. Else it is an operational change.
+
+.. js:attribute:: ServerEventState.check
+
+  A :ref:`server_event_checkres_class`, provided if the state change is
+  due to a server check (must be an operational change).
+
+.. js:attribute:: ServerEventState.cause
+
+  Printable state change cause. Might be empty.
+
+.. js:attribute:: ServerEventState.new_state
+
+  New server state due to operational or admin change.
+
+  It is a string that can be any of the following values:
+    - "STOPPED": The server is down
+    - "STOPPING": The server is up but soft-stopping
+    - "STARTING": The server is warming up
+    - "RUNNING": The server is fully up
+
+.. js:attribute:: ServerEventState.old_state
+
+  Previous server state prior to the operational or admin change.
+
+  Can be any value described in **new_state**, but they should differ.
+
+.. js:attribute:: ServerEventState.requeued
+
+  Number of connections that were requeued due to the server state change.
+
+  For a server going DOWN: it is the number of pending server connections
+  that are requeued to the backend (such connections will be redispatched
+  to any server that is suitable according to the configured load balancing
+  algorithm).
+
+  For a server doing UP: it is the number of pending connections on the
+  backend that may be redispatched to the server according to the load
+  balancing algorithm that is in use.
+
 .. _concat_class:
 
 Concat class
diff --git a/src/hlua.c b/src/hlua.c
index 973e277..34ad3c8 100644
--- a/src/hlua.c
+++ b/src/hlua.c
@@ -66,6 +66,7 @@
 #include <haproxy/vars.h>
 #include <haproxy/xref.h>
 #include <haproxy/event_hdl.h>
+#include <haproxy/check.h>
 
 /* Lua uses longjmp to perform yield or throwing errors. This
  * macro is used only for identifying the function that can
@@ -9029,6 +9030,67 @@
 	}
 }
 
+__LJMP static void hlua_event_hdl_cb_push_event_checkres(lua_State *L,
+                                                         struct event_hdl_cb_data_server_checkres *check)
+{
+	lua_pushstring(L, "agent");
+	lua_pushboolean(L, check->agent);
+	lua_settable(L, -3);
+	lua_pushstring(L, "result");
+	switch (check->result) {
+		case CHK_RES_FAILED:
+			lua_pushstring(L, "FAILED");
+			break;
+		case CHK_RES_PASSED:
+			lua_pushstring(L, "PASSED");
+			break;
+		case CHK_RES_CONDPASS:
+			lua_pushstring(L, "CONDPASS");
+			break;
+		default:
+			lua_pushnil(L);
+			break;
+	}
+	lua_settable(L, -3);
+
+	lua_pushstring(L, "duration");
+	lua_pushinteger(L, check->duration);
+	lua_settable(L, -3);
+
+	lua_pushstring(L, "reason");
+	lua_newtable(L);
+
+	lua_pushstring(L, "short");
+	lua_pushstring(L, get_check_status_info(check->reason.status));
+	lua_settable(L, -3);
+	lua_pushstring(L, "desc");
+	lua_pushstring(L, get_check_status_description(check->reason.status));
+	lua_settable(L, -3);
+	if (check->reason.status >= HCHK_STATUS_L57DATA) {
+		/* code only available when the check reached data analysis stage */
+		lua_pushstring(L, "code");
+		lua_pushinteger(L, check->reason.code);
+		lua_settable(L, -3);
+	}
+
+	lua_settable(L, -3); /* reason table */
+
+	lua_pushstring(L, "health");
+	lua_newtable(L);
+
+	lua_pushstring(L, "cur");
+	lua_pushinteger(L, check->health.cur);
+	lua_settable(L, -3);
+	lua_pushstring(L, "rise");
+	lua_pushinteger(L, check->health.rise);
+	lua_settable(L, -3);
+	lua_pushstring(L, "fall");
+	lua_pushinteger(L, check->health.fall);
+	lua_settable(L, -3);
+
+	lua_settable(L, -3); /* health table */
+}
+
 /* This function pushes various arguments such as event type and event data to
  * the lua function that will be called to consume the event.
  */
@@ -9072,6 +9134,73 @@
 		lua_pushinteger(hlua->T, e_server->safe.proxy_uuid);
 		lua_settable(hlua->T, -3);
 
+		/* special events, fetch additional info with explicit type casting */
+		if (event_hdl_sub_type_equal(EVENT_HDL_SUB_SERVER_STATE, event)) {
+			struct event_hdl_cb_data_server_state *state = data;
+			int it;
+
+			if (!lua_checkstack(hlua->T, 20))
+				WILL_LJMP(luaL_error(hlua->T, "Lua out of memory error."));
+
+			/* state subclass */
+			lua_pushstring(hlua->T, "state");
+			lua_newtable(hlua->T);
+
+			lua_pushstring(hlua->T, "admin");
+			lua_pushboolean(hlua->T, state->safe.type);
+			lua_settable(hlua->T, -3);
+
+			/* is it because of a check ? */
+			if (!state->safe.type &&
+			    (state->safe.op_st_chg.cause == SRV_OP_STCHGC_HEALTH ||
+			     state->safe.op_st_chg.cause == SRV_OP_STCHGC_AGENT)) {
+				/* yes, provide check result */
+				lua_pushstring(hlua->T, "check");
+				lua_newtable(hlua->T);
+				hlua_event_hdl_cb_push_event_checkres(hlua->T, &state->safe.op_st_chg.check);
+				lua_settable(hlua->T, -3); /* check table */
+			}
+
+			lua_pushstring(hlua->T, "cause");
+			if (state->safe.type)
+				lua_pushstring(hlua->T, srv_adm_st_chg_cause(state->safe.adm_st_chg.cause));
+			else
+				lua_pushstring(hlua->T, srv_op_st_chg_cause(state->safe.op_st_chg.cause));
+			lua_settable(hlua->T, -3);
+
+			/* old_state, new_state */
+			for (it = 0; it < 2; it++) {
+				enum srv_state srv_state = (!it) ? state->safe.old_state : state->safe.new_state;
+
+				lua_pushstring(hlua->T, (!it) ? "old_state" : "new_state");
+				switch (srv_state) {
+					case SRV_ST_STOPPED:
+						lua_pushstring(hlua->T, "STOPPED");
+						break;
+					case SRV_ST_STOPPING:
+						lua_pushstring(hlua->T, "STOPPING");
+						break;
+					case SRV_ST_STARTING:
+						lua_pushstring(hlua->T, "STARTING");
+						break;
+					case SRV_ST_RUNNING:
+						lua_pushstring(hlua->T, "RUNNING");
+						break;
+					default:
+						lua_pushnil(hlua->T);
+						break;
+				}
+				lua_settable(hlua->T, -3);
+			}
+
+			/* requeued */
+			lua_pushstring(hlua->T, "requeued");
+			lua_pushinteger(hlua->T, state->safe.requeued);
+			lua_settable(hlua->T, -3);
+
+			lua_settable(hlua->T, -3); /* state table */
+		}
+
 		/* attempt to provide reference server object
 		 * (if it wasn't removed yet, SERVER_DEL will never succeed here)
 		 */