1: <?php
2: declare(strict_types=1);
3: /**
4: * +------------------------------------------------------------+
5: * | apnscp |
6: * +------------------------------------------------------------+
7: * | Copyright (c) Apis Networks |
8: * +------------------------------------------------------------+
9: * | Licensed under Artistic License 2.0 |
10: * +------------------------------------------------------------+
11: * | Author: Matt Saladna (msaladna@apisnetworks.com) |
12: * +------------------------------------------------------------+
13: */
14:
15: use Opcenter\Bandwidth\Site;
16:
17: /**
18: * Bandwidth statistics
19: *
20: * @package core
21: */
22: class Bandwidth_Module extends Module_Skeleton implements \Module\Skeleton\Contracts\Hookable
23: {
24: const DEPENDENCY_MAP = [
25: 'siteinfo',
26: 'apache'
27: ];
28: const BW_DIR = '/var/log/bw';
29:
30: protected $exportedFunctions = [
31: '*' => PRIVILEGE_SITE,
32: 'amnesty' => PRIVILEGE_ADMIN
33: ];
34:
35: /**
36: * Get bandwidth consumed by period
37: *
38: * @param $grouping string group bandwidth by 'month' or 'day'
39: * @return array|false
40: */
41: public function get_all_composite_bandwidth_data($grouping = 'month')
42: {
43: if (!\in_array($grouping, ['month', 'day'])) {
44: return error("invalid bw grouping `%s'", $grouping);
45: }
46:
47: $cachekey = 'bandwidth.gacbd.' . substr($grouping, 0, 2);
48:
49: $cache = Cache_Account::spawn($this->getAuthContext());
50: $bw = $cache->get($cachekey);
51: if ($bw !== false) {
52: return $bw;
53: }
54: $bandwidth = (new Site($this->site_id))->getByComposite($grouping);
55: $cache->set($cachekey, $bandwidth, 1800);
56:
57: return $bandwidth;
58: }
59:
60: /**
61: * Get bandwidth ranges
62: * Indexes:
63: * begin: start starting rollover date
64: * end: ending rollover date
65: *
66: * @return array
67: */
68: public function get_cycle_periods(): array
69: {
70:
71: $cachekey = 'bandwidth.gcp';
72: $cache = Cache_Account::spawn($this->getAuthContext());
73: $periods = $cache->get($cachekey);
74: if ($periods !== false) {
75: return $periods;
76: }
77:
78: $periods = (new Site($this->site_id))->rollovers();
79:
80: $cache->set($cachekey, $periods, 43200);
81:
82: return $periods;
83: }
84:
85: /**
86: * Retrieve day on which banwidth rolls over to 0
87: *
88: * @return int
89: */
90: public function rollover(): int
91: {
92: $rollover = (int)$this->getServiceValue('bandwidth', 'rollover');
93: $localtime = localtime(time(), true);
94: $today = date('j');
95: $month = ($rollover < $today ? ($localtime['tm_mon'] + 1) : $localtime['tm_mon']);
96:
97: return mktime(0, 0, 0, ++$month, $rollover);
98: }
99:
100: /**
101: * Get cumulative bandwidth consumption
102: *
103: * @privilege PRIVILEGE_SITE
104: * @param int $type type of bandwidth usage to retrieve
105: * @return array|bool indexes begin, rollover, and threshold
106: */
107: public function usage(int $type = null)
108: {
109: if (!$site_id = $this->site_id) {
110: Error_Reporter::report(var_export($this, true));
111:
112: return false;
113: }
114: if (!$this->bandwidth_enabled()) {
115: return ['used' => 0, 'total' => -1];
116: }
117: $pgdb = \PostgreSQL::initialize();
118: switch ($type) {
119: case null:
120: $bw_rs = $pgdb->query('SELECT
121: begindate,
122: rollover,
123: threshold as threshold
124: FROM bandwidth_spans
125: JOIN bandwidth USING (site_id)
126: WHERE
127: bandwidth_spans.site_id = ' . $site_id . '
128: AND
129: bandwidth_spans.enddate IS NULL
130: AND
131: bandwidth.site_id = ' . $site_id);
132: if (!$bw_rs->num_rows()) {
133: if ($this->enabled()) {
134: // no span present, hotfix for next request
135: // ideally, recurse but bandwidth record isn't set through here
136: $this->_autofix_bandwidth($site_id, (int)$this->getServiceValue('bandwidth', 'rollover'));
137: }
138: return array('used' => 0, 'total' => -1);
139: }
140: $bw_rs = $bw_rs->fetch_object();
141: if (!$bw_rs) {
142: return [
143: 'used' => 0,
144: 'total' => -1
145: ];
146: }
147: // @BUG account has no bandwidth enddate
148: if ($bw_rs && !$bw_rs->begindate && $this->enabled()) {
149: $ret = $this->_autofix_bandwidth($site_id,
150: (int)$this->getServiceValue('bandwidth', 'rollover'));
151: if (!$ret) {
152: return error("failed to autofix bandwidth for site `%d'", $this->site_id);
153: }
154:
155: return $this->usage($type);
156: }
157:
158: $used_rs = $pgdb->query('SELECT
159: SUM(in_bytes)+SUM(out_bytes) AS sum
160: FROM
161: bandwidth_log
162: WHERE
163: site_id = ' . $this->site_id . "
164: AND
165: ts >= '" . $bw_rs->begindate . "'");
166:
167: return array(
168: 'used' => (float)$used_rs->fetch_object()?->sum,
169: 'total' => (float)($bw_rs ? $bw_rs->threshold : -1)
170: );
171: break;
172: default:
173: return error("Unknown bandwidth classifier `%d'", $type);
174: }
175: }
176:
177: /* }}} */
178:
179:
180: /**
181: * Fill in missing spans for bandwidth
182: *
183: * @param int $site_id
184: * @param int $rollover
185: * @return bool
186: * @throws PostgreSQLError
187: */
188: private function _autofix_bandwidth(int $site_id, int $rollover): bool
189: {
190: $db = \PostgreSQL::initialize();
191: $ts = mktime(0, 0, 0, (int)date('m'), $rollover);
192: $ts2 = strtotime('last month', $ts);
193: if ($ts > time()) {
194: $ts = $ts2;
195: }
196: $db->query("INSERT INTO bandwidth_spans (site_id, begindate, enddate) VALUES($site_id, TO_TIMESTAMP($ts), NULL)
197: ON CONFLICT (site_id,begindate) DO NOTHING;");
198:
199: return !(bool)pg_last_error($db);
200: }
201:
202: /**
203: * Get bandwidth consumed during a time interval
204: *
205: * @param int $begin beginning date
206: * @param int $end ending date
207: * @return array|bool
208: */
209: public function get_by_date(int $begin, int $end = null)
210: {
211: if (!$begin) {
212: return error('no begin period set');
213: } else if ($begin < 0 || $end < 0) {
214: return error('Invalid begin or end date');
215: } else if ($end && $begin > $end) {
216: return error('Begin date may not be before end date');
217: }
218: // there may be collisions, but sacrifice for key len
219: $cachekey = 'bandwidth.gbd.' . crc32($begin . $end);
220: $cache = Cache_Account::spawn($this->getAuthContext());
221: $services = $cache->get($cachekey);
222: if ($services !== false) {
223: return $services;
224: }
225: $services = (new Site($this->site_id))->getByRange($begin, $end);
226: $cache->set($cachekey, $services, 43200);
227:
228: return $services;
229: }
230:
231: public function enabled(): bool
232: {
233: return (bool)$this->getServiceValue('bandwidth', 'enabled');
234: }
235:
236: /**
237: * Grant bandwidth amnesty until closing date
238: *
239: * @param string $marker
240: * @return bool
241: */
242: public function amnesty(string $marker): bool
243: {
244: if (null === ($siteId = \Auth::get_site_id_from_anything($marker))) {
245: return error("Unable to resolve site from `%s'", $marker);
246: }
247: $bwhandler = new Site($siteId);
248: info(
249: 'Amnesty granted on site%d until %s',
250: $siteId,
251: date('r', $bwhandler->getCycleEnd())
252: );
253: return $bwhandler->amnesty();
254: }
255:
256: public function _delete()
257: {
258: $glob = self::BW_DIR . '/*/' . $this->site . '{,.?}';
259: foreach (glob($glob) as $f) {
260: unlink($f);
261: }
262: }
263:
264: public function _edit()
265: {
266: $conf_new = $this->getAuthContext()->getAccount()->new;
267: $conf_old = $this->getAuthContext()->getAccount()->old;
268: $user = array(
269: 'old' => $conf_old['siteinfo']['admin_user'],
270: 'new' => $conf_new['siteinfo']['admin_user']
271: );
272: if ($user['old'] !== $user['new']) {
273: (new Site($this->site_id))->renameExtendedInfo($user['old'], $user['new']);
274: }
275: }
276:
277: public function _edit_user(string $userold, string $usernew, array $oldpwd)
278: {
279: if ($userold === $usernew) {
280: return;
281: }
282: // update
283: (new Site($this->site_id))->renameExtendedInfo($userold, $usernew);
284:
285: return true;
286: }
287:
288: public function _verify_conf(\Opcenter\Service\ConfigurationContext $ctx): bool
289: {
290: return true;
291: }
292:
293: public function _create()
294: {
295: // TODO: Implement _create() method.
296: }
297:
298: public function _create_user(string $user)
299: {
300: // TODO: Implement _create_user() method.
301: }
302:
303: public function _delete_user(string $user)
304: {
305: // TODO: Implement _delete_user() method.
306: }
307:
308:
309: }