Coverage Report

Created: 2026-10-15 19:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/doris/be/src/agent/heartbeat_server.cpp
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#include "agent/heartbeat_server.h"
19
20
#include <gen_cpp/HeartbeatService.h>
21
#include <gen_cpp/HeartbeatService_types.h>
22
#include <gen_cpp/Types_types.h>
23
#include <glog/logging.h>
24
25
#include <memory>
26
#include <ostream>
27
#include <string>
28
#include <vector>
29
30
#include "cloud/cloud_cluster_info.h"
31
#include "cloud/cloud_tablet_mgr.h"
32
#include "cloud/config.h"
33
#include "common/config.h"
34
#include "common/status.h"
35
#include "runtime/cluster_info.h"
36
#include "runtime/exec_env.h"
37
#include "runtime/fragment_mgr.h"
38
#include "runtime/heartbeat_flags.h"
39
#include "service/backend_options.h"
40
#include "storage/storage_engine.h"
41
#include "util/debug_util.h"
42
#include "util/mem_info.h"
43
#include "util/network_util.h"
44
#include "util/thrift_server.h"
45
#include "util/time.h"
46
47
namespace apache {
48
namespace thrift {
49
class TProcessor;
50
} // namespace thrift
51
} // namespace apache
52
53
namespace doris {
54
55
HeartbeatServer::HeartbeatServer(ClusterInfo* cluster_info)
56
0
        : _engine(ExecEnv::GetInstance()->storage_engine()),
57
0
          _cluster_info(cluster_info),
58
0
          _fe_epoch(0) {
59
0
    _be_epoch = GetCurrentTimeMicros() / 1000;
60
0
}
61
62
0
void HeartbeatServer::init_cluster_id() {
63
0
    _cluster_info->cluster_id = _engine.effective_cluster_id();
64
0
}
65
66
void HeartbeatServer::heartbeat(THeartbeatResult& heartbeat_result,
67
0
                                const TMasterInfo& master_info) {
68
    //print heartbeat in every minute
69
0
    LOG_EVERY_N(INFO, 12) << "get heartbeat from FE."
70
0
                          << "host:" << master_info.network_address.hostname
71
0
                          << ", rpc port:" << master_info.network_address.port
72
0
                          << ", cluster id:" << master_info.cluster_id
73
0
                          << ", frontend_info:" << PrintFrontendInfos(master_info.frontend_infos)
74
0
                          << ", counter:" << google::COUNTER << ", BE start time: " << _be_epoch;
75
76
0
    MonotonicStopWatch watch;
77
0
    watch.start();
78
    // do heartbeat
79
0
    Status st = _heartbeat(master_info);
80
0
    st.to_thrift(&heartbeat_result.status);
81
82
0
    if (st.ok()) {
83
0
        heartbeat_result.backend_info.__set_be_port(config::be_port);
84
0
        heartbeat_result.backend_info.__set_http_port(config::webserver_port);
85
0
        heartbeat_result.backend_info.__set_be_rpc_port(-1);
86
0
        heartbeat_result.backend_info.__set_brpc_port(config::brpc_port);
87
0
        heartbeat_result.backend_info.__set_arrow_flight_sql_port(config::arrow_flight_sql_port);
88
0
        heartbeat_result.backend_info.__set_version(get_short_version());
89
0
        heartbeat_result.backend_info.__set_be_start_time(_be_epoch);
90
0
        heartbeat_result.backend_info.__set_be_node_role(config::be_node_role);
91
        // If be is gracefully stop, then k_doris_exist is set to true
92
0
        heartbeat_result.backend_info.__set_is_shutdown(doris::k_doris_exit);
93
0
        heartbeat_result.backend_info.__set_fragment_executing_count(
94
0
                get_fragment_executing_count());
95
0
        heartbeat_result.backend_info.__set_fragment_last_active_time(
96
0
                get_fragment_last_active_time());
97
0
        heartbeat_result.backend_info.__set_be_mem(MemInfo::physical_mem());
98
        // Reader enum support cannot be inferred from a release version during rolling upgrades.
99
0
        heartbeat_result.backend_info.__set_supports_paimon_rust_reader(true);
100
0
    }
101
0
    watch.stop();
102
0
    if (watch.elapsed_time() > 1000L * 1000L * 1000L) {
103
0
        LOG(WARNING) << "heartbeat consume too much time. time=" << watch.elapsed_time()
104
0
                     << ", host:" << master_info.network_address.hostname
105
0
                     << ", port:" << master_info.network_address.port
106
0
                     << ", cluster id:" << master_info.cluster_id
107
0
                     << ", frontend_info:" << PrintFrontendInfos(master_info.frontend_infos)
108
0
                     << ", counter:" << google::COUNTER << ", BE start time: " << _be_epoch;
109
0
    }
110
0
}
111
112
0
Status HeartbeatServer::_heartbeat(const TMasterInfo& master_info) {
113
0
    std::lock_guard<std::mutex> lk(_hb_mtx);
114
115
    // Check cluster id
116
0
    if (_cluster_info->cluster_id == -1) {
117
0
        LOG(INFO) << "get first heartbeat. update cluster id";
118
        // write and update cluster id
119
0
        RETURN_IF_ERROR(_engine.set_cluster_id(master_info.cluster_id));
120
121
0
        _cluster_info->cluster_id = master_info.cluster_id;
122
0
        LOG(INFO) << "record cluster id. host: " << master_info.network_address.hostname
123
0
                  << ". port: " << master_info.network_address.port
124
0
                  << ". cluster id: " << master_info.cluster_id
125
0
                  << ". frontend_infos: " << PrintFrontendInfos(master_info.frontend_infos);
126
0
    } else {
127
0
        if (_cluster_info->cluster_id != master_info.cluster_id) {
128
0
            return Status::InternalError(
129
0
                    "invalid cluster id. ignore. Record cluster id ={}, record frontend info {}. "
130
0
                    "Invalid cluster_id={}, invalid frontend info {}",
131
0
                    _cluster_info->cluster_id,
132
0
                    PrintFrontendInfos(ExecEnv::GetInstance()->get_frontends()),
133
0
                    master_info.cluster_id, PrintFrontendInfos(master_info.frontend_infos));
134
0
        }
135
0
    }
136
137
0
    if (master_info.__isset.backend_ip) {
138
        // master_info.backend_ip may be an IP or domain name, and it should be renamed 'backend_host', as it requires compatibility with historical versions, the name is still 'backend_ ip'
139
0
        if (master_info.backend_ip != BackendOptions::get_localhost()) {
140
0
            LOG(INFO) << master_info.backend_ip << " not equal to to backend localhost "
141
0
                      << BackendOptions::get_localhost();
142
            // step1: check master_info.backend_ip is IP or FQDN
143
0
            if (!is_valid_ip(master_info.backend_ip)) {
144
                //step2: resolve FQDN to IP
145
0
                std::string ip;
146
0
                Status status =
147
0
                        hostname_to_ip(master_info.backend_ip, ip, BackendOptions::is_bind_ipv6());
148
0
                if (!status.ok()) {
149
0
                    std::stringstream ss;
150
0
                    ss << "can not get ip from fqdn: " << status.to_string();
151
0
                    LOG(WARNING) << ss.str();
152
0
                    return status;
153
0
                }
154
0
                LOG(INFO) << "master_info.backend_ip: " << master_info.backend_ip
155
0
                          << ", hostname_to_ip: " << ip;
156
                //step3: get all ips of the interfaces on this machine
157
0
                std::vector<InetAddress> hosts;
158
0
                status = get_hosts(&hosts);
159
0
                if (!status.ok() || hosts.empty()) {
160
0
                    return Status::InternalError(
161
0
                            "the status was not ok when get_hosts, error is {}",
162
0
                            status.to_string());
163
0
                }
164
165
                //step4: check if the IP of FQDN belongs to the current machine and update BackendOptions._s_localhost
166
0
                bool set_new_localhost = false;
167
0
                for (auto& addr : hosts) {
168
0
                    if (addr.get_host_address() == ip) {
169
0
                        BackendOptions::set_localhost(master_info.backend_ip);
170
0
                        set_new_localhost = true;
171
0
                        break;
172
0
                    }
173
0
                }
174
175
0
                if (!set_new_localhost) {
176
0
                    return Status::InternalError(
177
0
                            "the host recorded in master is {}, but we cannot found the local ip "
178
0
                            "that mapped to that host. backend={}",
179
0
                            master_info.backend_ip, BackendOptions::get_localhost());
180
0
                }
181
0
            } else {
182
                // if is ip,not check anything,use it
183
0
                BackendOptions::set_localhost(master_info.backend_ip);
184
0
            }
185
186
0
            LOG(WARNING) << "update localhost done, the new localhost is "
187
0
                         << BackendOptions::get_localhost();
188
0
        }
189
0
    }
190
191
0
    bool need_report = false;
192
0
    if (_cluster_info->master_fe_addr.hostname != master_info.network_address.hostname ||
193
0
        _cluster_info->master_fe_addr.port != master_info.network_address.port) {
194
0
        if (master_info.epoch > _fe_epoch) {
195
0
            _cluster_info->master_fe_addr.hostname = master_info.network_address.hostname;
196
0
            _cluster_info->master_fe_addr.port = master_info.network_address.port;
197
0
            _fe_epoch = master_info.epoch;
198
0
            need_report = true;
199
0
            LOG(INFO) << "master change. new master host: "
200
0
                      << _cluster_info->master_fe_addr.hostname
201
0
                      << ". port: " << _cluster_info->master_fe_addr.port
202
0
                      << ". epoch: " << _fe_epoch;
203
0
        } else {
204
0
            return Status::InternalError(
205
0
                    "epoch is not greater than local. ignore heartbeat. host: {}, port: {}, local "
206
0
                    "epoch: {}, received epoch: {}",
207
0
                    _cluster_info->master_fe_addr.hostname, _cluster_info->master_fe_addr.port,
208
0
                    _fe_epoch, master_info.epoch);
209
0
        }
210
0
    } else {
211
        // when Master FE restarted, host and port remains the same, but epoch will be increased.
212
0
        if (master_info.epoch > _fe_epoch) {
213
0
            _fe_epoch = master_info.epoch;
214
0
            need_report = true;
215
0
            LOG(INFO) << "master restarted. epoch: " << _fe_epoch;
216
0
        }
217
0
    }
218
219
0
    if (master_info.__isset.token) {
220
0
        if (_cluster_info->token == "") {
221
0
            _cluster_info->token = master_info.token;
222
0
            LOG(INFO) << "get token. token: " << _cluster_info->token;
223
0
        } else if (_cluster_info->token != master_info.token) {
224
0
            return Status::InternalError("invalid token. local: {}, master: {}",
225
0
                                         _cluster_info->token, master_info.token);
226
0
        }
227
0
    }
228
229
0
    if (master_info.__isset.http_port) {
230
0
        _cluster_info->master_fe_http_port = master_info.http_port;
231
0
    }
232
233
0
    if (master_info.__isset.heartbeat_flags) {
234
0
        HeartbeatFlags* heartbeat_flags = ExecEnv::GetInstance()->heartbeat_flags();
235
0
        heartbeat_flags->update(master_info.heartbeat_flags);
236
0
    }
237
238
0
    if (master_info.__isset.backend_id) {
239
0
        _cluster_info->backend_id = master_info.backend_id;
240
0
        BackendOptions::set_backend_id(master_info.backend_id);
241
0
    }
242
0
    if (master_info.__isset.frontend_infos) {
243
0
        ExecEnv::GetInstance()->update_frontends(master_info.frontend_infos);
244
0
    } else {
245
0
        LOG_EVERY_N(WARNING, 2) << fmt::format(
246
0
                "Heartbeat from {}:{} does not have frontend_infos, this may because we are "
247
0
                "upgrading cluster",
248
0
                master_info.network_address.hostname, master_info.network_address.port);
249
0
    }
250
251
0
    if (master_info.__isset.meta_service_endpoint != config::is_cloud_mode()) {
252
0
        LOG(WARNING) << "Detected mismatch in cloud mode configuration between FE and BE. "
253
0
                     << "FE cloud mode: "
254
0
                     << (master_info.__isset.meta_service_endpoint ? "true" : "false")
255
0
                     << ", BE cloud mode: " << (config::is_cloud_mode() ? "true" : "false")
256
0
                     << ". If fe is earlier than version 3.0.2, the message can be ignored.";
257
0
    }
258
259
0
    if (master_info.__isset.meta_service_endpoint) {
260
0
        if (config::meta_service_endpoint.empty() && !master_info.meta_service_endpoint.empty()) {
261
0
            auto st = config::set_config("meta_service_endpoint", master_info.meta_service_endpoint,
262
0
                                         true);
263
0
            LOG(INFO) << "set config meta_service_endpoint " << master_info.meta_service_endpoint
264
0
                      << " " << st;
265
0
        }
266
267
0
        if (master_info.meta_service_endpoint != config::meta_service_endpoint) {
268
0
            LOG(WARNING) << "Detected mismatch in meta_service_endpoint configuration between FE "
269
0
                            "and BE. "
270
0
                         << "FE meta_service_endpoint: " << master_info.meta_service_endpoint
271
0
                         << ", BE meta_service_endpoint: " << config::meta_service_endpoint;
272
0
            std::vector<std::string> old_endpoints =
273
0
                    doris::split(config::meta_service_endpoint, ",");
274
0
            std::vector<std::string> new_endpoints =
275
0
                    doris::split(master_info.meta_service_endpoint, ",");
276
0
            auto has_intersection = false;
277
0
            for (auto endpoint : new_endpoints) {
278
0
                if (std::find(old_endpoints.begin(), old_endpoints.end(), endpoint) !=
279
0
                    old_endpoints.end()) {
280
0
                    has_intersection = true;
281
0
                }
282
0
            }
283
0
            if (has_intersection) {
284
0
                auto st = config::set_config("meta_service_endpoint",
285
0
                                             master_info.meta_service_endpoint, true);
286
0
                LOG(INFO) << "change config meta_service_endpoint to "
287
0
                          << master_info.meta_service_endpoint << " " << st;
288
0
            }
289
0
            if (!has_intersection && config::enable_meta_service_endpoint_consistency_check) {
290
0
                return Status::InvalidArgument<false>(
291
0
                        "fe and be do not work in same mode or meta_service_endpoint mismatch,"
292
0
                        "fe meta_service_endpoint: {}, be meta_service_endpoint: {}",
293
0
                        master_info.meta_service_endpoint, config::meta_service_endpoint);
294
0
            }
295
0
        }
296
0
    }
297
298
0
    if (master_info.__isset.cloud_unique_id &&
299
0
        config::cloud_unique_id != master_info.cloud_unique_id &&
300
0
        config::enable_use_cloud_unique_id_from_fe) {
301
0
        auto st = config::set_config("cloud_unique_id", master_info.cloud_unique_id, true);
302
0
        LOG(INFO) << "set config cloud_unique_id " << master_info.cloud_unique_id << " " << st;
303
0
    }
304
305
0
    if (master_info.__isset.cloud_cluster_info) {
306
0
        auto* cloud_cluster_info = static_cast<CloudClusterInfo*>(_cluster_info);
307
0
        if (master_info.cloud_cluster_info.__isset.isStandby) {
308
0
            cloud_cluster_info->set_is_in_standby(master_info.cloud_cluster_info.isStandby);
309
0
        }
310
0
        if (master_info.cloud_cluster_info.__isset.cloud_compute_group_id &&
311
0
            !master_info.cloud_cluster_info.cloud_compute_group_id.empty()) {
312
0
            cloud_cluster_info->set_cloud_compute_group_id(
313
0
                    master_info.cloud_cluster_info.cloud_compute_group_id);
314
0
        }
315
0
    }
316
317
0
    if (master_info.__isset.tablet_report_inactive_duration_ms) {
318
0
        doris::g_tablet_report_inactive_duration_ms =
319
0
                master_info.tablet_report_inactive_duration_ms;
320
0
    }
321
322
0
    if (master_info.__isset.auth_token) {
323
0
        if (_cluster_info->curr_auth_token == "") {
324
0
            _cluster_info->curr_auth_token = master_info.auth_token;
325
0
            LOG(INFO) << "set new auth token: " << master_info.auth_token;
326
0
        } else if (_cluster_info->curr_auth_token != master_info.auth_token) {
327
0
            LOG(INFO) << "last auth token: " << _cluster_info->last_auth_token
328
0
                      << "set new auth token: " << master_info.auth_token;
329
0
            _cluster_info->last_auth_token = _cluster_info->curr_auth_token;
330
0
            _cluster_info->curr_auth_token = master_info.auth_token;
331
0
        }
332
0
    }
333
334
0
    if (need_report) {
335
0
        LOG(INFO) << "Master FE is changed or restarted. report tablet and disk info immediately";
336
0
        _engine.notify_listeners();
337
0
    }
338
339
0
    return Status::OK();
340
0
}
341
342
} // namespace doris