1: | <?php |
2: | declare(strict_types=1); |
3: | |
4: | |
5: | |
6: | |
7: | |
8: | |
9: | |
10: | |
11: | |
12: | |
13: | |
14: | |
15: | use Opcenter\Bandwidth\Site; |
16: | |
17: | |
18: | |
19: | |
20: | |
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: | |
37: | |
38: | |
39: | |
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: | |
62: | |
63: | |
64: | |
65: | |
66: | |
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: | |
87: | |
88: | |
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: | |
102: | |
103: | |
104: | |
105: | |
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: | |
135: | |
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: | |
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: | |
182: | |
183: | |
184: | |
185: | |
186: | |
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: | |
204: | |
205: | |
206: | |
207: | |
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: | |
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: | |
238: | |
239: | |
240: | |
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: | |
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: | |
296: | } |
297: | |
298: | public function _create_user(string $user) |
299: | { |
300: | |
301: | } |
302: | |
303: | public function _delete_user(string $user) |
304: | { |
305: | |
306: | } |
307: | |
308: | |
309: | } |