OpenStack   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 250
Duplicated Lines 0 %

Test Coverage

Coverage 44.12%

Importance

Changes 0
Metric Value
eloc 74
c 0
b 0
f 0
dl 0
loc 250
ccs 30
cts 68
cp 0.4412
rs 10
wmc 17

9 Methods

Rating   Name   Duplication   Size   Complexity  
A getPath() 0 3 1
A getUploadPath() 0 3 2
A connect() 0 18 1
A setup() 0 22 2
A getOrCreateContainer() 0 7 2
A createCollector() 0 3 1
A validateConfig() 0 5 3
A sync() 0 28 4
A simulate() 0 12 1
1
<?php
2
namespace phpbu\App\Backup\Sync;
3
4
use GuzzleHttp\Psr7\Stream;
5
use OpenStack\ObjectStore\v1\Models\Container;
6
use OpenStack\ObjectStore\v1\Service as ObjectStoreService;
7
use GuzzleHttp\Client;
8
use OpenStack\Common\Transport\HandlerStack;
9
use OpenStack\Common\Transport\Utils;
10
use OpenStack\Identity\v2\Service;
11
use phpbu\App\Backup\Collector;
12
use phpbu\App\Backup\Path;
13
use phpbu\App\Backup\Target;
14
use phpbu\App\Result;
15
use phpbu\App\Util;
16
17
/**
18
 * OpenStack Swift Sync
19
 *
20
 * @package    phpbu
21
 * @subpackage Backup
22
 * @author     Vitaly Baev <[email protected]>
23
 * @author     Sebastian Feldmann <[email protected]>
24
 * @copyright  Sebastian Feldmann <[email protected]>
25
 * @license    https://opensource.org/licenses/MIT The MIT License (MIT)
26
 * @link       http://phpbu.de/
27
 * @since      Class available since Release 5.1
28
 */
29
class OpenStack implements Simulator
30
{
31
    use Cleanable;
0 ignored issues
show
introduced by
The trait phpbu\App\Backup\Sync\Cleanable requires some properties which are not provided by phpbu\App\Backup\Sync\OpenStack: $type, $options
Loading history...
32
33
    /**
34
     * OpenStack identify url
35
     *
36
     * @var string
37
     */
38
    protected $authUrl;
39
40
    /**
41
     * OpenStack region
42
     *
43
     * @var string
44
     */
45
    protected $region;
46
47
    /**
48
     * @var string
49
     */
50
    protected $username;
51
52
    /**
53
     * @var string
54
     */
55
    protected $password;
56
57
    /**
58
     * Object Store container name
59
     *
60
     * @var string
61
     */
62
    protected $containerName;
63
64
    /**
65
     * OpenStack service name
66
     *
67
     * @var string
68
     */
69
    protected $serviceName;
70
71
    /**
72
     * Max stream upload size, files over this size have to be uploaded as Dynamic Large Objects
73
     *
74
     * @var int
75
     */
76
    protected $maxStreamUploadSize = 5368709120;
77
78
    /**
79
     * Path where to copy the backup without leading or trailing slashes.
80
     *
81
     * @var Path
82
     */
83
    protected $path;
84
85
    /**
86
     * Unix timestamp of generating path from placeholder.
87
     *
88
     * @var int
89
     */
90
    protected $time;
91
92
    /**
93
     * Path where to copy the backup still containing possible date placeholders.
94
     *
95
     * @var string
96
     */
97
    protected $pathRaw = '';
98
99
    /**
100
     * @var Container
101
     */
102
    protected $container;
103
104
    /**
105
     * @return string
106
     */
107
    public function getPath(): string
108
    {
109
        return $this->path;
110
    }
111
112
    /**
113
     * (non-PHPDoc)
114
     *
115
     * @see    \phpbu\App\Backup\Sync::setup()
116
     * @param  array $config
117
     * @throws \phpbu\App\Backup\Sync\Exception
118
     * @throws \phpbu\App\Exception
119
     */
120 9
    public function setup(array $config)
121
    {
122 9
        if (!class_exists('\\OpenStack\\OpenStack')) {
123
            throw new Exception('OpeStack SDK not loaded: use composer to install "php-opencloud/openstack"');
124
        }
125
126
        // check for mandatory options
127 9
        $this->validateConfig($config, ['auth_url', 'region', 'username', 'password', 'container_name']);
128
129 4
        $this->time          = time();
130 4
        $this->authUrl       = $config['auth_url'];
131 4
        $this->region        = $config['region'];
132 4
        $this->username      = $config['username'];
133 4
        $this->password      = $config['password'];
134 4
        $this->containerName = $config['container_name'];
135 4
        $this->serviceName   = Util\Arr::getValue($config, 'service_name', 'swift');
136 4
        $this->path          = new Path(
137
            Util\Path::withoutLeadingSlash(Util\Arr::getValue($config, 'path', '')),
138 4
            $this->time
139 4
        );
140
141
        $this->setUpCleanable($config);
142
    }
143
144
    /**
145
     * Make sure all mandatory keys are present in given config.
146
     *
147
     * @param  array $config
148 9
     * @param  array $keys
149
     * @throws Exception
150 9
     */
151 9
    protected function validateConfig(array $config, array $keys)
152 9
    {
153
        foreach ($keys as $option) {
154
            if (!Util\Arr::isSetAndNotEmptyString($config, $option)) {
155 4
                throw new Exception($option . ' is mandatory');
156
            }
157
        }
158
    }
159
160
    /**
161
     * Execute the sync.
162
     *
163
     * @see    \phpbu\App\Backup\Sync::sync()
164
     * @param  \phpbu\App\Backup\Target $target
165
     * @param  \phpbu\App\Result        $result
166
     * @throws \phpbu\App\Backup\Sync\Exception
167
     * @throws \OpenStack\Common\Error\BadResponseError
168
     */
169
    public function sync(Target $target, Result $result)
170
    {
171
        if (!$this->container) {
172
            $this->connect($result);
173
        }
174
175
        try {
176
            if ($target->getSize() > $this->maxStreamUploadSize) {
177
                // use Dynamic Large Objects
178
                $uploadOptions = [
179
                    'name'   => $this->getUploadPath($target),
180
                    'stream' => new Stream(fopen($target->getPathname(), 'r')),
0 ignored issues
show
Bug introduced by
It seems like fopen($target->getPathname(), 'r') can also be of type false; however, parameter $stream of GuzzleHttp\Psr7\Stream::__construct() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

180
                    'stream' => new Stream(/** @scrutinizer ignore-type */ fopen($target->getPathname(), 'r')),
Loading history...
181
                ];
182
                $this->container->createLargeObject($uploadOptions);
183
            } else {
184
                // create an object
185
                $uploadOptions = [
186
                    'name'    => $this->getUploadPath($target),
187
                    'content' => file_get_contents($target->getPathname()),
188
                ];
189
                $this->container->createObject($uploadOptions);
190
            }
191
        } catch (\Exception $e) {
192
            throw new Exception($e->getMessage(), null, $e);
193
        }
194
        // run remote cleanup
195
        $this->cleanup($target, $result);
196
        $result->debug('upload: done');
197
    }
198
199
    /**
200
     * Simulate the sync execution.
201
     *
202 1
     * @param \phpbu\App\Backup\Target $target
203
     * @param \phpbu\App\Result        $result
204 1
     */
205 1
    public function simulate(Target $target, Result $result)
206 1
    {
207 1
        $result->debug(
208 1
            'sync backup to OpenStack' . PHP_EOL
209 1
            . '  region:   ' . $this->region . PHP_EOL
210 1
            . '  key:      ' . $this->username . PHP_EOL
211
            . '  password:    ********' . PHP_EOL
212
            . '  container: ' . $this->containerName
213 1
            . '  path: "' . $this->path->getPath() . '"' . PHP_EOL
214 1
        );
215
216
        $this->simulateRemoteCleanup($target, $result);
217
    }
218
219
    /**
220
     * Creates collector for OpenStack.
221
     *
222
     * @param  \phpbu\App\Backup\Target $target
223
     * @return \phpbu\App\Backup\Collector
224
     */
225
    protected function createCollector(Target $target): Collector
226
    {
227
        return new Collector\OpenStack($target, $this->path, $this->container);
228
    }
229
230
    /**
231
     * @param  \OpenStack\ObjectStore\v1\Service $service
232
     * @param  \phpbu\App\Result                 $result
233
     * @return \OpenStack\ObjectStore\v1\Models\Container
234
     * @throws \OpenStack\Common\Error\BadResponseError
235
     */
236
    protected function getOrCreateContainer(ObjectStoreService $service, Result $result)
237
    {
238
        if (!$service->containerExists($this->containerName)) {
239
            $result->debug('create container');
240
            return $service->createContainer(['name' => $this->containerName]);
241
        }
242
        return $service->getContainer($this->containerName);
243
    }
244
245
    /**
246
     * Get the upload path
247
     *
248 2
     * @param \phpbu\App\Backup\Target $target
249
     * @return string
250 2
     */
251
    public function getUploadPath(Target $target)
252
    {
253
        return (!empty($this->path->getPath()) ? $this->path->getPath() . '/' : '') . $target->getFilename();
254
    }
255
256
    /**
257
     * @param  \phpbu\App\Result $result
258
     * @return void
259
     * @throws \OpenStack\Common\Error\BadResponseError
260
     */
261
    protected function connect(Result $result)
262
    {
263
        $httpClient = new Client([
264
            'base_uri' => Utils::normalizeUrl($this->authUrl),
265
            'handler'  => HandlerStack::create(),
266
        ]);
267
268
        $options = [
269
            'authUrl'         => $this->authUrl,
270
            'region'          => $this->region,
271
            'username'        => $this->username,
272
            'password'        => $this->password,
273
            'identityService' => Service::factory($httpClient),
274
        ];
275
276
        $openStack          = new \OpenStack\OpenStack($options);
277
        $objectStoreService = $openStack->objectStoreV1(['catalogName' => $this->serviceName]);
278
        $this->container    = $this->getOrCreateContainer($objectStoreService, $result);
279
    }
280
}
281