This is an automated email from the ASF dual-hosted git repository.
wzhou pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git
The following commit(s) were added to refs/heads/master by this push:
new f67f5f181 IMPALA-12705: Add /catalog_ha_info page on Statestore to
show catalog HA information
f67f5f181 is described below
commit f67f5f1815c60a4723887ea6fcdaa067b7fa4ca5
Author: ttttttz <[email protected]>
AuthorDate: Fri May 31 10:24:47 2024 +0800
IMPALA-12705: Add /catalog_ha_info page on Statestore to show catalog HA
information
This patch adds /catalog_ha_info page on Statestore to show catalog HA
information. The page contains the following information: Active Node,
Standby Node, and Notified Subscribers table. In the Notified
Subscribers table, include the following information items:
-- Id,
-- Address,
-- Registration ID,
-- Subscriber Type,
-- Catalogd Version,
-- Catalogd Address,
-- Last Update Catalogd Time
Change-Id: If85f6a827ae8180d13caac588b92af0511ac35e3
Reviewed-on: http://gerrit.cloudera.org:8080/21418
Reviewed-by: Impala Public Jenkins <[email protected]>
Tested-by: Impala Public Jenkins <[email protected]>
---
be/src/statestore/statestore-catalogd-mgr.cc | 17 ++++
be/src/statestore/statestore-catalogd-mgr.h | 12 ++-
be/src/statestore/statestore.cc | 136 +++++++++++++++++++++++++++
be/src/statestore/statestore.h | 24 +++++
common/thrift/StatestoreService.thrift | 3 +
tests/custom_cluster/test_catalogd_ha.py | 41 ++++++++
www/catalog_ha_info.tmpl | 84 +++++++++++++++++
7 files changed, 316 insertions(+), 1 deletion(-)
diff --git a/be/src/statestore/statestore-catalogd-mgr.cc
b/be/src/statestore/statestore-catalogd-mgr.cc
index a381e1a6b..1bb8c81a0 100644
--- a/be/src/statestore/statestore-catalogd-mgr.cc
+++ b/be/src/statestore/statestore-catalogd-mgr.cc
@@ -59,6 +59,7 @@ bool StatestoreCatalogdMgr::RegisterCatalogd(bool
is_reregistering,
is_active_catalogd_assigned_ = true;
COPY_CATALOGD_REGISTRATION_FROM_LOCAL_VARIABLES(active);
++active_catalogd_version_;
+ last_update_catalogd_time_ = UnixMillis();
return true;
}
@@ -73,6 +74,7 @@ bool StatestoreCatalogdMgr::RegisterCatalogd(bool
is_reregistering,
LOG(INFO) << active_catalogd_subscriber_id_
<< " is re-registered with FLAGS_force_catalogd_active.";
++active_catalogd_version_;
+ last_update_catalogd_time_ = UnixMillis();
return true;
}
} else {
@@ -86,6 +88,7 @@ bool StatestoreCatalogdMgr::RegisterCatalogd(bool
is_reregistering,
<< " is re-registered after HA preemption waiting period and
"
<< "is assigned as active catalogd.";
++active_catalogd_version_;
+ last_update_catalogd_time_ = UnixMillis();
return true;
}
}
@@ -116,6 +119,7 @@ bool StatestoreCatalogdMgr::RegisterCatalogd(bool
is_reregistering,
COPY_CATALOGD_REGISTRATION_FROM_LOCAL_VARIABLES(active);
LOG(INFO) << active_catalogd_subscriber_id_ << " is assigned as active
catalogd.";
++active_catalogd_version_;
+ last_update_catalogd_time_ = UnixMillis();
return true;
}
// Wait second catalogd to be registered.
@@ -138,6 +142,7 @@ bool StatestoreCatalogdMgr::RegisterCatalogd(bool
is_reregistering,
<< " is registered with FLAGS_force_catalogd_active and is
assigned as "
<< "active catalogd.";
++active_catalogd_version_;
+ last_update_catalogd_time_ = UnixMillis();
return true;
} else if (is_active_catalogd_assigned_) {
// Existing one is already assigned as active catalogd.
@@ -159,6 +164,7 @@ bool StatestoreCatalogdMgr::RegisterCatalogd(bool
is_reregistering,
LOG(INFO) << active_catalogd_subscriber_id_
<< " has higher priority and is assigned as active catalogd.";
++active_catalogd_version_;
+ last_update_catalogd_time_ = UnixMillis();
return true;
}
}
@@ -182,6 +188,7 @@ bool StatestoreCatalogdMgr::CheckActiveCatalog() {
LOG(INFO) << active_catalogd_subscriber_id_
<< " is assigned as active catalogd after preemption waiting
period.";
++active_catalogd_version_;
+ last_update_catalogd_time_ = UnixMillis();
return true;
}
@@ -197,6 +204,7 @@ bool StatestoreCatalogdMgr::UnregisterCatalogd(
COPY_CATALOGD_REGISTRATION_FROM_MEMBER_VARIABLES(active, standby);
RESET_CATALOGD_REGISTRATION_MEMBER_VARIABLES(standby);
LOG(INFO) << "Fail over active catalogd to " <<
active_catalogd_subscriber_id_;
+ last_update_catalogd_time_ = UnixMillis();
++active_catalogd_version_;
return true;
} else {
@@ -226,6 +234,11 @@ const TCatalogRegistration&
StatestoreCatalogdMgr::GetActiveCatalogRegistration(
return active_catalogd_registration_;
}
+const TCatalogRegistration&
StatestoreCatalogdMgr::GetStandbyCatalogRegistration() {
+ std::lock_guard<std::mutex> l(catalog_mgr_lock_);
+ return standby_catalogd_registration_;
+}
+
const SubscriberId& StatestoreCatalogdMgr::GetActiveCatalogdSubscriberId() {
std::lock_guard<std::mutex> l(catalog_mgr_lock_);
return active_catalogd_subscriber_id_;
@@ -236,3 +249,7 @@ bool StatestoreCatalogdMgr::IsActiveCatalogd(const
SubscriberId& subscriber_id)
return active_catalogd_subscriber_id_ == subscriber_id;
}
+int64_t StatestoreCatalogdMgr::GetLastUpdateCatalogTime() {
+ std::lock_guard<std::mutex> l(catalog_mgr_lock_);
+ return last_update_catalogd_time_;
+}
diff --git a/be/src/statestore/statestore-catalogd-mgr.h
b/be/src/statestore/statestore-catalogd-mgr.h
index 2fb7140ce..00b203437 100644
--- a/be/src/statestore/statestore-catalogd-mgr.h
+++ b/be/src/statestore/statestore-catalogd-mgr.h
@@ -45,7 +45,8 @@ class StatestoreCatalogdMgr {
is_active_catalogd_assigned_(false),
num_registered_catalogd_(0),
first_catalogd_register_time_(0),
- active_catalogd_version_(0L) {}
+ active_catalogd_version_(0L),
+ last_update_catalogd_time_(0L) {}
/// Register one catalogd.
/// Return true if new active catalogd is designated during this
registration.
@@ -73,12 +74,18 @@ class StatestoreCatalogdMgr {
/// This function should be called after the active catalogd is designated.
const SubscriberId& GetActiveCatalogdSubscriberId();
+ /// Return the protocol version of catalog service and address of standby
catalogd.
+ const TCatalogRegistration& GetStandbyCatalogRegistration();
+
/// Check if the subscriber with given subscriber_id is active catalogd.
bool IsActiveCatalogd(const SubscriberId&subscriber_id);
/// Return the mutex lock.
std::mutex* GetLock() { return &catalog_mgr_lock_; }
+ /// Return the last time when the catalogd is updated.
+ int64_t GetLastUpdateCatalogTime();
+
private:
/// Protect all member variables.
std::mutex catalog_mgr_lock_;
@@ -119,6 +126,9 @@ class StatestoreCatalogdMgr {
/// Monotonically increasing version number. The value is increased when a
new active
/// catalogd is designated.
int64_t active_catalogd_version_;
+
+ /// The time is updated when a new active catalogd is designated.
+ int64_t last_update_catalogd_time_;
};
} // namespace impala
diff --git a/be/src/statestore/statestore.cc b/be/src/statestore/statestore.cc
index 3114cc9f7..c150bb1aa 100644
--- a/be/src/statestore/statestore.cc
+++ b/be/src/statestore/statestore.cc
@@ -284,6 +284,7 @@ class StatestoreThriftIf : public StatestoreServiceIf {
TCatalogRegistration catalogd_registration;
if (params.__isset.catalogd_registration) {
catalogd_registration = params.catalogd_registration;
+ catalogd_registration.__set_registration_time(UnixMillis());
}
RegistrationId registration_id;
@@ -306,6 +307,7 @@ class StatestoreThriftIf : public StatestoreServiceIf {
if (is_active_statestored && has_active_catalogd) {
response.__set_catalogd_registration(active_catalogd_registration);
response.__set_catalogd_version(active_catalogd_version);
+ statestore_->UpdateSubscriberCatalogInfo(params.subscriber_id);
}
}
@@ -645,6 +647,13 @@ void
Statestore::Subscriber::RefreshLastHeartbeatTimestamp() {
last_heartbeat_ts_.Store(MonotonicMillis());
}
+void Statestore::Subscriber::UpdateCatalogInfo(
+ int64_t catalogd_version, const TNetworkAddress& catalogd_address) {
+ catalogd_version_ = catalogd_version;
+ catalogd_address_ = catalogd_address;
+ last_update_catalogd_time_ = UnixMillis();
+}
+
Statestore::Statestore(MetricGroup* metrics)
: protocol_version_(StatestoreServiceVersion::V2),
catalog_manager_(FLAGS_enable_catalogd_ha),
@@ -817,6 +826,13 @@ void Statestore::RegisterWebpages(Webserver* webserver,
bool metrics_only) {
webserver->RegisterUrlCallback("/subscribers", "statestore_subscribers.tmpl",
subscribers_callback, true);
+ if (FLAGS_enable_catalogd_ha) {
+ Webserver::UrlCallback show_catalog_ha_callback =
+ bind<void>(&Statestore::CatalogHAInfoHandler, this, _1, _2);
+ webserver->RegisterUrlCallback(
+ "/catalog_ha_info", "catalog_ha_info.tmpl", show_catalog_ha_callback,
true);
+ }
+
RegisterLogLevelCallbacks(webserver, false);
}
@@ -1580,6 +1596,7 @@ void Statestore::SendUpdateCatalogdNotification(int64_t*
last_active_catalogd_ve
// left in the receiver list so that RPC will be resent to it in next
round.
++it;
} else {
+ UpdateSubscriberCatalogInfo(it->get()->id());
successful_update_catalogd_rpc_metric_->Increment(1);
// Remove the subscriber from the receiver list so that Statestore
will not resend
// RPC to it in next round.
@@ -2155,3 +2172,122 @@ Status Statestore::SendHaHeartbeat() {
}
return status;
}
+
+void Statestore::UpdateSubscriberCatalogInfo(const SubscriberId&
subscriber_id) {
+ lock_guard<mutex> l(subscribers_lock_);
+ SubscriberMap::iterator it = subscribers_.find(subscriber_id);
+ if (it == subscribers_.end()) return;
+ std::shared_ptr<Subscriber> subscriber = it->second;
+ bool has_active_catalogd;
+ int64_t active_catalogd_version = 0;
+ TCatalogRegistration catalogd_registration =
+ catalog_manager_.GetActiveCatalogRegistration(
+ &has_active_catalogd, &active_catalogd_version);
+ if (has_active_catalogd) {
+ subscriber->UpdateCatalogInfo(active_catalogd_version,
catalogd_registration.address);
+ }
+}
+
+void Statestore::CatalogHAInfoHandler(
+ const Webserver::WebRequest& req, Document* document) {
+ if (FLAGS_enable_statestored_ha && !is_active_) {
+ document->AddMember("is_active_statestored", false,
document->GetAllocator());
+ return;
+ }
+ document->AddMember("is_active_statestored", true, document->GetAllocator());
+ // HA INFO
+ bool has_active_catalogd;
+ int64_t active_catalogd_version = 0;
+ TCatalogRegistration active_catalog_registration =
+ catalog_manager_.GetActiveCatalogRegistration(&has_active_catalogd,
+ &active_catalogd_version);
+
+ document->AddMember("has_active_catalogd", has_active_catalogd,
+ document->GetAllocator());
+ document->AddMember("active_catalogd_version", active_catalogd_version,
+ document->GetAllocator());
+ if (active_catalogd_version > 0) {
+ Value last_update_catalogd_time_(ToStringFromUnixMillis(
+ catalog_manager_.GetLastUpdateCatalogTime(),
+ TimePrecision::Millisecond).c_str(), document->GetAllocator());
+ document->AddMember("last_update_catalogd_time",
last_update_catalogd_time_,
+ document->GetAllocator());
+ }
+
+ if (has_active_catalogd) {
+ // Active catalogd information.
+ document->AddMember("active_catalogd_enable_catalogd_ha",
+ active_catalog_registration.enable_catalogd_ha,
document->GetAllocator());
+ Value active_catalogd_address(
+ TNetworkAddressToString(active_catalog_registration.address).c_str(),
+ document->GetAllocator());
+ document->AddMember("active_catalogd_address", active_catalogd_address,
+ document->GetAllocator());
+ document->AddMember("active_catalogd_force_catalogd_active",
+ active_catalog_registration.force_catalogd_active,
document->GetAllocator());
+ Value active_catalogd_registration_time(ToStringFromUnixMillis(
+ active_catalog_registration.registration_time,
+ TimePrecision::Millisecond).c_str(), document->GetAllocator());
+ document->AddMember("active_catalogd_registration_time",
+ active_catalogd_registration_time, document->GetAllocator());
+ }
+
+ // Standby catalogd information.
+ TCatalogRegistration standby_catalog_registration =
+ catalog_manager_.GetStandbyCatalogRegistration();
+ if (standby_catalog_registration.__isset.registration_time) {
+ document->AddMember("standby_catalogd_enable_catalogd_ha",
+ standby_catalog_registration.enable_catalogd_ha,
document->GetAllocator());
+ Value standby_catalogd_address(
+ TNetworkAddressToString(standby_catalog_registration.address).c_str(),
+ document->GetAllocator());
+ document->AddMember(
+ "standby_catalogd_address", standby_catalogd_address,
document->GetAllocator());
+ document->AddMember("standby_catalogd_force_catalogd_active",
+ standby_catalog_registration.force_catalogd_active,
document->GetAllocator());
+ Value standby_catalogd_registration_time(ToStringFromUnixMillis(
+ standby_catalog_registration.registration_time,
+ TimePrecision::Millisecond).c_str(), document->GetAllocator());
+ document->AddMember("standby_catalogd_registration_time",
+ standby_catalogd_registration_time, document->GetAllocator());
+ }
+
+ lock_guard<mutex> l(subscribers_lock_);
+ Value notified_subscribers(kArrayType);
+ for (const SubscriberMap::value_type& subscriber : subscribers_) {
+ // Only subscribers of type COORDINATOR, COORDINATOR_EXECUTOR, or CATALOGD
+ // need to be returned.
+ if (subscriber.second->IsSubscribedCatalogdChange()) {
+ Value sub_json(kObjectType);
+ Value subscriber_id(subscriber.second->id().c_str(),
document->GetAllocator());
+ sub_json.AddMember("id", subscriber_id, document->GetAllocator());
+ Value address(TNetworkAddressToString(
+ subscriber.second->network_address()).c_str(),
document->GetAllocator());
+ sub_json.AddMember("address", address, document->GetAllocator());
+ Value
registration_id(PrintId(subscriber.second->registration_id()).c_str(),
+ document->GetAllocator());
+ sub_json.AddMember("registration_id", registration_id,
document->GetAllocator());
+ Value subscriber_type(SubscriberTypeToString(
+ subscriber.second->subscriber_type()).c_str(),
document->GetAllocator());
+ sub_json.AddMember("subscriber_type", subscriber_type,
document->GetAllocator());
+ if (subscriber.second->catalogd_version() > 0) {
+ sub_json.AddMember("catalogd_version",
subscriber.second->catalogd_version(),
+ document->GetAllocator());
+ Value catalogd_address(TNetworkAddressToString(
+ subscriber.second->catalogd_address()).c_str(),
document->GetAllocator());
+ sub_json.AddMember("catalogd_address", catalogd_address,
+ document->GetAllocator());
+ Value last_subscriber_update_catalogd_time(ToStringFromUnixMillis(
+ subscriber.second->last_update_catalogd_time(),
+ TimePrecision::Millisecond).c_str(), document->GetAllocator());
+ sub_json.AddMember("last_subscriber_update_catalogd_time",
+ last_subscriber_update_catalogd_time, document->GetAllocator());
+ }
+
+ notified_subscribers.PushBack(sub_json, document->GetAllocator());
+ }
+ }
+ document->AddMember(
+ "notified_subscribers", notified_subscribers, document->GetAllocator());
+ return;
+}
diff --git a/be/src/statestore/statestore.h b/be/src/statestore/statestore.h
index 11b0fb6fe..949cac7fd 100644
--- a/be/src/statestore/statestore.h
+++ b/be/src/statestore/statestore.h
@@ -292,6 +292,9 @@ class Statestore : public CacheLineAligned {
return TNetworkAddressToString(peer_statestore_ha_addr_);
}
+ // Update the subscriber's catalog information.
+ void UpdateSubscriberCatalogInfo(const SubscriberId& subscriber_id);
+
private:
/// A TopicEntry is a single entry in a topic, and logically is a <string,
byte string>
/// pair.
@@ -508,6 +511,10 @@ class Statestore : public CacheLineAligned {
const TNetworkAddress& network_address() const { return network_address_; }
const SubscriberId& id() const { return subscriber_id_; }
const RegistrationId& registration_id() const { return registration_id_; }
+ TStatestoreSubscriberType::type subscriber_type() const { return
subscriber_type_; }
+ int64_t catalogd_version() const { return catalogd_version_; }
+ const TNetworkAddress& catalogd_address() const { return
catalogd_address_; }
+ int64_t last_update_catalogd_time() const { return
last_update_catalogd_time_; }
/// Returns the time elapsed (in seconds) since the last heartbeat.
double SecondsSinceHeartbeat() const {
@@ -571,6 +578,10 @@ class Statestore : public CacheLineAligned {
return subscribe_catalogd_change_;
}
+ /// The subscriber updates the catalog information.
+ void UpdateCatalogInfo(
+ int64_t catalogd_version, const TNetworkAddress& catalogd_address);
+
private:
/// Unique human-readable identifier for this subscriber, set by the
subscriber itself
/// on a Register call.
@@ -610,6 +621,15 @@ class Statestore : public CacheLineAligned {
/// True once DeleteAllTransientEntries() has been called during subscriber
/// unregisteration. Protected by 'transient_entry_lock_'
bool unregistered_ = false;
+
+ // Version of catalogd.
+ int64_t catalogd_version_ = 0L;
+
+ // Address of catalogd.
+ TNetworkAddress catalogd_address_;
+
+ // The last time to update the catalogd.
+ int64_t last_update_catalogd_time_ = 0L;
};
/// Unique identifier for this statestore instance.
@@ -1009,6 +1029,10 @@ class Statestore : public CacheLineAligned {
// Return true if this statestore instance is in recovery mode.
bool IsInRecoveryMode();
+
+ /// Json callback for /catalog_ha_info.
+ void CatalogHAInfoHandler(const Webserver::WebRequest& req,
+ rapidjson::Document* document);
};
} // namespace impala
diff --git a/common/thrift/StatestoreService.thrift
b/common/thrift/StatestoreService.thrift
index 69b73a26d..98d079b89 100644
--- a/common/thrift/StatestoreService.thrift
+++ b/common/thrift/StatestoreService.thrift
@@ -217,6 +217,9 @@ struct TCatalogRegistration {
// True if the catalogd instance is started as active instance.
4: optional bool force_catalogd_active;
+
+ // The registration time of the catalogd.
+ 5: optional i64 registration_time;
}
struct TRegisterSubscriberRequest {
diff --git a/tests/custom_cluster/test_catalogd_ha.py
b/tests/custom_cluster/test_catalogd_ha.py
index 244b8a58f..c2b5cdb0f 100644
--- a/tests/custom_cluster/test_catalogd_ha.py
+++ b/tests/custom_cluster/test_catalogd_ha.py
@@ -16,8 +16,10 @@
# under the License.
from __future__ import absolute_import, division, print_function
+import json
import logging
import re
+import requests
from tests.common.custom_cluster_test_suite import CustomClusterTestSuite
from tests.common.environ import build_flavor_timeout
@@ -35,6 +37,12 @@ class TestCatalogdHA(CustomClusterTestSuite):
statestored and catalogds are started with starting flag
FLAGS_enable_catalogd_ha
as true. """
+ VARZ_URL = "http://localhost:{0}/varz"
+ CATALOG_HA_INFO_URL = "http://localhost:{0}/catalog_ha_info"
+ JSON_METRICS_URL = "http://localhost:{0}/jsonmetrics"
+
+ SS_TEST_PORT = ["25010"]
+
def get_workload(self):
return 'functional-query'
@@ -461,3 +469,36 @@ class TestCatalogdHA(CustomClusterTestSuite):
self.execute_query_expect_success(
self.client, "select %s.identity_tmp(10)" % unique_database)
+
+ def test_page_with_disable_ha(self):
+ self.__test_catalog_ha_info_page()
+
+ @CustomClusterTestSuite.with_args(start_args="--enable_catalogd_ha")
+ def test_page_with_enable_ha(self):
+ self.__test_catalog_ha_info_page()
+
+ def __test_catalog_ha_info_page(self):
+ for port in self.SS_TEST_PORT:
+ response = requests.get(self.VARZ_URL.format(port) + "?json")
+ assert response.status_code == requests.codes.ok
+ varz_json = json.loads(response.text)
+ ha_flags = [e for e in varz_json["flags"]
+ if e["name"] == "enable_catalogd_ha"]
+ assert len(ha_flags) == 1
+ assert ha_flags[0]["default"] == "false"
+
+ # High availability for the Catalog is enabled.
+ if ha_flags[0]["current"] == "true":
+ url = self.JSON_METRICS_URL.format(port) + "?json"
+ metrics = json.loads(requests.get(url).text)
+ if metrics["statestore.active-status"]:
+ url = self.CATALOG_HA_INFO_URL.format(port) + "?json"
+ catalog_ha_info = json.loads(requests.get(url).text)
+ assert catalog_ha_info["active_catalogd_address"]\
+ == metrics["statestore.active-catalogd-address"]
+ else:
+ reponse = requests.get(self.CATALOG_HA_INFO_URL.format(port)).text
+ assert reponse.__contains__("The current statestored is inactive.")
+ else:
+ page = requests.get(self.CATALOG_HA_INFO_URL.format(port))
+ assert page.status_code == requests.codes.not_found
diff --git a/www/catalog_ha_info.tmpl b/www/catalog_ha_info.tmpl
new file mode 100644
index 000000000..e2376b3ec
--- /dev/null
+++ b/www/catalog_ha_info.tmpl
@@ -0,0 +1,84 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+{{> www/common-header.tmpl }}
+{{#is_active_statestored}}
+<h2>HA Info</h2>
+<pre>
+<b>has-active-catalogd:</b> {{has_active_catalogd}}
+<b>active-catalogd-version:</b> {{active_catalogd_version}}
+<b>active-catalogd-address:</b> {{active_catalogd_address}}
+<b>last-update-catalogd-time:</b> {{last_update_catalogd_time}}
+</pre>
+<h2>Active Catalogd Info</h2>
+<pre>
+<b>enable-catalogd-ha:</b> {{active_catalogd_enable_catalogd_ha}}
+<b>address:</b> {{active_catalogd_address}}
+<b>force-catalogd-active:</b> {{active_catalogd_force_catalogd_active}}
+<b>registration-time:</b> {{active_catalogd_registration_time}}
+</pre>
+<h2>Standby Catalogd Info</h2>
+<pre>
+<b>enable-catalogd-ha:</b> {{standby_catalogd_enable_catalogd_ha}}
+<b>address:</b> {{standby_catalogd_address}}
+<b>force-catalogd-active:</b> {{standby_catalogd_force_catalogd_active}}
+<b>registration-time:</b> {{standby_catalogd_registration_time}}
+</pre>
+<h2>Notified Subscribers ({{%notified_subscribers}} total)</h2>
+ <table id="notified-subscribers-tbl" class='table table-hover table-striped'>
+ <thead>
+ <tr>
+ <th>Id</th>
+ <th>Address</th>
+ <th>Registration ID</th>
+ <th>Subscriber Type</th>
+ <th>Catalogd Version</th>
+ <th>Catalogd Address</th>
+ <th>Last Update Catalogd Time</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#notified_subscribers}}
+ <tr>
+ <td>{{id}}</td>
+ <td>{{address}}</td>
+ <td>{{registration_id}}</td>
+ <td>{{subscriber_type}}</td>
+ <td>{{catalogd_version}}</td>
+ <td>{{catalogd_address}}</td>
+ <td>{{last_subscriber_update_catalogd_time}}</td>
+ </tr>
+ {{/notified_subscribers}}
+ </tbody>
+ </table>
+{{/is_active_statestored}}
+
+{{^is_active_statestored}}
+<h5>The current statestored is inactive. Please refer to the active
+statestored for the catalog's high availability information.</h5>
+{{/is_active_statestored}}
+<script>
+ $(document).ready(function() {
+ $('#notified-subscribers-tbl').DataTable({
+ "order": [[ 6, "asc" ]],
+ "pageLength": 100
+ });
+ });
+</script>
+
+{{> www/common-footer.tmpl }}