Passed
Push — master ( ae0161...6e4dff )
by Sebastian
01:09
created

Redis::restore()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 0
cts 7
cp 0
rs 9.8666
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 2
1
<?php
2
namespace phpbu\App\Backup\Source;
3
4
use phpbu\App\Backup\Restore\Plan;
5
use phpbu\App\Backup\Target;
6
use phpbu\App\Cli\Executable;
7
use phpbu\App\Exception;
8
use phpbu\App\Result;
9
use phpbu\App\Util;
10
11
/**
12
 * Tar source class.
13
 *
14
 * @package    phpbu
15
 * @subpackage Backup
16
 * @author     Sebastian Feldmann <[email protected]>
17
 * @copyright  Sebastian Feldmann <[email protected]>
18
 * @license    https://opensource.org/licenses/MIT The MIT License (MIT)
19
 * @link       http://phpbu.de/
20
 * @since      Class available since Release 2.1.12
21
 */
22
class Redis extends SimulatorExecutable implements Simulator, Restorable
23
{
24
    /**
25
     * Executable to handle redis command.
26
     *
27
     * @var \phpbu\App\Cli\Executable\RedisCli
28
     */
29
    protected $executable;
30
31
    /**
32
     * Path to executable.
33
     *
34
     * @var string
35
     */
36
    private $pathToRedisCli;
37
38
    /**
39
     * Time to wait for the dump to finish, 45 seconds by default
40
     *
41
     * @var integer
42
     */
43
    private $timeout;
44
45
    /**
46
     * Host to backup
47
     *
48
     * @var string
49
     */
50
    private $host;
51
52
    /**
53
     * Port to connect to
54
     *
55
     * @var int
56
     */
57
    private $port;
58
59
    /**
60
     * Password for authentication
61
     *
62
     * @var string
63
     */
64
    private $password;
65
66
    /**
67
     * Path to the redis rdb directory, for Debian it's /var/lib/redis/{PORT}/dump.rdb
68
     *
69
     * @var string
70
     */
71
    private $pathToRedisData;
72
73
    /**
74
     * Setup.
75
     *
76
     * @see    \phpbu\App\Backup\Source
77
     * @param  array $conf
78
     * @throws \phpbu\App\Exception
79
     */
80 7
    public function setup(array $conf = [])
81
    {
82 7
        $this->pathToRedisCli  = Util\Arr::getValue($conf, 'pathToRedisCli', '');
83 7
        $this->pathToRedisData = Util\Arr::getValue($conf, 'pathToRedisData', '');
84 7
        $this->timeout         = Util\Arr::getValue($conf, 'timeout', 45);
85 7
        $this->host            = Util\Arr::getValue($conf, 'host', '');
86 7
        $this->port            = Util\Arr::getValue($conf, 'port', 0);
87 7
        $this->password        = Util\Arr::getValue($conf, 'password', '');
88
89 7
        if (empty($this->pathToRedisData)) {
90 1
            throw new Exception('pathToRedisData option is mandatory');
91
        }
92 6
    }
93
94
    /**
95
     * Execute the backup.
96
     *
97
     * @see    \phpbu\App\Backup\Source
98
     * @param  \phpbu\App\Backup\Target $target
99
     * @param  \phpbu\App\Result        $result
100
     * @return \phpbu\App\Backup\Source\Status
101
     * @throws \phpbu\App\Exception
102
     */
103 5
    public function backup(Target $target, Result $result) : Status
104
    {
105
        // set uncompressed default MIME type
106 5
        $target->setMimeType('application/octet-stream');
107
108 5
        $redisSave = $this->getExecutable($target);
109 5
        $redisLast = $this->getRedisLastSave($this->executable);
110
111 5
        $lastBackupTimestamp = $this->getLastBackupTime($redisLast);
112 4
        $saveResult          = $this->runCommand($redisSave);
113 4
        $result->debug($this->getExecutable($target)->getCommandPrintable());
114 4
        if (!$saveResult->isSuccessful()) {
115 1
            throw new Exception('redis-cli BGSAVE failed:' . $saveResult->getStdErr());
116
        }
117
        // check if the save process is finished
118 3
        $this->isDumpCreatedYet($lastBackupTimestamp, $redisLast);
119 2
        $this->copyDumpToTarget($target);
120
121 1
        return $this->createStatus($target);
122
    }
123
124
    /**
125
     * Setup the Executable to run the 'tar' command.
126
     *
127
     * @param  \phpbu\App\Backup\Target $target
128
     * @return \phpbu\App\Cli\Executable
129
     */
130 6
    protected function createExecutable(Target $target) : Executable
131
    {
132 6
        $executable = new Executable\RedisCli($this->pathToRedisCli);
133 6
        $executable->backup()
134 6
                   ->useHost($this->host)
135 6
                   ->usePort($this->port)
136 6
                   ->usePassword($this->password);
137 6
        return $executable;
138
    }
139
140
    /**
141
     * Creates a RedisLastSave command from a RedisSave command.
142
     *
143
     * @param  \phpbu\App\Cli\Executable\RedisCli $redis
144
     * @return \phpbu\App\Cli\Executable\RedisCli
145
     */
146 5
    public function getRedisLastSave(Executable\RedisCli $redis) : Executable\RedisCli
147
    {
148 5
        $redisLast = clone($redis);
149 5
        $redisLast->lastBackupTime();
150 5
        return $redisLast;
151
    }
152
153
    /**
154
     * Return last successful save timestamp.
155
     *
156
     * @param  \phpbu\App\Cli\Executable\RedisCli $redis
157
     * @return int
158
     * @throws \phpbu\App\Exception
159
     */
160 5
    private function getLastBackupTime(Executable\RedisCli $redis) : int
161
    {
162 5
        $result  = $this->runCommand($redis);
163 5
        $output  = $result->getStdOut();
164 5
        $matches = [];
165 5
        if (!preg_match('#(\(integer\) )?([0-9]+)#i', $output, $matches)) {
166 1
            throw new Exception('invalid redis-cli LASTSAVE output');
167
        }
168 4
        return (int) $matches[2];
169
    }
170
171
    /**
172
     * Check the dump date and return true if BGSAVE is finished.
173
     *
174
     * @param  int                                $lastTimestamp
175
     * @param  \phpbu\App\Cli\Executable\RedisCli $redis
176
     * @return bool
177
     * @throws \phpbu\App\Exception
178
     */
179 3
    private function isDumpCreatedYet($lastTimestamp, $redis) : bool
180
    {
181 3
        $i = 0;
182 3
        while ($this->getLastBackupTime($redis) <= $lastTimestamp) {
183 1
            if ($i > $this->timeout) {
184 1
                throw new Exception('redis-cli BGSAVE is taking to long, increase timeout');
185
            }
186 1
            $i++;
187 1
            sleep(1);
188
        }
189 2
        return true;
190
    }
191
192
    /**
193
     * Copy the redis RDB file to its backup location.
194
     *
195
     * @param  \phpbu\App\Backup\Target $target
196
     * @return string
197
     * @throws \phpbu\App\Exception
198
     */
199 2
    private function copyDumpToTarget(Target $target) : string
200
    {
201 2
        if (!file_exists($this->pathToRedisData)) {
202 1
            throw new Exception('Redis data not found at: \'' . $this->pathToRedisCli . '\'');
203
        }
204 1
        $targetFile = $target->getPathnamePlain();
205 1
        copy($this->pathToRedisData, $targetFile);
206 1
        return $targetFile;
207
    }
208
209
    /**
210
     * Create backup status.
211
     *
212
     * @param  \phpbu\App\Backup\Target $target
213
     * @return \phpbu\App\Backup\Source\Status
214
     */
215 1
    protected function createStatus(Target $target) : Status
216
    {
217 1
        return Status::create()->uncompressedFile($target->getPathnamePlain());
218
    }
219
220
    /**
221
     * Restore the backup
222
     *
223
     * @param  \phpbu\App\Backup\Target       $target
224
     * @param  \phpbu\App\Backup\Restore\Plan $plan
225
     * @return \phpbu\App\Backup\Source\Status
226
     */
227
    public function restore(Target $target, Plan $plan): Status
228
    {
229
        $plan->addRestoreCommand(
230
            sprintf(
231
                'cp %s %s',
232
                $target->getPathnamePlain(),
233
                $this->pathToRedisData
234
            )
235
        );
236
237
        return Status::create()->uncompressedFile($target->getPathnamePlain());
238
    }
239
}
240