E2SAR 0.3.0
Loading...
Searching...
No Matches
e2sarUtil.hpp
1#ifndef E2SARUTILHPP
2#define E2SARUTILHPP
3
4#include <fstream>
5#include <vector>
6#include <boost/url.hpp>
7#include <boost/algorithm/string.hpp>
8#include <boost/asio.hpp>
9#include <boost/chrono.hpp>
10#include <boost/thread.hpp>
11#include <boost/log/trivial.hpp>
12#include <boost/log/common.hpp>
13#include <boost/log/sinks.hpp>
14#include <boost/log/sources/logger.hpp>
15#include <boost/log/expressions.hpp>
16#include <boost/log/attributes.hpp>
17#include <boost/log/support/date_time.hpp>
18#include <boost/core/null_deleter.hpp>
19#include <boost/shared_ptr.hpp>
20
21#include "grpc/loadbalancer.grpc.pb.h"
22
23#include "e2sarError.hpp"
24
25using namespace boost::asio;
26using namespace std::string_literals;
27using namespace boost::log;
28
29#define BOOST_LOG_FLUSH() sink->flush()
30#define BOOST_MLL_START(PREF) { std::ostringstream PREF_ostr;
31#define BOOST_MLL_LOG(PREF) PREF_ostr
32#define BOOST_MLL_STOP(PREF) BOOST_LOG_SEV(lg, trivial::info) << PREF_ostr.str(); } BOOST_LOG_FLUSH();
33
34#define BOOST_LOG_INFO() BOOST_LOG_SEV(lg, trivial::info)
35#define BOOST_LOG_WARN() BOOST_LOG_SEV(lg, trivial::warning)
36#define BOOST_LOG_ERR() BOOST_LOG_SEV(lg, trivial::error)
37
38/***
39 * Supporting classes for E2SAR
40 */
41namespace e2sar
42{
43 typedef sinks::asynchronous_sink<sinks::text_ostream_backend> text_sink;
44 extern boost::shared_ptr<text_sink> sink;
45 extern sources::severity_logger_mt<trivial::severity_level> lg;
46
47 const u_int16_t DATAPLANE_PORT = 19522;
48
56 {
57 public:
58 enum class TokenType: u_int16_t {
59 // contains overloaded names according to old and new token type hierarchy
60 all=0, admin=1, load_balancer=admin, instance=2, reservation=instance, session=3, END
61 };
62 inline static constexpr size_t ttAsIdx(TokenType tt)
63 {
64 return static_cast<size_t>(tt);
65 }
66 inline static const std::string toString(TokenType tt)
67 {
68 switch(tt) {
69 case TokenType::all: return "ALL"s;
70 case TokenType::admin: return "LOAD_BALANCER"s;
71 case TokenType::instance: return "RESERVATION"s;
72 case TokenType::session: return "SESSION"s;
73 default: return "UNKNOWN"s;
74 }
75 }
76 static const size_t tokenTypeCardinality = static_cast<size_t>(TokenType::END);
77
78
79 enum class TokenPermission: u_int16_t {
80 // 'register' is a keyword in C++, so adding '_' to names
81 _read_only_, _register_, _reserve_, _update_, END
82 };
83 inline static const std::string toString(TokenPermission tt)
84 {
85 switch(tt) {
86 case TokenPermission::_read_only_: return "READ"s;
87 case TokenPermission::_register_: return "REGISTER"s;
88 case TokenPermission::_reserve_: return "RESERVE"s;
89 case TokenPermission::_update_: return "UPDATE"s;
90 default: return "UNKNOWN"s;
91 }
92 }
93 static const size_t tokenPermissionCardinality = static_cast<size_t>(TokenPermission::END);
94
95 private:
96 std::string rawURI;
98 bool haveDatav4;
99 bool haveDatav6;
101 bool haveSync;
103 bool useTls;
105 bool preferV6;
106
108 u_int16_t syncPort;
110 u_int16_t cpPort;
112 u_int16_t dataPort;
113
115 std::string lbName;
117 std::string lbId;
119 std::array<std::string, tokenTypeCardinality> tokensByType;
121 std::string sessionId;
122
124 ip::address dataAddrv4;
125 ip::address dataAddrv6;
127 ip::address syncAddr;
129 ip::address cpAddr;
130 std::string cpHost;
131
132 public:
139 EjfatURI(const std::string &uri, TokenType tt=TokenType::admin, bool preferV6=false);
140
143
144 friend bool operator== (const EjfatURI &u1, const EjfatURI &u2);
145
146 friend inline bool operator!= (const EjfatURI &u1, const EjfatURI &u2)
147 {
148 return !(u1 == u2);
149 }
150
152 inline bool get_useTls() const
153 {
154 return useTls;
155 }
156
158 inline void set_Token(const std::string &t, TokenType tt)
159 {
160 tokensByType[ttAsIdx(tt)] = t;
161 }
162
164 inline void set_InstanceToken(const std::string &t)
165 {
166 tokensByType[ttAsIdx(TokenType::instance)] = t;
167 }
168
170 inline void set_SessionToken(const std::string &t)
171 {
172 tokensByType[ttAsIdx(TokenType::session)] = t;
173 }
174
176 inline const result<std::string> get_InstanceToken() const
177 {
178 auto idx = ttAsIdx(TokenType::instance);
179 if (!tokensByType[idx].empty())
180 return tokensByType[idx];
181 else
182 return E2SARErrorInfo{E2SARErrorc::ParameterNotAvailable, "Instance token not available"s};
183 }
184
186 inline const result<std::string> get_SessionToken() const
187 {
188 auto idx = ttAsIdx(TokenType::session);
189 if (!tokensByType[idx].empty())
190 return tokensByType[idx];
191 else
192 return E2SARErrorInfo{E2SARErrorc::ParameterNotAvailable, "Session token not available"s};
193 }
194
196 inline const result<std::string> get_AdminToken() const
197 {
198 auto idx = ttAsIdx(TokenType::admin);
199 if (!tokensByType[idx].empty())
200 return tokensByType[idx];
201 else
202 return E2SARErrorInfo{E2SARErrorc::ParameterNotAvailable, "Admin token not available"s};
203 }
204
206 inline void set_lbName(const std::string &n)
207 {
208 lbName = n;
209 }
210
212 inline void set_lbId(const std::string &i)
213 {
214 lbId = i;
215 }
216
218 inline void set_sessionId(const std::string &i)
219 {
220 sessionId = i;
221 }
222
226 inline void set_syncAddr(const std::pair<ip::address, u_int16_t> &a)
227 {
228 syncAddr = a.first;
229 syncPort = a.second;
230 haveSync = true;
231 }
232
236 inline void set_dataAddr(const std::pair<ip::address, u_int16_t> &a)
237 {
238 if (a.first.is_v4()) {
239 dataAddrv4 = a.first;
240 haveDatav4 = true;
241 }
242 else {
243 dataAddrv6 = a.first;
244 haveDatav6 = true;
245 }
246 }
247
249 inline const std::string get_lbName() const
250 {
251 return lbName;
252 }
253
255 inline const std::string get_lbId() const
256 {
257 return lbId;
258 }
259
261 inline const std::string get_sessionId() const
262 {
263 return sessionId;
264 }
265
267 inline const result<std::pair<ip::address, u_int16_t>> get_cpAddr() const
268 {
269 return std::pair<ip::address, u_int16_t>(cpAddr, cpPort);
270 }
271
273 inline const result<std::pair<std::string, u_int16_t>> get_cpHost() const
274 {
275 if (!cpHost.empty())
276 return std::pair<std::string, u_int16_t>(cpHost, cpPort);
277 else
278 return E2SARErrorInfo{E2SARErrorc::ParameterNotAvailable, "Control plane hostname not available"s};
279 }
280
282 inline const bool has_dataAddrv4() const
283 {
284 return haveDatav4;
285 }
286
288 inline const bool has_dataAddrv6() const
289 {
290 return haveDatav6;
291 }
292
294 inline const bool has_dataAddr() const
295 {
296 return haveDatav4 || haveDatav6;
297 }
298
300 inline const bool has_syncAddr() const
301 {
302 return haveSync;
303 }
304
306 inline const result<std::pair<ip::address, u_int16_t>> get_dataAddrv4() const noexcept
307 {
308 if (haveDatav4)
309 return std::pair<ip::address, u_int16_t>(dataAddrv4, dataPort);
310 return E2SARErrorInfo{E2SARErrorc::ParameterNotAvailable, "Data plane address not available"s};
311 }
312
314 inline const result<std::pair<ip::address, u_int16_t>> get_dataAddrv6() const noexcept
315 {
316 if (haveDatav6)
317 return std::pair<ip::address, u_int16_t>(dataAddrv6, dataPort);
318 return E2SARErrorInfo{E2SARErrorc::ParameterNotAvailable, "Data plane address not available"s};
319 }
320
322 inline const result<std::pair<ip::address, u_int16_t>> get_syncAddr() const noexcept
323 {
324 if (haveSync)
325 return std::pair<ip::address, u_int16_t>(syncAddr, syncPort);
326 return E2SARErrorInfo{E2SARErrorc::ParameterNotAvailable, "Sync address not available"s};
327 }
332 operator std::string() const;
333
334 const std::string to_string(TokenType tt = TokenType::admin) const;
335
341 static inline result<EjfatURI> getFromEnv(const std::string &envVar = "EJFAT_URI"s,
342 TokenType tt=TokenType::admin, bool preferV6=false) noexcept
343 {
344 const char *envStr = std::getenv(envVar.c_str());
345 if (envStr != nullptr)
346 {
347 try
348 {
349 return EjfatURI(envStr, tt, preferV6);
350 }
351 catch (const E2SARException &e)
352 {
353 return E2SARErrorInfo{E2SARErrorc::CaughtException, "Unable to parse EJFAT_URI from environment variable: "s + static_cast<std::string>(e)};
354 }
355 }
356 return E2SARErrorInfo{E2SARErrorc::Undefined, "Environment variable "s + envVar + " not defined."s};
357 }
358
364 static inline result<EjfatURI> getFromString(const std::string &uriStr,
365 TokenType tt=TokenType::admin, bool preferV6=false) noexcept
366 {
367 try
368 {
369 return EjfatURI(uriStr, tt, preferV6);
370 }
371 catch (const E2SARException &e)
372 {
373 return E2SARErrorInfo{E2SARErrorc::CaughtException, "Unable to parse URI from string: "s + static_cast<std::string>(e)};
374 }
375 }
376
382 static inline result<EjfatURI> getFromFile(const std::string &fileName = "/tmp/ejfat_uri"s,
383 TokenType tt=TokenType::admin, bool preferV6=false) noexcept
384 {
385 if (!fileName.empty())
386 {
387 std::ifstream file(fileName);
388 if (file.is_open())
389 {
390 std::string uriLine;
391 if (std::getline(file, uriLine))
392 {
393 file.close();
394 try
395 {
396 return EjfatURI(uriLine, tt, preferV6);
397 }
398 catch (const E2SARException &e)
399 {
400 return E2SARErrorInfo{E2SARErrorc::CaughtException, "Unable to parse URI: "s + static_cast<std::string>(e)};
401 }
402 }
403 file.close();
404 return E2SARErrorInfo{E2SARErrorc::Undefined, "Unable to parse URI."s};
405 }
406 }
407 return E2SARErrorInfo{E2SARErrorc::NotFound, "Unable to find file "s + fileName};
408 }
409
415 result<std::vector<ip::address>> getDataplaneLocalAddresses(bool v6=false) noexcept;
416 };
417
421 static inline const result<ip::address> string_to_ip(const std::string &addr) noexcept
422 {
423 try
424 {
425 if (addr[0] == '[')
426 {
427 // strip '[]' from IPv6
428 try
429 {
430 ip::make_address(addr.substr(1, addr.length() - 2));
431 }
432 catch (...)
433 {
434 ;
435 }
436 return ip::make_address(addr.substr(1, addr.length() - 2));
437 }
438 else
439 return ip::make_address(addr);
440 }
441 catch (boost::system::system_error &e)
442 {
443 return E2SARErrorInfo{E2SARErrorc::ParameterError, "Unable to convert IP address from "s + addr};
444 }
445 }
446
450 static inline const result<u_int16_t> string_to_port(const std::string &port_string) noexcept
451 {
452 try
453 {
454 u_int16_t port = std::stoi(port_string);
455 if (port < 1024 || port > 65535)
456 {
457 // port is out of range
458 return E2SARErrorInfo{E2SARErrorc::OutOfRange, "Port value "s + port_string + " is out of range"s};
459 }
460 return port;
461 }
462 catch (const std::exception &e)
463 {
464 return E2SARErrorInfo{E2SARErrorc::ParameterError, "Unable to convert "s + port_string + " to integer"s};
465 }
466 }
467
471 static inline const result<std::pair<ip::address, u_int16_t>> string_tuple_to_ip_and_port(const std::string &t) noexcept
472 {
473 // search for last ":" (ip:port) or "]" (ipv6 by itself) whichever comes last
474 auto const pos = t.find_last_of("]:");
475
476 // IPv4 or IPv6 by itself
477 if ((pos == std::string::npos) || (t[pos] == ']'))
478 {
479 auto r1 = string_to_ip(t);
480 if (r1)
481 return std::pair<ip::address, u_int16_t>(r1.value(), 0);
482 else
483 return E2SARErrorInfo{E2SARErrorc::ParameterError, "Unable to convert "s + t + " to ip address and port"s};
484 }
485
486 // port with either IPv4 or IPv6 address x.x.x.x:num or [x:x:x:x::y]:num
487 auto r1 = string_to_ip(t.substr(0, pos));
488 auto r2 = string_to_port(t.substr(pos + 1));
489 if (r1 && r2)
490 return std::pair<ip::address, int>(r1.value(), r2.value());
491 return E2SARErrorInfo{E2SARErrorc::ParameterError, "Unable to convert "s + t + " to ip address and port"s};
492 }
493
500 static inline result<std::vector<ip::address>> resolveHost(const std::string &host_name) noexcept
501 {
502
503 std::vector<ip::address> addresses;
504 boost::asio::io_context io_context;
505
506 try
507 {
508 ip::udp::resolver resolver(io_context);
509 ip::udp::resolver::results_type results = resolver.resolve(host_name, "443");
510
511 for(auto i = results.begin(); i != results.end(); ++i)
512 {
513 ip::udp::endpoint endpoint = *i;
514 addresses.push_back(endpoint.address());
515 }
516 return addresses;
517 }
518 catch (...)
519 {
520 // anything happens - we can't find the host
521 return E2SARErrorInfo{E2SARErrorc::NotFound, "Unable to convert "s + host_name + " to ip address"s};
522 }
523 }
524
525 // to support unordered maps of pairs
526 struct pair_hash {
527 std::size_t operator()(const std::pair<u_int64_t, u_int16_t>& p) const {
528 u_int64_t hash1 = p.first;
529 u_int64_t tmp = p.second;
530 u_int64_t hash2 = tmp | tmp << 16 | tmp << 32 | tmp << 48;
531 return hash1 ^ hash2; // Combine the two hashes
532 }
533 };
534
535 struct pair_equal {
536 bool operator()(const std::pair<u_int64_t, u_int16_t>& lhs, const std::pair<u_int64_t, u_int16_t>& rhs) const {
537 return lhs.first == rhs.first && lhs.second == rhs.second;
538 }
539 };
540
549 static inline float clockEntropyTest(int totalTests = 1000, int sleepMs = 1)
550 {
551 std::vector<int_least64_t> points;
552 std::vector<int> bins(256, 0);
553
554 for (int i = 0; i < totalTests; i++)
555 {
556 auto now = boost::chrono::system_clock::now();
557 auto nowUsec = boost::chrono::duration_cast<boost::chrono::microseconds>(now.time_since_epoch()).count();
558 bins[nowUsec & 0xff]++;
559 auto until = now + boost::chrono::milliseconds(sleepMs);
560 boost::this_thread::sleep_until(until);
561 }
562
563 // compute the probabilities and entropy
564 float entropy{0.0};
565 for (size_t i = 0; i < bins.size(); i++)
566 {
567 float prob = static_cast<float>(bins[i])/(totalTests*1.0);
568 entropy += prob * std::log(prob);
569 }
570
571 // normalize into bits
572 entropy *= -1.0/std::log(2);
573 return entropy;
574 }
575
576 template<typename Container>
577 std::string concatWithSeparator(const Container& c, const std::string& sep=","s)
578 {
579 typename Container::const_iterator it = c.begin();
580 std::string rets{};
581 while(it != c.end())
582 {
583 rets += *it;
584 if (++it != c.end())
585 rets += sep;
586 }
587 return rets;
588 }
589
590 // busy wait for a given number of microseconds using high_resolution_clock timepoints
591 inline void busyWaitUsecs(const boost::chrono::steady_clock::time_point &tp, int64_t usecs)
592 {
593 while(true)
594 {
595 // busy wait checking the clock
596 if (boost::chrono::duration_cast<boost::chrono::microseconds>(boost::chrono::high_resolution_clock::now()
597 - tp).count() > usecs)
598 break;
599 }
600 }
601
602 using OptimizationsWord = u_int16_t;
608 public:
609 // go as powers of 2 to make calculations easier
610 enum class Code {
611 none = 0,
612 sendmmsg = 1,
613 liburing_send = 2,
614 liburing_recv = 3,
615 // always last
616 unknown = 15
617 };
618 public:
619 inline static OptimizationsWord toWord(Code o)
620 {
621 return 1 << static_cast<int>(o);
622 }
623 inline static std::string toString(Code o)
624 {
625 switch(o)
626 {
627 case Code::none: return "none"s;
628 case Code::sendmmsg: return "sendmmsg";
629 case Code::liburing_recv: return "liburing_recv";
630 case Code::liburing_send: return "liburing_send";
631 default: "unknown"s;
632 }
633 return "unknown"s;
634 }
635 inline static Code fromString(const std::string& opt)
636 {
637 if (opt == "none"s)
638 return Code::none;
639 else if (opt == "sendmmsg"s)
640 return Code::sendmmsg;
641 else if (opt == "liburing_recv"s)
642 return Code::liburing_recv;
643 else if (opt == "liburing_send"s)
644 return Code::liburing_send;
645 return Code::unknown;
646 }
650 const static std::vector<std::string> availableAsStrings() noexcept;
651
655 const static OptimizationsWord availableAsWord() noexcept;
656
661 static result<int> select(std::vector<std::string>& opt) noexcept;
662
667 static result <int> select(std::vector<Code> &opt) noexcept;
668
672 const static std::vector<std::string> selectedAsStrings() noexcept;
673
677 const static OptimizationsWord selectedAsWord() noexcept;
678
682 const static std::vector<Code> selectedAsList() noexcept;
683
687 const static bool isSelected(Code o) noexcept;
688 private:
689 // all available compiled in optimizations
690 static const std::vector<Optimizations::Code> available;
691
692 // this is where we store selected optimizations;
693 OptimizationsWord selected_optimizations;
694
695 // c-tor is private
696 Optimizations():selected_optimizations{toWord(Code::none)}
697 {}
698 // d-tor doesn't exist
699 ~Optimizations() = delete;
700
701 static Optimizations* instance;
702 static inline Optimizations* _get()
703 {
704 if (not instance)
705 instance = new Optimizations();
706 return instance;
707 }
708 };
709
715 std::string expandTilde(const std::string& path);
716
720 void defineClogLogger();
721};
722#endif
Definition e2sarError.hpp:62
Definition e2sarUtil.hpp:56
EjfatURI(const std::string &uri, TokenType tt=TokenType::admin, bool preferV6=false)
Definition e2sarUtil.cpp:189
const result< std::pair< ip::address, u_int16_t > > get_syncAddr() const noexcept
Definition e2sarUtil.hpp:322
const result< std::string > get_SessionToken() const
Definition e2sarUtil.hpp:186
void set_lbName(const std::string &n)
Definition e2sarUtil.hpp:206
~EjfatURI()
Definition e2sarUtil.hpp:142
bool get_useTls() const
Definition e2sarUtil.hpp:152
const bool has_dataAddrv4() const
Definition e2sarUtil.hpp:282
void set_InstanceToken(const std::string &t)
Definition e2sarUtil.hpp:164
const result< std::pair< ip::address, u_int16_t > > get_dataAddrv6() const noexcept
Definition e2sarUtil.hpp:314
const std::string get_lbId() const
Definition e2sarUtil.hpp:255
const result< std::pair< std::string, u_int16_t > > get_cpHost() const
Definition e2sarUtil.hpp:273
static result< EjfatURI > getFromEnv(const std::string &envVar="EJFAT_URI"s, TokenType tt=TokenType::admin, bool preferV6=false) noexcept
Definition e2sarUtil.hpp:341
const bool has_syncAddr() const
Definition e2sarUtil.hpp:300
void set_sessionId(const std::string &i)
Definition e2sarUtil.hpp:218
const result< std::string > get_AdminToken() const
Definition e2sarUtil.hpp:196
void set_Token(const std::string &t, TokenType tt)
Definition e2sarUtil.hpp:158
const result< std::pair< ip::address, u_int16_t > > get_cpAddr() const
Definition e2sarUtil.hpp:267
const std::string get_sessionId() const
Definition e2sarUtil.hpp:261
void set_dataAddr(const std::pair< ip::address, u_int16_t > &a)
Definition e2sarUtil.hpp:236
const result< std::string > get_InstanceToken() const
Definition e2sarUtil.hpp:176
static result< EjfatURI > getFromFile(const std::string &fileName="/tmp/ejfat_uri"s, TokenType tt=TokenType::admin, bool preferV6=false) noexcept
Definition e2sarUtil.hpp:382
void set_SessionToken(const std::string &t)
Definition e2sarUtil.hpp:170
const bool has_dataAddrv6() const
Definition e2sarUtil.hpp:288
static result< EjfatURI > getFromString(const std::string &uriStr, TokenType tt=TokenType::admin, bool preferV6=false) noexcept
Definition e2sarUtil.hpp:364
result< std::vector< ip::address > > getDataplaneLocalAddresses(bool v6=false) noexcept
Definition e2sarUtil.cpp:386
const bool has_dataAddr() const
Definition e2sarUtil.hpp:294
const result< std::pair< ip::address, u_int16_t > > get_dataAddrv4() const noexcept
Definition e2sarUtil.hpp:306
void set_syncAddr(const std::pair< ip::address, u_int16_t > &a)
Definition e2sarUtil.hpp:226
const std::string get_lbName() const
Definition e2sarUtil.hpp:249
void set_lbId(const std::string &i)
Definition e2sarUtil.hpp:212
Definition e2sarUtil.hpp:607
Definition e2sar.hpp:11
std::string expandTilde(const std::string &path)
Definition e2sarUtil.cpp:418
void defineClogLogger()
Definition e2sarUtil.cpp:440
Definition e2sarError.hpp:42
Definition e2sarUtil.hpp:535
Definition e2sarUtil.hpp:526