Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.16% covered (success)
95.16%
59 / 62
72.73% covered (warning)
72.73%
8 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
InfrastructureCapacityService
95.16% covered (success)
95.16%
59 / 62
72.73% covered (warning)
72.73%
8 / 11
20
0.00% covered (danger)
0.00%
0 / 1
 getCapacity
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 consume
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 recalculateCapacity
95.24% covered (success)
95.24%
20 / 21
0.00% covered (danger)
0.00%
0 / 1
3
 getLockTtl
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getPersistence
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 getEventManager
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 logCapacityCalculationDetails
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 getCapacityCacheTtl
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getInfrastructureLoadLimit
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTaoCapacityLimit
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 collectInfrastructureLoad
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2019 (original work) Open Assessment Technologies SA ;
19 */
20
21namespace oat\taoDelivery\model\Capacity;
22
23use InvalidArgumentException;
24use oat\generis\persistence\PersistenceManager;
25use oat\oatbox\event\EventManager;
26use oat\oatbox\log\LoggerAwareTrait;
27use oat\oatbox\mutex\LockTrait;
28use oat\oatbox\service\ConfigurableService;
29use oat\tao\model\metrics\MetricsService;
30use oat\taoDelivery\model\event\SystemCapacityUpdatedEvent;
31use oat\taoDelivery\model\Metrics\InfrastructureLoadMetricInterface;
32
33class InfrastructureCapacityService extends ConfigurableService implements CapacityInterface
34{
35    use LoggerAwareTrait;
36    use LockTrait;
37
38    public const METRIC = InfrastructureLoadMetricInterface::class;
39
40    public const OPTION_INFRASTRUCTURE_LOAD_LIMIT = 'infrastructure_load_limit';
41    public const OPTION_TAO_CAPACITY_LIMIT = 'tao_capacity';
42    public const OPTION_PERSISTENCE = 'persistence';
43    public const OPTION_TTL = 'ttl';
44
45    public const DEFAULT_INFRASTRUCTURE_LOAD_LIMIT = 75;
46    public const DEFAULT_TAO_CAPACITY_LIMIT = 100;
47    public const DEFAULT_TTL = 300;
48    public const DEFAULT_LOCK_TTL = 30;
49
50    public const CAPACITY_TO_PROVIDE_CACHE_KEY = 'infrastructure_capacity_to_provide';
51    public const CAPACITY_TO_CONSUME_CACHE_KEY = 'infrastructure_capacity_to_consume';
52
53    /**
54     * Returns the available capacity of the system
55     * Will return -1 if unlimited
56     *
57     * @return int
58     * @throws \common_Exception
59     */
60    public function getCapacity()
61    {
62        $cachedCapacity = $capacity = $this->getPersistence()->get(self::CAPACITY_TO_PROVIDE_CACHE_KEY);
63        if ($cachedCapacity === false || $cachedCapacity === null) {
64            $capacity = $this->recalculateCapacity(self::CAPACITY_TO_PROVIDE_CACHE_KEY);
65        }
66        if ($capacity <= 0) {
67            return 0;
68        }
69        $this->getPersistence()->decr(self::CAPACITY_TO_PROVIDE_CACHE_KEY);
70
71        return $capacity;
72    }
73
74    /**
75     * {@inheritdoc}
76     */
77    public function consume()
78    {
79        $lock = $this->createLock(__CLASS__ . __METHOD__, $this->getLockTtl());
80        $lock->acquire(true);
81
82        try {
83            $cachedCapacity = $capacity = $this->getPersistence()->get(self::CAPACITY_TO_CONSUME_CACHE_KEY);
84            if ($cachedCapacity === false || $cachedCapacity === null) {
85                $capacity = $this->recalculateCapacity(self::CAPACITY_TO_CONSUME_CACHE_KEY);
86            }
87            if ($capacity <= 0) {
88                return false;
89            }
90
91            $this->getPersistence()->decr(self::CAPACITY_TO_CONSUME_CACHE_KEY);
92            return true;
93        } finally {
94            $lock->release();
95        }
96    }
97
98    /**
99     * @param  string $keyToCheck
100     * @return float
101     * @throws \common_Exception
102     */
103    private function recalculateCapacity($keyToCheck)
104    {
105        $lock = $this->createLock(__CLASS__ . __METHOD__, $this->getLockTtl());
106        $lock->acquire(true);
107
108        try {
109            $persistence = $this->getPersistence();
110            $cachedValue = null;
111            if ($persistence->exists($keyToCheck) && ($cachedValue = $persistence->get($keyToCheck)) !== null) {
112                return $cachedValue;
113            }
114
115            $infrastructureLoadLimit = $this->getInfrastructureLoadLimit();
116            $taoLimit = $this->getTaoCapacityLimit();
117            $currentInfrastructureLoad = $this->collectInfrastructureLoad();
118
119            $capacity = floor((1 - $currentInfrastructureLoad / $infrastructureLoadLimit) * $taoLimit);
120            $persistence->set(self::CAPACITY_TO_PROVIDE_CACHE_KEY, $capacity, $this->getCapacityCacheTtl());
121            $persistence->set(self::CAPACITY_TO_CONSUME_CACHE_KEY, $capacity, $this->getCapacityCacheTtl());
122
123            $this->logCapacityCalculationDetails(
124                $capacity,
125                $currentInfrastructureLoad,
126                $infrastructureLoadLimit,
127                $taoLimit
128            );
129            $this->getEventManager()->trigger(new SystemCapacityUpdatedEvent(null, $capacity));
130
131            return $capacity;
132        } finally {
133            $lock->release();
134        }
135    }
136
137    /**
138     * @return int
139     */
140    private function getLockTtl()
141    {
142        $capacityTtl = $this->getCapacityCacheTtl();
143
144        return min($capacityTtl, self::DEFAULT_LOCK_TTL);
145    }
146
147    /**
148     * @return \common_persistence_KeyValuePersistence
149     */
150    private function getPersistence()
151    {
152        if (!$this->hasOption(self::OPTION_PERSISTENCE)) {
153            throw new InvalidArgumentException('Persistence for ' . self::SERVICE_ID . ' is not configured');
154        }
155
156        $persistenceId = $this->getOption(self::OPTION_PERSISTENCE);
157
158        return $this->getServiceLocator()->get(PersistenceManager::SERVICE_ID)->getPersistenceById($persistenceId);
159    }
160
161    /**
162     * @return EventManager
163     */
164    private function getEventManager()
165    {
166        return $this->getServiceLocator()->get(EventManager::SERVICE_ID);
167    }
168
169    private function logCapacityCalculationDetails(
170        $capacity,
171        $currentInfrastructureLoad,
172        $infrastructureLimit,
173        $taoLimit
174    ) {
175        $this->getLogger()->info(
176            sprintf(
177                'Recalculated system capacity: %s, current infrastructure load: %s%%, configured infrastructure '
178                    . 'limit: %s%%, configured TAO limit: %s',
179                $capacity,
180                $currentInfrastructureLoad,
181                $infrastructureLimit,
182                $taoLimit
183            )
184        );
185    }
186
187    /**
188     * @return int|mixed
189     */
190    private function getCapacityCacheTtl()
191    {
192        return $this->getOption(self::OPTION_TTL) ?? self::DEFAULT_TTL;
193    }
194
195    /**
196     * @return int|mixed
197     */
198    private function getInfrastructureLoadLimit()
199    {
200        return $this->getOption(self::OPTION_INFRASTRUCTURE_LOAD_LIMIT) ?? self::DEFAULT_INFRASTRUCTURE_LOAD_LIMIT;
201    }
202
203    /**
204     * @return int|mixed
205     */
206    private function getTaoCapacityLimit()
207    {
208        return $this->getOption(self::OPTION_TAO_CAPACITY_LIMIT) ?? self::DEFAULT_TAO_CAPACITY_LIMIT;
209    }
210
211    /**
212     * @return mixed
213     */
214    private function collectInfrastructureLoad()
215    {
216        $infrastructureMetricService = $this->getServiceLocator()->get(MetricsService::class)->getOneMetric(
217            self::METRIC
218        );
219
220        return $infrastructureMetricService->collect();
221    }
222}