├── .gitignore
├── README.md
├── pom.xml
└── src
├── main
├── assemblies
│ └── plugin.xml
├── java
│ └── org
│ │ └── elasticsearch
│ │ ├── aliyun
│ │ └── oss
│ │ │ ├── blobstore
│ │ │ ├── OssBlobContainer.java
│ │ │ └── OssBlobStore.java
│ │ │ └── service
│ │ │ ├── OssClientSettings.java
│ │ │ ├── OssService.java
│ │ │ ├── OssServiceImpl.java
│ │ │ ├── OssStorageClient.java
│ │ │ └── exception
│ │ │ └── CreateStsOssClientException.java
│ │ ├── plugin
│ │ └── repository
│ │ │ └── oss
│ │ │ └── OssRepositoryPlugin.java
│ │ ├── repository
│ │ └── oss
│ │ │ └── OssRepository.java
│ │ └── utils
│ │ ├── DateHelper.java
│ │ ├── HttpClientHelper.java
│ │ └── PermissionHelper.java
└── resources
│ ├── plugin-descriptor.properties
│ └── plugin-security.policy
└── test
└── java
└── org
└── elasticsearch
├── aliyun
└── oss
│ └── blobstore
│ ├── MockOssService.java
│ ├── OssBlobContainerTest.java
│ └── OssBlobStoreTest.java
├── bootstrap
└── JarHell.java
└── repository
└── oss
└── OssRepositoryTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Files for the Dalvik VM
2 | *.dex
3 |
4 | # Java class files
5 | *.class
6 |
7 | # Generated files
8 | */bin/
9 | */gen/
10 | */out/
11 |
12 | # Gradle files
13 | .gradle/
14 | build/
15 | */build/
16 | gradlew
17 | gradlew.bat
18 |
19 | # Local configuration file (sdk path, etc)
20 | local.properties
21 |
22 | # Proguard folder generated by Eclipse
23 | proguard/
24 |
25 | # Log Files
26 | *.log
27 |
28 | # Android Studio Navigation editor temp files
29 | .navigation/
30 |
31 | # Android Studio captures folder
32 | captures/
33 |
34 | # Intellij
35 | *.iml
36 | */*.iml
37 |
38 | # Keystore files
39 | #*.jks
40 | #gradle wrapper
41 | gradle/
42 |
43 | #some local files
44 | */.settings/
45 | */.DS_Store
46 | .DS_Store
47 | */.idea/
48 | .idea/
49 | target/
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # elasticsearch-repository-oss
2 |
3 |
4 | 要备份你的集群,你可以使用 `snapshot` API。这个会拿到你集群里当前的状态和数据然后保存到一个共享仓库里。这个备份过程是"智能"的。你的第一个快照会是一个数据的完整拷贝,但是所有后续的快照会保留的是已存快照和新数据之间的差异。随着你不时的对数据进行快照,备份也在增量的添加和删除。这意味着后续备份会相当快速,因为它们只传输很小的数据量。
5 |
6 | * [OSS快照迁移文档](https://github.com/zhichen/elasticsearch-repository-oss/wiki/OSS快照迁移)
7 |
8 |
9 | ## 创建仓库
10 | ```
11 | PUT _snapshot/my_backup
12 | {
13 | "type": "oss",
14 | "settings": {
15 | "endpoint": "http://oss-cn-hangzhou-internal.aliyuncs.com", <1>
16 | "access_key_id": "xxxx",
17 | "secret_access_key": "xxxxxx",
18 | "bucket": "xxxxxx", <2>
19 | "compress": true
20 | }
21 | }
22 | ```
23 | * <1> 本处的OSS, 要求和你的elasticsearch集群在同一个region中, 这里的endpoint填的是这个region对应的内网地址 ,具体参考 https://help.aliyun.com/document_detail/31837.html?spm=5176.doc31922.6.577.YxqZYt 中`ECS访问的内网Endpoint`一栏
24 | * <2> 需要一个已经存在的OSS bucket
25 |
26 |
27 |
28 |
29 | 假设我们上传的数据非常大, 我们可以限制snapshot过程中分块的大小,超过这个大小,数据将会被分块上传到OSS中
30 |
31 | ```
32 | POST _snapshot/my_backup/ <1>
33 | {
34 | "type": "oss",
35 | "settings": {
36 | "endpoint": "http://oss-cn-hangzhou-internal.aliyuncs.com",
37 | "access_key_id": "xxxx",
38 | "secret_access_key": "xxxxxx",
39 | "bucket": "xxxxxx",
40 | "chunk_size": "500mb",
41 | "base_path": "snapshot/" <2>
42 | }
43 | }
44 | ```
45 | * <1> 注意我们用的是 `POST` 而不是 `PUT` 。这会更新已有仓库的设置。
46 | * <2> base_path 设置仓库的起始位置默认为根目录
47 |
48 | ## 列出仓库信息
49 | ```
50 | GET _snapshot
51 | ```
52 | * 也可以使用 `GET _snapshot/my_backup` 获取指定仓库的信息
53 |
54 | ### 备份快照迁移
55 | 如果需要将快照迁移到另一个集群.只需要备份到OSS, 然后再在新的集群上注册一个快照仓库(相同的OSS),设置`base_path`的位置为备份文件所在的地方,然后执行恢复备份的命令即可。
56 |
57 | ### 快照所有打开的索引 (以下内容和官方一致)
58 |
59 | 一个仓库可以包含多个快照。每个快照跟一系列索引相关(比如所有索引,一部分索引,或者单个索引)。当创建快照的时候,你指定你感兴趣的索引然后给快照取一个唯一的名字。
60 |
61 | 让我们从最基础的快照命令开始:
62 |
63 | ```
64 | PUT _snapshot/my_backup/snapshot_1
65 | ```
66 |
67 | 这个会备份所有打开的索引到 `my_backup` 仓库下一个命名为 `snapshot_1` 的快照里。这个调用会立刻返回,然后快照会在后台运行。
68 |
69 |
70 |
71 | 通常你会希望你的快照作为后台进程运行,不过有时候你会希望在你的脚本中一直等待到完成。这可以通过添加一个 `wait_for_completion` 标记实现:
72 |
73 | ```
74 | PUT _snapshot/my_backup/snapshot_1?wait_for_completion=true
75 | ```
76 |
77 | 这个会阻塞调用直到快照完成。注意大型快照会花很长时间才返回。
78 |
79 |
80 |
81 |
82 | ### 快照指定索引
83 |
84 | 默认行为是备份所有打开的索引。不过如果你在用 Kibana,你不是真的想要把所有诊断相关的 `.kibana` 索引也备份起来。可能你就压根没那么大空间备份所有数据。
85 |
86 | 这种情况下,你可以在快照你的集群的时候指定备份哪些索引:
87 |
88 | ```
89 | PUT _snapshot/my_backup/snapshot_2
90 | {
91 | "indices": "index_1,index_2"
92 | }
93 | ```
94 |
95 | 这个快照命令现在只会备份 `index1` 和 `index2` 了。
96 |
97 | ### 列出快照相关的信息
98 |
99 | 一旦你开始在你的仓库里积攒起快照了,你可能就慢慢忘记里面各自的细节了——特别是快照按照时间划分命名的时候(比如, `backup_2014_10_28` )。
100 |
101 | 要获得单个快照的信息,直接对仓库和快照名发起一个 `GET` 请求:
102 |
103 | ```
104 | GET _snapshot/my_backup/snapshot_2
105 | ```
106 |
107 | 这个会返回一个小响应,包括快照相关的各种信息:
108 |
109 | ```
110 | {
111 | "snapshots": [
112 | {
113 | "snapshot": "snapshot_1",
114 | "indices": [
115 | ".marvel_2014_28_10",
116 | "index1",
117 | "index2"
118 | ],
119 | "state": "SUCCESS",
120 | "start_time": "2014-09-02T13:01:43.115Z",
121 | "start_time_in_millis": 1409662903115,
122 | "end_time": "2014-09-02T13:01:43.439Z",
123 | "end_time_in_millis": 1409662903439,
124 | "duration_in_millis": 324,
125 | "failures": [],
126 | "shards": {
127 | "total": 10,
128 | "failed": 0,
129 | "successful": 10
130 | }
131 | }
132 | ]
133 | }
134 | ```
135 |
136 | 要获取一个仓库中所有快照的完整列表,使用 `_all` 占位符替换掉具体的快照名称:
137 |
138 | ```
139 | GET _snapshot/my_backup/_all
140 | ```
141 |
142 | ### 删除快照
143 |
144 | 最后,我们需要一个命令来删除所有不再有用的旧快照。这只要对仓库/快照名称发一个简单的 `DELETE` HTTP 调用:
145 |
146 | ```
147 | DELETE _snapshot/my_backup/snapshot_2
148 | ```
149 |
150 | 用 API 删除快照很重要,而不能用其他机制(比如手动删除)。因为快照是增量的,有可能很多快照依赖于过去的段。`delete` API 知道哪些数据还在被更多近期快照使用,然后会只删除不再被使用的段。
151 |
152 | 但是,如果你做了一次人工文件删除,你将会面临备份严重损坏的风险,因为你在删除的是可能还在使用中的数据。
153 |
154 |
155 | ### 监控快照进度
156 |
157 | `wait_for_completion` 标记提供了一个监控的基础形式,但哪怕只是对一个中等规模的集群做快照恢复的时候,它都真的不够用。
158 |
159 | 另外两个 API 会给你有关快照状态更详细的信息。首先你可以给快照 ID 执行一个 `GET`,就像我们之前获取一个特定快照的信息时做的那样:
160 |
161 | ```
162 | GET _snapshot/my_backup/snapshot_3
163 | ```
164 |
165 | 如果你调用这个命令的时候快照还在进行中,你会看到它什么时候开始,运行了多久等等信息。不过要注意,这个 API 用的是快照机制相同的线程池。如果你在快照非常大的分片,状态更新的间隔会很大,因为 API 在竞争相同的线程池资源。
166 |
167 | 更好的方案是拽取 `_status` API 数据:
168 |
169 | ```
170 | GET _snapshot/my_backup/snapshot_3/_status
171 | ```
172 |
173 | `_status` API 立刻返回,然后给出详细的多的统计值输出:
174 |
175 | ```
176 | {
177 | "snapshots": [
178 | {
179 | "snapshot": "snapshot_3",
180 | "repository": "my_backup",
181 | "state": "IN_PROGRESS", <1>
182 | "shards_stats": {
183 | "initializing": 0,
184 | "started": 1, <2>
185 | "finalizing": 0,
186 | "done": 4,
187 | "failed": 0,
188 | "total": 5
189 | },
190 | "stats": {
191 | "number_of_files": 5,
192 | "processed_files": 5,
193 | "total_size_in_bytes": 1792,
194 | "processed_size_in_bytes": 1792,
195 | "start_time_in_millis": 1409663054859,
196 | "time_in_millis": 64
197 | },
198 | "indices": {
199 | "index_3": {
200 | "shards_stats": {
201 | "initializing": 0,
202 | "started": 0,
203 | "finalizing": 0,
204 | "done": 5,
205 | "failed": 0,
206 | "total": 5
207 | },
208 | "stats": {
209 | "number_of_files": 5,
210 | "processed_files": 5,
211 | "total_size_in_bytes": 1792,
212 | "processed_size_in_bytes": 1792,
213 | "start_time_in_millis": 1409663054859,
214 | "time_in_millis": 64
215 | },
216 | "shards": {
217 | "0": {
218 | "stage": "DONE",
219 | "stats": {
220 | "number_of_files": 1,
221 | "processed_files": 1,
222 | "total_size_in_bytes": 514,
223 | "processed_size_in_bytes": 514,
224 | "start_time_in_millis": 1409663054862,
225 | "time_in_millis": 22
226 | }
227 | },
228 | ...
229 | ```
230 | * <1> 一个正在运行的快照会显示 `IN_PROGRESS` 作为状态。
231 | * <2> 这个特定快照有一个分片还在传输(另外四个已经完成)。
232 |
233 | 响应包括快照的总体状况,但也包括下钻到每个索引和每个分片的统计值。这个给你展示了有关快照进展的非常详细的视图。分片可以在不同的完成状态:
234 |
235 | `INITIALIZING`::
236 | 分片在检查集群状态看看自己是否可以被快照。这个一般是非常快的。
237 |
238 | `STARTED`::
239 | 数据正在被传输到仓库。
240 |
241 | `FINALIZING`::
242 | 数据传输完成;分片现在在发送快照元数据。
243 |
244 | `DONE`::
245 | 快照完成!
246 |
247 | `FAILED`::
248 | 快照处理的时候碰到了错误,这个分片/索引/快照不可能完成了。检查你的日志获取更多信息。
249 |
250 |
251 | ### 取消一个快照
252 |
253 | 最后,你可能想取消一个快照或恢复。因为它们是长期运行的进程,执行操作的时候一个笔误或者过错就会花很长时间来解决——而且同时还会耗尽有价值的资源。
254 |
255 | 要取消一个快照,在他进行中的时候简单的删除快照就可以:
256 |
257 | ```
258 | DELETE _snapshot/my_backup/snapshot_3
259 | ```
260 |
261 | 这个会中断快照进程。然后删除仓库里进行到一半的快照。
262 |
263 |
264 | ### 从快照恢复
265 |
266 | 一旦你备份过了数据,恢复它就简单了:只要在你希望恢复回集群的快照 ID 后面加上 `_restore` 即可:
267 |
268 | ```
269 | POST _snapshot/my_backup/snapshot_1/_restore
270 | ```
271 |
272 | 默认行为是把这个快照里存有的所有索引都恢复。如果 `snapshot_1` 包括五个索引,这五个都会被恢复到我们集群里。和 `snapshot` API 一样,我们也可以选择希望恢复具体哪个索引。
273 |
274 | 还有附加的选项用来重命名索引。这个选项允许你通过模式匹配索引名称,然后通过恢复进程提供一个新名称。如果你想在不替换现有数据的前提下,恢复老数据来验证内容,或者做其他处理,这个选项很有用。让我们从快照里恢复单个索引并提供一个替换的名称:
275 |
276 | ```
277 | POST /_snapshot/my_backup/snapshot_1/_restore
278 | {
279 | "indices": "index_1", <1>
280 | "rename_pattern": "index_(.+)", <2>
281 | "rename_replacement": "restored_index_$1" <3>
282 | }
283 | ```
284 | * <1> 只恢复 `index_1` 索引,忽略快照中存在的其余索引。
285 | * <2> 查找所提供的模式能匹配上的正在恢复的索引。
286 | * <3> 然后把它们重命名成替代的模式。
287 |
288 | 这个会恢复 `index_1` 到你及群里,但是重命名成了 `restored_index_1` 。
289 |
290 |
291 |
292 | 和快照类似, `restore` 命令也会立刻返回,恢复进程会在后台进行。如果你更希望你的 HTTP 调用阻塞直到恢复完成,添加 `wait_for_completion` 标记:
293 |
294 | ```
295 | POST _snapshot/my_backup/snapshot_1/_restore?wait_for_completion=true
296 | ```
297 |
298 |
299 |
300 |
301 | ### 监控恢复操作
302 |
303 | 从仓库恢复数据借鉴了 Elasticsearch 里已有的现行恢复机制。在内部实现上,从仓库恢复分片和从另一个节点恢复是等价的。
304 |
305 | 如果你想监控恢复的进度,你可以使用 `recovery` API。这是一个通用目的的 API,用来展示你集群中移动着的分片状态。
306 |
307 | 这个 API 可以为你在恢复的指定索引单独调用:
308 |
309 | ```
310 | GET restored_index_3/_recovery
311 | ```
312 |
313 | 或者查看你集群里所有索引,可能包括跟你的恢复进程无关的其他分片移动:
314 |
315 | ```
316 | GET /_recovery/
317 | ```
318 |
319 | 输出会跟这个类似(注意,根据你集群的活跃度,输出可能会变得非常啰嗦!):
320 |
321 | ```
322 | {
323 | "restored_index_3" : {
324 | "shards" : [ {
325 | "id" : 0,
326 | "type" : "snapshot", <1>
327 | "stage" : "index",
328 | "primary" : true,
329 | "start_time" : "2014-02-24T12:15:59.716",
330 | "stop_time" : 0,
331 | "total_time_in_millis" : 175576,
332 | "source" : { <2>
333 | "repository" : "my_backup",
334 | "snapshot" : "snapshot_3",
335 | "index" : "restored_index_3"
336 | },
337 | "target" : {
338 | "id" : "ryqJ5lO5S4-lSFbGntkEkg",
339 | "hostname" : "my.fqdn",
340 | "ip" : "10.0.1.7",
341 | "name" : "my_es_node"
342 | },
343 | "index" : {
344 | "files" : {
345 | "total" : 73,
346 | "reused" : 0,
347 | "recovered" : 69,
348 | "percent" : "94.5%" <3>
349 | },
350 | "bytes" : {
351 | "total" : 79063092,
352 | "reused" : 0,
353 | "recovered" : 68891939,
354 | "percent" : "87.1%"
355 | },
356 | "total_time_in_millis" : 0
357 | },
358 | "translog" : {
359 | "recovered" : 0,
360 | "total_time_in_millis" : 0
361 | },
362 | "start" : {
363 | "check_index_time" : 0,
364 | "total_time_in_millis" : 0
365 | }
366 | } ]
367 | }
368 | }
369 | ```
370 | * <1> `type` 字段告诉你恢复的本质;这个分片是在从一个快照恢复。
371 | * <2> `source` 哈希描述了作为恢复来源的特定快照和仓库。
372 | * <3> `percent` 字段让你对恢复的状态有个概念。这个特定分片目前已经恢复了 94% 的文件;它就快完成了。
373 |
374 | 输出会列出所有目前正在经历恢复的索引,然后列出这些索引里的所有分片。每个分片里会有启动/停止时间、持续时间、恢复百分比、传输字节数等统计值。
375 |
376 | ### 取消一个恢复
377 |
378 | 要取消一个恢复,你需要删除正在恢复的索引。因为恢复进程其实就是分片恢复,发送一个 `删除索引` API 修改集群状态,就可以停止恢复进程。比如:
379 |
380 | ```
381 | DELETE /restored_index_3
382 | ```
383 |
384 | 如果 `restored_index_3` 正在恢复中,这个删除命令会停止恢复,同时删除所有已经恢复到集群里的数据。
385 |
386 |
387 |
388 |
389 |
390 |
391 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.opensearch.es
8 | elasticsearch-repository-oss
9 | 6.7.0
10 |
11 |
12 |
13 | 6.7.0
14 | 7.7.0
15 | 1.8
16 | elasticsearch-repository-oss
17 | ${project.basedir}/src/main/assemblies/plugin.xml
18 |
19 | elasticsearch-repository-oss
20 |
21 | org.elasticsearch.plugin.repository.oss.OssRepositoryPlugin
22 |
23 | true
24 | true
25 | false
26 | true
27 | 4E899B30
28 | true
29 | UTF-8
30 |
31 |
32 |
33 | org.elasticsearch
34 | elasticsearch
35 | ${elasticsearch.version}
36 | compile
37 |
38 |
39 | securemock
40 | org.elasticsearch
41 |
42 |
43 |
44 |
45 | org.elasticsearch.test
46 | framework
47 | ${elasticsearch.version}
48 | test
49 |
50 |
51 | org.apache.lucene
52 | lucene-test-framework
53 | ${lucene.version}
54 | test
55 |
56 |
57 | com.google.guava
58 | guava
59 | 18.0
60 | compile
61 |
62 |
63 | org.slf4j
64 | slf4j-log4j12
65 | 1.7.12
66 | provided
67 |
68 |
69 | log4j
70 | log4j
71 | 1.2.17
72 | provided
73 |
74 |
75 |
76 |
77 | org.apache.logging.log4j
78 | log4j-core
79 | 2.9.1
80 | provided
81 |
82 |
83 |
84 | com.aliyun.oss
85 | aliyun-sdk-oss
86 | 2.8.1
87 |
88 |
89 | commons-beanutils
90 | commons-beanutils
91 |
92 |
93 | commons-collections
94 | commons-collections
95 |
96 |
97 |
98 |
99 | com.squareup.okhttp3
100 | okhttp
101 | 3.10.0
102 |
103 |
104 | com.alibaba
105 | fastjson
106 | 1.2.47
107 |
108 |
109 | junit
110 | junit
111 | 4.12
112 | test
113 |
114 |
115 | org.mockito
116 | mockito-core
117 | 1.10.19
118 | test
119 |
120 |
121 | com.squareup.okhttp3
122 | mockwebserver
123 | 3.10.0
124 | test
125 |
126 |
127 | org.mockito
128 | mockito-core
129 | 2.18.0
130 | test
131 |
132 |
133 | org.powermock
134 | powermock-api-mockito2
135 | 2.0.0-beta.5
136 | test
137 |
138 |
139 | org.powermock
140 | powermock-module-junit4
141 | 2.0.0-beta.5
142 | test
143 |
144 |
145 | org.elasticsearch
146 | securemock
147 | 1.2
148 | test
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 | maven-assembly-plugin
157 | 2.3
158 |
159 | false
160 |
161 |
162 | ${basedir}/src/main/assemblies/plugin.xml
163 |
164 |
165 |
166 |
167 | package
168 |
169 | single
170 |
171 |
172 |
173 |
174 |
175 |
176 | org.apache.maven.plugins
177 | maven-compiler-plugin
178 |
179 | ${maven.compiler.target}
180 | ${maven.compiler.target}
181 |
182 |
183 |
184 |
185 |
186 |
187 |
--------------------------------------------------------------------------------
/src/main/assemblies/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | elasticsearch-repository-oss
4 |
5 | zip
6 |
7 | false
8 |
9 |
10 | elasticsearch
11 | true
12 | true
13 |
14 | org.elasticsearch:elasticsearch
15 |
16 |
17 |
18 |
19 |
20 | src/main/resources
21 | /elasticsearch
22 |
23 | plugin-security.policy
24 | plugin-descriptor.properties
25 | log4j2.properties
26 |
27 |
28 |
29 |
30 | lib
31 |
32 |
33 | *.jar
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/main/java/org/elasticsearch/aliyun/oss/blobstore/OssBlobContainer.java:
--------------------------------------------------------------------------------
1 | package org.elasticsearch.aliyun.oss.blobstore;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.nio.file.FileAlreadyExistsException;
6 | import java.nio.file.NoSuchFileException;
7 | import java.util.Map;
8 |
9 | import com.aliyun.oss.ClientException;
10 | import com.aliyun.oss.OSSException;
11 | import org.apache.commons.lang.StringUtils;
12 | import org.apache.logging.log4j.LogManager;
13 | import org.apache.logging.log4j.Logger;
14 | import org.elasticsearch.common.blobstore.BlobMetaData;
15 | import org.elasticsearch.common.blobstore.BlobPath;
16 | import org.elasticsearch.common.blobstore.BlobStoreException;
17 | import org.elasticsearch.common.blobstore.support.AbstractBlobContainer;
18 | /**
19 | * A class for managing a oss repository of blob entries, where each blob entry is just a named group of bytes
20 | * Created by yangkongshi on 2017/11/24.
21 | */
22 | public class OssBlobContainer extends AbstractBlobContainer {
23 | private static final Logger logger = LogManager.getLogger(OssBlobContainer.class);
24 | protected final OssBlobStore blobStore;
25 | protected final String keyPath;
26 |
27 | public OssBlobContainer(BlobPath path, OssBlobStore blobStore) {
28 | super(path);
29 | this.keyPath = path.buildAsString();
30 | this.blobStore = blobStore;
31 | }
32 |
33 | /**
34 | * Tests whether a blob with the given blob name exists in the container.
35 | *
36 | * @param blobName The name of the blob whose existence is to be determined.
37 | * @return {@code true} if a blob exists in the BlobContainer with the given name, and {@code false} otherwise.
38 | */
39 | @Override
40 | public boolean blobExists(String blobName) {
41 | logger.trace("blobExists({})", blobName);
42 | try {
43 | return blobStore.blobExists(buildKey(blobName));
44 | } catch (OSSException | ClientException | IOException e) {
45 | logger.warn("can not access [{}] : {}", blobName, e.getMessage());
46 | throw new BlobStoreException("Failed to check if blob [" + blobName + "] exists", e);
47 | }
48 | }
49 |
50 | /**
51 | * Creates a new {@link InputStream} for the given blob name.
52 | *
53 | * @param blobName The name of the blob to get an {@link InputStream} for.
54 | * @return The {@code InputStream} to read the blob.
55 | * @throws NoSuchFileException if the blob does not exist
56 | * @throws IOException if the blob can not be read.
57 | */
58 | @Override
59 | public InputStream readBlob(String blobName) throws IOException {
60 | logger.trace("readBlob({})", blobName);
61 | if (!blobExists(blobName)) {
62 | throw new NoSuchFileException("[" + blobName + "] blob not found");
63 | }
64 | return blobStore.readBlob(buildKey(blobName));
65 | }
66 |
67 | /**
68 | * Reads blob content from the input stream and writes it to the container in a new blob with the given name.
69 | * This method assumes the container does not already contain a blob of the same blobName. If a blob by the
70 | * same name already exists, the operation will fail and an {@link IOException} will be thrown.
71 | *
72 | * @param blobName The name of the blob to write the contents of the input stream to.
73 | * @param inputStream The input stream from which to retrieve the bytes to write to the blob.
74 | * @param blobSize The size of the blob to be written, in bytes. It is implementation dependent whether
75 | * this value is used in writing the blob to the repository.
76 | * @param failIfAlreadyExists whether to throw a FileAlreadyExistsException if the given blob already exists
77 | * @throws FileAlreadyExistsException if failIfAlreadyExists is true and a blob by the same name already exists
78 | * @throws IOException if the input stream could not be read, or the target blob could not be written to.
79 | */
80 | @Override
81 | public void writeBlob(String blobName, InputStream inputStream, long blobSize, boolean failIfAlreadyExists)
82 | throws IOException {
83 | if (blobExists(blobName)) {
84 | if (failIfAlreadyExists) {
85 | throw new FileAlreadyExistsException(
86 | "blob [" + blobName + "] already exists, cannot overwrite");
87 | } else {
88 | deleteBlobIgnoringIfNotExists(blobName);
89 | }
90 | }
91 | logger.trace("writeBlob({}, stream, {})", blobName, blobSize);
92 | blobStore.writeBlob(buildKey(blobName), inputStream, blobSize);
93 | }
94 |
95 | /**
96 | * Deletes a blob with giving name, if the blob exists. If the blob does not exist, this method throws an
97 | * IOException.
98 | *
99 | * @param blobName The name of the blob to delete.
100 | * @throws NoSuchFileException if the blob does not exist
101 | * @throws IOException if the blob exists but could not be deleted.
102 | */
103 | @Override
104 | public void deleteBlob(String blobName) throws IOException {
105 | logger.trace("deleteBlob({})", blobName);
106 | if (!blobExists(blobName)) {
107 | throw new NoSuchFileException("Blob [" + blobName + "] does not exist");
108 | }
109 | try {
110 | blobStore.deleteBlob(buildKey(blobName));
111 | } catch (OSSException | ClientException e) {
112 | logger.warn("can not access [{}] : {}", blobName,
113 | e.getMessage());
114 | throw new IOException(e);
115 | }
116 |
117 | }
118 |
119 | /**
120 | * Lists all blobs in the container.
121 | *
122 | * @return A map of all the blobs in the container. The keys in the map are the names of the blobs and
123 | * the values are {@link BlobMetaData}, containing basic information about each blob.
124 | * @throws IOException if there were any failures in reading from the blob container.
125 | */
126 | @Override
127 | public Map listBlobs() throws IOException {
128 | return listBlobsByPrefix(null);
129 | }
130 |
131 | /**
132 | * Lists all blobs in the container.
133 | *
134 | * @return A map of all the blobs in the container. The keys in the map are the names of the blobs and
135 | * the values are {@link BlobMetaData}, containing basic information about each blob.
136 | * @throws IOException if there were any failures in reading from the blob container.
137 | */
138 | @Override
139 | public Map listBlobsByPrefix(String blobNamePrefix)
140 | throws IOException {
141 | logger.trace("listBlobsByPrefix({})", blobNamePrefix);
142 | try {
143 | return blobStore.listBlobsByPrefix(keyPath, blobNamePrefix);
144 | } catch (IOException e) {
145 | logger.warn("can not access [{}] : {}", blobNamePrefix, e.getMessage());
146 | throw new IOException(e);
147 | }
148 | }
149 |
150 | protected String buildKey(String blobName) {
151 | return keyPath + (blobName == null ? StringUtils.EMPTY : blobName);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/main/java/org/elasticsearch/aliyun/oss/blobstore/OssBlobStore.java:
--------------------------------------------------------------------------------
1 | package org.elasticsearch.aliyun.oss.blobstore;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.security.PrivilegedExceptionAction;
6 | import java.util.ArrayList;
7 | import java.util.Iterator;
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | import com.aliyun.oss.ClientException;
12 | import com.aliyun.oss.OSSException;
13 | import com.aliyun.oss.model.DeleteObjectsRequest;
14 | import com.aliyun.oss.model.ListObjectsRequest;
15 | import com.aliyun.oss.model.OSSObjectSummary;
16 | import com.aliyun.oss.model.ObjectListing;
17 | import com.aliyun.oss.model.ObjectMetadata;
18 | import org.apache.commons.lang.StringUtils;
19 | import org.elasticsearch.aliyun.oss.service.OssService;
20 | import org.elasticsearch.common.blobstore.BlobContainer;
21 | import org.elasticsearch.common.blobstore.BlobMetaData;
22 | import org.elasticsearch.common.blobstore.BlobPath;
23 | import org.elasticsearch.common.blobstore.BlobStore;
24 | import org.elasticsearch.common.blobstore.BlobStoreException;
25 | import org.elasticsearch.common.blobstore.support.PlainBlobMetaData;
26 | import org.elasticsearch.common.collect.MapBuilder;
27 | import org.elasticsearch.utils.PermissionHelper;
28 |
29 | /**
30 | * An oss blob store for managing oss client write and read blob directly
31 | * Created by yangkongshi on 2017/11/24.
32 | */
33 | public class OssBlobStore implements BlobStore {
34 |
35 | private final OssService client;
36 | private final String bucket;
37 |
38 | public OssBlobStore(String bucket, OssService client) {
39 | this.client = client;
40 | this.bucket = bucket;
41 | if (!doesBucketExist(bucket)) {
42 | throw new BlobStoreException("bucket does not exist");
43 | }
44 | }
45 |
46 | public String getBucket() {
47 | return this.bucket;
48 | }
49 |
50 | @Override
51 | public BlobContainer blobContainer(BlobPath blobPath) {
52 | return new OssBlobContainer(blobPath, this);
53 | }
54 |
55 | @Override
56 | public void delete(BlobPath blobPath) throws IOException {
57 | DeleteObjectsRequest deleteRequest = new DeleteObjectsRequest(bucket);
58 | Map blobs = listBlobsByPrefix(blobPath.buildAsString(), null);
59 | List toBeDeletedBlobs = new ArrayList<>();
60 | Iterator blobNameIterator = blobs.keySet().iterator();
61 | while (blobNameIterator.hasNext()) {
62 | String blobName = blobNameIterator.next();
63 | toBeDeletedBlobs.add(blobPath.buildAsString() + blobName);
64 | if (toBeDeletedBlobs.size() > DeleteObjectsRequest.DELETE_OBJECTS_ONETIME_LIMIT / 2
65 | || !blobNameIterator.hasNext()) {
66 | deleteRequest.setKeys(toBeDeletedBlobs);
67 | deleteObjects(deleteRequest);
68 | toBeDeletedBlobs.clear();
69 | }
70 | }
71 | }
72 |
73 | @Override
74 | public void close() {
75 | client.shutdown();
76 | }
77 |
78 | /**
79 | * Return true if the given bucket exists
80 | *
81 | * @param bucketName name of the bucket
82 | * @return true if the bucket exists, false otherwise
83 | */
84 | boolean doesBucketExist(String bucketName) {
85 | try {
86 | return doPrivilegedAndRefreshClient(() -> this.client.doesBucketExist(bucketName));
87 | } catch (IOException e) {
88 | throw new BlobStoreException("do privileged has failed", e);
89 | }
90 | }
91 |
92 | /**
93 | * List all blobs in the bucket which have a prefix
94 | *
95 | * @param prefix prefix of the blobs to list
96 | * @return a map of blob names and their metadata
97 | */
98 | Map listBlobsByPrefix(String keyPath, String prefix) throws IOException {
99 | MapBuilder blobsBuilder = MapBuilder.newMapBuilder();
100 | String actualPrefix = keyPath + (prefix == null ? StringUtils.EMPTY : prefix);
101 | String nextMarker = null;
102 | ObjectListing blobs;
103 | do {
104 | blobs = listBlobs(actualPrefix, nextMarker);
105 | for (OSSObjectSummary summary : blobs.getObjectSummaries()) {
106 | String blobName = summary.getKey().substring(keyPath.length());
107 | blobsBuilder.put(blobName, new PlainBlobMetaData(blobName, summary.getSize()));
108 | }
109 | nextMarker = blobs.getNextMarker();
110 | } while (blobs.isTruncated());
111 | return blobsBuilder.immutableMap();
112 | }
113 |
114 | /**
115 | * list blob with privilege check
116 | *
117 | * @param actualPrefix actual prefix of the blobs to list
118 | * @param nextMarker blobs next marker
119 | * @return {@link ObjectListing}
120 | */
121 | ObjectListing listBlobs(String actualPrefix, String nextMarker) throws IOException {
122 | return doPrivilegedAndRefreshClient(() -> this.client.listObjects(
123 | new ListObjectsRequest(bucket).withPrefix(actualPrefix).withMarker(nextMarker)
124 | ));
125 | }
126 |
127 | /**
128 | * Delete Objects
129 | *
130 | * @param deleteRequest {@link DeleteObjectsRequest}
131 | */
132 | void deleteObjects(DeleteObjectsRequest deleteRequest) throws IOException {
133 | doPrivilegedAndRefreshClient(() -> this.client.deleteObjects(deleteRequest));
134 | }
135 |
136 | /**
137 | * Returns true if the blob exists in the bucket
138 | *
139 | * @param blobName name of the blob
140 | * @return true if the blob exists, false otherwise
141 | */
142 | boolean blobExists(String blobName) throws OSSException, ClientException, IOException {
143 | return doPrivilegedAndRefreshClient(() -> this.client.doesObjectExist(bucket, blobName));
144 | }
145 |
146 | /**
147 | * Returns an {@link java.io.InputStream} for a given blob
148 | *
149 | * @param blobName name of the blob
150 | * @return an InputStream
151 | */
152 | InputStream readBlob(String blobName) throws OSSException, ClientException, IOException {
153 | return doPrivilegedAndRefreshClient(() -> this.client.getObject(bucket, blobName).getObjectContent());
154 | }
155 |
156 | /**
157 | * Writes a blob in the bucket.
158 | *
159 | * @param inputStream content of the blob to be written
160 | * @param blobSize expected size of the blob to be written
161 | */
162 | void writeBlob(String blobName, InputStream inputStream, long blobSize)
163 | throws OSSException, ClientException, IOException {
164 | ObjectMetadata meta = new ObjectMetadata();
165 | meta.setContentLength(blobSize);
166 | doPrivilegedAndRefreshClient(() -> this.client.putObject(bucket, blobName, inputStream, meta));
167 | }
168 |
169 | /**
170 | * Deletes a blob in the bucket
171 | *
172 | * @param blobName name of the blob
173 | */
174 | void deleteBlob(String blobName) throws OSSException, ClientException, IOException {
175 | doPrivilegedAndRefreshClient(() -> {
176 | this.client.deleteObject(bucket, blobName);
177 | return null;
178 | });
179 | }
180 |
181 | public void move(String sourceBlobName, String targetBlobName)
182 | throws OSSException, ClientException, IOException {
183 | doPrivilegedAndRefreshClient(() -> {
184 | this.client.copyObject(bucket, sourceBlobName, bucket, targetBlobName);
185 | return null;
186 | });
187 | doPrivilegedAndRefreshClient(() -> {
188 | this.client.deleteObject(bucket, sourceBlobName);
189 | return null;
190 | });
191 | }
192 |
193 | /**
194 | * Executes a {@link PrivilegedExceptionAction} with privileges enabled.
195 | */
196 | T doPrivilegedAndRefreshClient(PrivilegedExceptionAction operation) throws IOException {
197 | refreshStsOssClient();
198 | return PermissionHelper.doPrivileged(operation);
199 | }
200 |
201 | private void refreshStsOssClient() throws IOException {
202 | if (this.client.isUseStsOssClient()) {
203 | this.client.refreshStsOssClient();//refresh token to avoid expired
204 | }
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/src/main/java/org/elasticsearch/aliyun/oss/service/OssClientSettings.java:
--------------------------------------------------------------------------------
1 | package org.elasticsearch.aliyun.oss.service;
2 |
3 | import org.apache.commons.lang.StringUtils;
4 | import org.elasticsearch.common.settings.SecureString;
5 | import org.elasticsearch.common.settings.Setting;
6 | import org.elasticsearch.common.unit.ByteSizeUnit;
7 | import org.elasticsearch.common.unit.ByteSizeValue;
8 |
9 | import static org.elasticsearch.common.settings.Setting.Property;
10 | import static org.elasticsearch.common.settings.Setting.boolSetting;
11 | import static org.elasticsearch.common.settings.Setting.byteSizeSetting;
12 | import static org.elasticsearch.common.settings.Setting.simpleString;
13 |
14 | /**
15 | * OSS client configuration
16 | * Created by yangkongshi on 2017/11/27.
17 | */
18 | public class OssClientSettings {
19 | private static final ByteSizeValue MIN_CHUNK_SIZE = new ByteSizeValue(1, ByteSizeUnit.MB);
20 | private static final ByteSizeValue MAX_CHUNK_SIZE = new ByteSizeValue(1, ByteSizeUnit.GB);
21 |
22 | public static final Setting ACCESS_KEY_ID =
23 | new Setting<>("access_key_id", StringUtils.EMPTY, SecureString::new, Property.Filtered,
24 | Property.Dynamic, Property.NodeScope);
25 | public static final Setting SECRET_ACCESS_KEY =
26 | new Setting<>("secret_access_key", StringUtils.EMPTY, SecureString::new, Property.Filtered,
27 | Property.Dynamic, Property.NodeScope);
28 | public static final Setting ENDPOINT =
29 | Setting.simpleString("endpoint", Setting.Property.NodeScope, Setting.Property.Dynamic);
30 | public static final Setting SECURITY_TOKEN =
31 | new Setting<>("security_token", StringUtils.EMPTY, SecureString::new, Property.Filtered,
32 | Property.Dynamic, Property.NodeScope);
33 | public static final Setting BUCKET =
34 | simpleString("bucket", Setting.Property.NodeScope, Setting.Property.Dynamic);
35 | public static final Setting BASE_PATH = simpleString("base_path", Setting.Property.NodeScope,
36 | Setting.Property.Dynamic);
37 | public static final Setting COMPRESS =
38 | boolSetting("compress", false, Setting.Property.NodeScope, Setting.Property.Dynamic);
39 | public static final Setting CHUNK_SIZE =
40 | byteSizeSetting("chunk_size", MAX_CHUNK_SIZE, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE,
41 | Setting.Property.NodeScope, Setting.Property.Dynamic);
42 | public static final Setting ECS_RAM_ROLE =
43 | new Setting<>("ecs_ram_role", StringUtils.EMPTY, SecureString::new, Property.Filtered,
44 | Property.Dynamic, Property.NodeScope);
45 | public static final Setting AUTO_SNAPSHOT_BUCKET =
46 | new Setting<>("auto_snapshot_bucket", StringUtils.EMPTY, SecureString::new, Property.Filtered,
47 | Property.Dynamic, Property.NodeScope);
48 | public static final Setting SUPPORT_CNAME =
49 | boolSetting("support_cname", true, Setting.Property.NodeScope, Setting.Property.Dynamic);
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/org/elasticsearch/aliyun/oss/service/OssService.java:
--------------------------------------------------------------------------------
1 | package org.elasticsearch.aliyun.oss.service;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 |
6 | import com.aliyun.oss.ClientException;
7 | import com.aliyun.oss.OSSException;
8 | import com.aliyun.oss.model.Bucket;
9 | import com.aliyun.oss.model.CopyObjectResult;
10 | import com.aliyun.oss.model.DeleteObjectsRequest;
11 | import com.aliyun.oss.model.DeleteObjectsResult;
12 | import com.aliyun.oss.model.ListObjectsRequest;
13 | import com.aliyun.oss.model.OSSObject;
14 | import com.aliyun.oss.model.ObjectListing;
15 | import com.aliyun.oss.model.ObjectMetadata;
16 | import com.aliyun.oss.model.PutObjectResult;
17 | import org.elasticsearch.aliyun.oss.service.exception.CreateStsOssClientException;
18 |
19 | /**
20 | * OSS Service interface for creating oss client.
21 | * Created by yangkongshi on 2017/11/24.
22 | */
23 | public interface OssService {
24 |
25 | /**
26 | * Bulk delete the specified bucket {@link OSSObject}.
27 | *
28 | * @param deleteObjectsRequest request parameters {@link DeleteObjectsRequest}
29 | * @return delete results in bulk.
30 | */
31 | DeleteObjectsResult deleteObjects(DeleteObjectsRequest deleteObjectsRequest)
32 | throws OSSException, ClientException;
33 |
34 | /**
35 | * Determines if the specified {@link OSSObject} exists under the specified {@link Bucket}.
36 | *
37 | * @param bucketName bucket name
38 | * @param key Object Key
39 | * @return {@code true} if a blob exists with the given name, and {@code false} otherwise.
40 | */
41 | boolean doesObjectExist(String bucketName, String key) throws OSSException, ClientException;
42 |
43 | /**
44 | * Determines if a given {@link Bucket} exists.
45 | *
46 | * @param bucketName bucket name
47 | */
48 | boolean doesBucketExist(String bucketName) throws OSSException, ClientException;
49 |
50 | /**
51 | * List {@link OSSObject} under the specified {@link Bucket}.
52 | *
53 | * @param listObjectsRequest request information
54 | * @return object list {@link ObjectListing}
55 | * @throws OSSException
56 | * @throws ClientException
57 | */
58 | ObjectListing listObjects(ListObjectsRequest listObjectsRequest)
59 | throws OSSException, ClientException;
60 |
61 | /**
62 | * Export {@link OSSObject} from the OSS specified {@link Bucket}.
63 | *
64 | * @param bucketName Bucket name.
65 | * @param key Object Key.
66 | * @return Request result {@link OSSObject} instance. After use, you need to manually
67 | * close the ObjectContent release request connection.
68 | */
69 | OSSObject getObject(String bucketName, String key)
70 | throws OSSException, ClientException, IOException;
71 |
72 | /**
73 | * Upload the specified {@link OSSObject} to the {@link Bucket} specified in OSS.
74 | *
75 | * @param bucketName Bucket name
76 | * @param key object key
77 | * @param input inputStream
78 | * @param metadata the meta-information of the object {@link ObjectMetadata},
79 | * if the meta-information does not contain Content-Length,
80 | * transmits the request data in chunked encoding.
81 | */
82 | PutObjectResult putObject(String bucketName, String key, InputStream input,
83 | ObjectMetadata metadata) throws OSSException, ClientException, IOException;
84 |
85 | /**
86 | * Delete the specified {@link OSSObject}.
87 | *
88 | * @param bucketName Bucket name.
89 | * @param key Object key.
90 | * @throws OSSException
91 | * @throws ClientException
92 | */
93 | void deleteObject(String bucketName, String key) throws OSSException, ClientException;
94 |
95 | /**
96 | * Copy an Object that already exists on the OSS into another Object.
97 | *
98 | * @param sourceBucketName the name of the bucket where the source object resides.
99 | * @param sourceKey key of source Object.
100 | * @param destinationBucketName the name of the bucket where the target object is.
101 | * @param destinationKey key of the target Object.
102 | * @return request result {@link CopyObjectResult} instance.
103 | * @throws OSSException
104 | * @throws ClientException
105 | */
106 | CopyObjectResult copyObject(String sourceBucketName, String sourceKey,
107 | String destinationBucketName, String destinationKey) throws OSSException, ClientException;
108 |
109 | /**
110 | * Closes the Client instance and frees all the resources that are in use.
111 | * Once closed, no requests to the OSS will be processed anymore.
112 | */
113 | void shutdown();
114 |
115 | /**
116 | * refresh StsOssClient instance which construct via sts-accessKeyId, sts-accessKeySecret, sts-securityToken
117 | * Once refreshed, sts-securityToken is valid.
118 | */
119 | void refreshStsOssClient() throws CreateStsOssClientException;
120 |
121 | /**
122 | * judge if use Sts OssClient
123 | */
124 | boolean isUseStsOssClient();
125 | }
126 |
--------------------------------------------------------------------------------
/src/main/java/org/elasticsearch/aliyun/oss/service/OssServiceImpl.java:
--------------------------------------------------------------------------------
1 | package org.elasticsearch.aliyun.oss.service;
2 |
3 | import java.io.InputStream;
4 |
5 | import com.aliyun.oss.ClientException;
6 | import com.aliyun.oss.OSSException;
7 | import com.aliyun.oss.model.CopyObjectResult;
8 | import com.aliyun.oss.model.DeleteObjectsRequest;
9 | import com.aliyun.oss.model.DeleteObjectsResult;
10 | import com.aliyun.oss.model.ListObjectsRequest;
11 | import com.aliyun.oss.model.OSSObject;
12 | import com.aliyun.oss.model.ObjectListing;
13 | import com.aliyun.oss.model.ObjectMetadata;
14 | import com.aliyun.oss.model.PutObjectResult;
15 | import org.elasticsearch.aliyun.oss.service.exception.CreateStsOssClientException;
16 | import org.elasticsearch.cluster.metadata.RepositoryMetaData;
17 |
18 | /**
19 | * OSS Service implementation for creating oss client
20 | * Created by yangkongshi on 2017/11/24.
21 | */
22 | public class OssServiceImpl implements OssService {
23 |
24 | private OssStorageClient ossStorageClient;
25 |
26 | public OssServiceImpl(RepositoryMetaData metadata) throws CreateStsOssClientException {
27 | this.ossStorageClient = new OssStorageClient(metadata);
28 | }
29 |
30 | @Override
31 | public DeleteObjectsResult deleteObjects(DeleteObjectsRequest deleteObjectsRequest)
32 | throws OSSException, ClientException {
33 | return this.ossStorageClient.deleteObjects(deleteObjectsRequest);
34 | }
35 |
36 | @Override
37 | public boolean doesObjectExist(String bucketName, String key)
38 | throws OSSException, ClientException {
39 | return this.ossStorageClient.doesObjectExist(bucketName, key);
40 | }
41 |
42 | @Override
43 | public boolean doesBucketExist(String bucketName)
44 | throws OSSException, ClientException {
45 | return this.ossStorageClient.doesBucketExist(bucketName);
46 | }
47 |
48 | @Override
49 | public ObjectListing listObjects(ListObjectsRequest listObjectsRequest)
50 | throws OSSException, ClientException {
51 | return this.ossStorageClient.listObjects(listObjectsRequest);
52 | }
53 |
54 | @Override
55 | public OSSObject getObject(String bucketName, String key)
56 | throws OSSException, ClientException {
57 | return this.ossStorageClient.getObject(bucketName, key);
58 | }
59 |
60 | @Override
61 | public PutObjectResult putObject(String bucketName, String key, InputStream input,
62 | ObjectMetadata metadata) throws OSSException, ClientException {
63 | return this.ossStorageClient.putObject(bucketName, key, input, metadata);
64 | }
65 |
66 | @Override
67 | public void deleteObject(String bucketName, String key)
68 | throws OSSException, ClientException {
69 | this.ossStorageClient.deleteObject(bucketName, key);
70 | }
71 |
72 | @Override
73 | public CopyObjectResult copyObject(String sourceBucketName, String sourceKey,
74 | String destinationBucketName, String destinationKey) throws OSSException, ClientException {
75 | return this.ossStorageClient
76 | .copyObject(sourceBucketName, sourceKey, destinationBucketName, destinationKey);
77 | }
78 |
79 | @Override
80 | public void shutdown() {
81 | ossStorageClient.shutdown();
82 | }
83 |
84 | @Override
85 | public void refreshStsOssClient() throws CreateStsOssClientException {
86 | ossStorageClient.refreshStsOssClient();
87 | }
88 |
89 | @Override
90 | public boolean isUseStsOssClient() {
91 | return ossStorageClient.isStsOssClient();
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/main/java/org/elasticsearch/aliyun/oss/service/OssStorageClient.java:
--------------------------------------------------------------------------------
1 | package org.elasticsearch.aliyun.oss.service;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.util.Date;
6 | import java.util.concurrent.locks.ReadWriteLock;
7 | import java.util.concurrent.locks.ReentrantReadWriteLock;
8 |
9 | import com.alibaba.fastjson.JSON;
10 | import com.alibaba.fastjson.JSONObject;
11 |
12 | import com.aliyun.oss.ClientConfiguration;
13 | import com.aliyun.oss.ClientException;
14 | import com.aliyun.oss.OSSClient;
15 | import com.aliyun.oss.OSSException;
16 | import com.aliyun.oss.model.CopyObjectResult;
17 | import com.aliyun.oss.model.DeleteObjectsRequest;
18 | import com.aliyun.oss.model.DeleteObjectsResult;
19 | import com.aliyun.oss.model.ListObjectsRequest;
20 | import com.aliyun.oss.model.OSSObject;
21 | import com.aliyun.oss.model.ObjectListing;
22 | import com.aliyun.oss.model.ObjectMetadata;
23 | import com.aliyun.oss.model.PutObjectResult;
24 | import okhttp3.Response;
25 | import org.apache.commons.lang.StringUtils;
26 | import org.apache.logging.log4j.LogManager;
27 | import org.apache.logging.log4j.Logger;
28 | import org.elasticsearch.aliyun.oss.blobstore.OssBlobContainer;
29 | import org.elasticsearch.aliyun.oss.service.exception.CreateStsOssClientException;
30 | import org.elasticsearch.cluster.metadata.RepositoryMetaData;
31 | import org.elasticsearch.common.settings.SecureString;
32 | import org.elasticsearch.repository.oss.OssRepository;
33 | import org.elasticsearch.utils.DateHelper;
34 | import org.elasticsearch.utils.HttpClientHelper;
35 |
36 | import static java.lang.Thread.sleep;
37 |
38 | /**
39 | * @author hanqing.zhq@alibaba-inc.com
40 | * @date 2018/6/26
41 | */
42 | public class OssStorageClient {
43 | private static final Logger logger = LogManager.getLogger(OssBlobContainer.class);
44 |
45 | private RepositoryMetaData metadata;
46 | private OSSClient client;
47 | private Date stsTokenExpiration;
48 | private String ECS_METADATA_SERVICE = "http://100.100.100.200/latest/meta-data/ram/security-credentials/";
49 | private final int IN_TOKEN_EXPIRED_MS = 5000;
50 | private final String ACCESS_KEY_ID = "AccessKeyId";
51 | private final String ACCESS_KEY_SECRET = "AccessKeySecret";
52 | private final String SECURITY_TOKEN = "SecurityToken";
53 | private final int REFRESH_RETRY_COUNT = 3;
54 | private boolean isStsOssClient;
55 | private ReadWriteLock readWriteLock;
56 |
57 | private final String EXPIRATION = "Expiration";
58 |
59 | public OssStorageClient(RepositoryMetaData metadata) throws CreateStsOssClientException {
60 | this.metadata = metadata;
61 | if (StringUtils.isNotEmpty(OssClientSettings.ECS_RAM_ROLE.get(metadata.settings()).toString())) {
62 | isStsOssClient = true;
63 | } else {
64 | isStsOssClient = false;
65 | }
66 | readWriteLock = new ReentrantReadWriteLock();
67 | client = createClient(metadata);
68 |
69 | }
70 |
71 | public boolean isStsOssClient() {
72 | return isStsOssClient;
73 | }
74 |
75 | public DeleteObjectsResult deleteObjects(DeleteObjectsRequest deleteObjectsRequest)
76 | throws OSSException, ClientException {
77 | if (isStsOssClient) {
78 | readWriteLock.readLock().lock();
79 | try {
80 | return this.client.deleteObjects(deleteObjectsRequest);
81 | } finally {
82 | readWriteLock.readLock().unlock();
83 | }
84 | } else {
85 | return this.client.deleteObjects(deleteObjectsRequest);
86 | }
87 | }
88 |
89 | public boolean doesObjectExist(String bucketName, String key)
90 | throws OSSException, ClientException {
91 | if (isStsOssClient) {
92 | readWriteLock.readLock().lock();
93 | try {
94 | return this.client.doesObjectExist(bucketName, key);
95 | } finally {
96 | readWriteLock.readLock().unlock();
97 | }
98 | } else {
99 | return this.client.doesObjectExist(bucketName, key);
100 | }
101 | }
102 |
103 | public boolean doesBucketExist(String bucketName)
104 | throws OSSException, ClientException {
105 | if (isStsOssClient) {
106 | readWriteLock.readLock().lock();
107 | try {
108 | return this.client.doesBucketExist(bucketName);
109 | } finally {
110 | readWriteLock.readLock().unlock();
111 | }
112 | } else {
113 | return this.client.doesBucketExist(bucketName);
114 | }
115 | }
116 |
117 | public ObjectListing listObjects(ListObjectsRequest listObjectsRequest)
118 | throws OSSException, ClientException {
119 | if (isStsOssClient) {
120 | readWriteLock.readLock().lock();
121 | try {
122 | return this.client.listObjects(listObjectsRequest);
123 | } finally {
124 | readWriteLock.readLock().unlock();
125 | }
126 | } else {
127 | return this.client.listObjects(listObjectsRequest);
128 | }
129 | }
130 |
131 | public OSSObject getObject(String bucketName, String key)
132 | throws OSSException, ClientException {
133 | if (isStsOssClient) {
134 | readWriteLock.readLock().lock();
135 | try {
136 | return this.client.getObject(bucketName, key);
137 | } finally {
138 | readWriteLock.readLock().unlock();
139 | }
140 | } else {
141 | return this.client.getObject(bucketName, key);
142 | }
143 | }
144 |
145 | public PutObjectResult putObject(String bucketName, String key, InputStream input,
146 | ObjectMetadata metadata) throws OSSException, ClientException {
147 | if (isStsOssClient) {
148 | readWriteLock.readLock().lock();
149 | try {
150 | return this.client.putObject(bucketName, key, input, metadata);
151 | } finally {
152 | readWriteLock.readLock().unlock();
153 | }
154 | } else {
155 | return this.client.putObject(bucketName, key, input, metadata);
156 | }
157 | }
158 |
159 | public void deleteObject(String bucketName, String key)
160 | throws OSSException, ClientException {
161 | if (isStsOssClient) {
162 | readWriteLock.readLock().lock();
163 | try {
164 | this.client.deleteObject(bucketName, key);
165 | } finally {
166 | readWriteLock.readLock().unlock();
167 | }
168 | } else {
169 | this.client.deleteObject(bucketName, key);
170 | }
171 |
172 | }
173 |
174 | public CopyObjectResult copyObject(String sourceBucketName, String sourceKey,
175 | String destinationBucketName, String destinationKey) throws OSSException, ClientException {
176 |
177 | if (isStsOssClient) {
178 | readWriteLock.readLock().lock();
179 | try {
180 | return this.client
181 | .copyObject(sourceBucketName, sourceKey, destinationBucketName, destinationKey);
182 | } finally {
183 | readWriteLock.readLock().unlock();
184 | }
185 | } else {
186 | return this.client
187 | .copyObject(sourceBucketName, sourceKey, destinationBucketName, destinationKey);
188 | }
189 | }
190 |
191 | public void refreshStsOssClient() throws CreateStsOssClientException {
192 | int retryCount = 0;
193 | while (isStsTokenExpired() || isTokenWillExpired()) {
194 | retryCount++;
195 | if (retryCount > REFRESH_RETRY_COUNT) {
196 | logger.error("Can't get valid token after retry {} times", REFRESH_RETRY_COUNT);
197 | throw new CreateStsOssClientException(
198 | "Can't get valid token after retry " + REFRESH_RETRY_COUNT + " times");
199 | }
200 | this.client = createStsOssClient(this.metadata);
201 | try {
202 | if (isStsTokenExpired() || isTokenWillExpired()) {
203 | sleep(IN_TOKEN_EXPIRED_MS * 2);
204 | }
205 | } catch (InterruptedException e) {
206 | logger.error("refresh sleep exception", e);
207 | throw new CreateStsOssClientException(e);
208 | }
209 | }
210 | }
211 |
212 | public void shutdown() {
213 | if (isStsOssClient) {
214 | readWriteLock.writeLock().lock();
215 | try {
216 | if (null != this.client) {
217 | this.client.shutdown();
218 | }
219 | } finally {
220 | readWriteLock.writeLock().unlock();
221 | }
222 | } else {
223 | if (null != this.client) {
224 | this.client.shutdown();
225 | }
226 | }
227 | }
228 |
229 | private boolean isStsTokenExpired() {
230 | boolean expired = true;
231 | Date now = new Date();
232 | if (null != stsTokenExpiration) {
233 | if (stsTokenExpiration.after(now)) {
234 | expired = false;
235 | }
236 | }
237 | return expired;
238 | }
239 |
240 | private boolean isTokenWillExpired() {
241 | boolean in = true;
242 | Date now = new Date();
243 | long millisecond = stsTokenExpiration.getTime() - now.getTime();
244 | if (millisecond >= IN_TOKEN_EXPIRED_MS) {
245 | in = false;
246 | }
247 | return in;
248 | }
249 |
250 | private OSSClient createClient(RepositoryMetaData repositoryMetaData) throws CreateStsOssClientException {
251 | OSSClient client;
252 |
253 | String ecsRamRole = OssClientSettings.ECS_RAM_ROLE.get(repositoryMetaData.settings()).toString();
254 | String stsToken = OssClientSettings.SECURITY_TOKEN.get(repositoryMetaData.settings()).toString();
255 | /*
256 | * If ecsRamRole exist
257 | * means use ECS metadata service to get ststoken for auto snapshot.
258 | * */
259 | if (StringUtils.isNotEmpty(ecsRamRole.toString())) {
260 | client = createStsOssClient(repositoryMetaData);
261 | } else if (StringUtils.isNotEmpty(stsToken)) {
262 | //no used still now.
263 | client = createAKStsTokenClient(repositoryMetaData);
264 | } else {
265 | client = createAKOssClient(repositoryMetaData);
266 | }
267 | return client;
268 | }
269 |
270 | private ClientConfiguration extractClientConfiguration(RepositoryMetaData repositoryMetaData) {
271 | ClientConfiguration configuration = new ClientConfiguration();
272 | configuration.setSupportCname(OssRepository.getSetting(OssClientSettings.SUPPORT_CNAME, repositoryMetaData));
273 | return configuration;
274 | }
275 |
276 | private OSSClient createAKOssClient(RepositoryMetaData repositoryMetaData) {
277 | SecureString accessKeyId =
278 | OssRepository.getSetting(OssClientSettings.ACCESS_KEY_ID, repositoryMetaData);
279 | SecureString secretAccessKey =
280 | OssRepository.getSetting(OssClientSettings.SECRET_ACCESS_KEY, repositoryMetaData);
281 | String endpoint = OssRepository.getSetting(OssClientSettings.ENDPOINT, repositoryMetaData);
282 | return new OSSClient(endpoint, accessKeyId.toString(), secretAccessKey.toString(),
283 | extractClientConfiguration(repositoryMetaData));
284 | }
285 |
286 | private OSSClient createAKStsTokenClient(RepositoryMetaData repositoryMetaData) {
287 | SecureString securityToken = OssClientSettings.SECURITY_TOKEN.get(repositoryMetaData.settings());
288 | SecureString accessKeyId =
289 | OssRepository.getSetting(OssClientSettings.ACCESS_KEY_ID, repositoryMetaData);
290 | SecureString secretAccessKey =
291 | OssRepository.getSetting(OssClientSettings.SECRET_ACCESS_KEY, repositoryMetaData);
292 | String endpoint = OssRepository.getSetting(OssClientSettings.ENDPOINT, repositoryMetaData);
293 | return new OSSClient(endpoint, accessKeyId.toString(), secretAccessKey.toString(),
294 | securityToken.toString(), extractClientConfiguration(repositoryMetaData));
295 | }
296 |
297 | private synchronized OSSClient createStsOssClient(RepositoryMetaData repositoryMetaData)
298 | throws CreateStsOssClientException {
299 | if (isStsTokenExpired() || isTokenWillExpired()) {
300 | try {
301 | if (null == repositoryMetaData) {
302 | throw new IOException("repositoryMetaData is null");
303 | }
304 | String ecsRamRole = OssClientSettings.ECS_RAM_ROLE.get(repositoryMetaData.settings()).toString();
305 | String endpoint = OssRepository.getSetting(OssClientSettings.ENDPOINT, repositoryMetaData);
306 |
307 | String fullECSMetaDataServiceUrl = ECS_METADATA_SERVICE + ecsRamRole;
308 | Response response = HttpClientHelper.httpRequest(fullECSMetaDataServiceUrl);
309 | if (!response.isSuccessful()) {
310 | throw new IOException("ECS meta service server error");
311 | }
312 | String jsonStringResponse = response.body().string();
313 | JSONObject jsonObjectResponse = JSON.parseObject(jsonStringResponse);
314 | String accessKeyId = jsonObjectResponse.getString(ACCESS_KEY_ID);
315 | String accessKeySecret = jsonObjectResponse.getString(ACCESS_KEY_SECRET);
316 | String securityToken = jsonObjectResponse.getString(SECURITY_TOKEN);
317 | stsTokenExpiration = DateHelper.convertStringToDate(jsonObjectResponse.getString(EXPIRATION));
318 | try {
319 | readWriteLock.writeLock().lock();
320 | if (null != this.client) {
321 | this.client.shutdown();
322 | }
323 | this.client = new OSSClient(endpoint, accessKeyId, accessKeySecret, securityToken,
324 | extractClientConfiguration(repositoryMetaData));
325 | } finally {
326 | readWriteLock.writeLock().unlock();
327 | }
328 | response.close();
329 | } catch (IOException e) {
330 | logger.error("create stsOssClient exception", e);
331 | throw new CreateStsOssClientException(e);
332 | }
333 | return this.client;
334 | } else {
335 | return this.client;
336 | }
337 | }
338 | }
339 |
--------------------------------------------------------------------------------
/src/main/java/org/elasticsearch/aliyun/oss/service/exception/CreateStsOssClientException.java:
--------------------------------------------------------------------------------
1 | package org.elasticsearch.aliyun.oss.service.exception;
2 |
3 | import java.io.IOException;
4 |
5 | /**
6 | * @author hanqing.zhq@alibaba-inc.com
7 | * @date 2018/4/17
8 | */
9 | public class CreateStsOssClientException extends IOException {
10 | public CreateStsOssClientException(Throwable e) {
11 | super(e);
12 | }
13 |
14 | public CreateStsOssClientException(String s) {
15 | super(s);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/org/elasticsearch/plugin/repository/oss/OssRepositoryPlugin.java:
--------------------------------------------------------------------------------
1 | package org.elasticsearch.plugin.repository.oss;
2 |
3 | import java.security.AccessController;
4 | import java.security.PrivilegedAction;
5 | import java.util.Arrays;
6 | import java.util.Collections;
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | import org.apache.logging.log4j.LogManager;
11 | import org.apache.logging.log4j.Logger;
12 | import org.elasticsearch.SpecialPermission;
13 | import org.elasticsearch.aliyun.oss.blobstore.OssBlobContainer;
14 | import org.elasticsearch.aliyun.oss.service.OssClientSettings;
15 | import org.elasticsearch.aliyun.oss.service.OssService;
16 | import org.elasticsearch.aliyun.oss.service.OssServiceImpl;
17 | import org.elasticsearch.aliyun.oss.service.exception.CreateStsOssClientException;
18 | import org.elasticsearch.cluster.metadata.RepositoryMetaData;
19 | import org.elasticsearch.common.settings.Setting;
20 | import org.elasticsearch.common.xcontent.NamedXContentRegistry;
21 | import org.elasticsearch.env.Environment;
22 | import org.elasticsearch.plugins.Plugin;
23 | import org.elasticsearch.plugins.RepositoryPlugin;
24 | import org.elasticsearch.repositories.Repository;
25 | import org.elasticsearch.repository.oss.OssRepository;
26 |
27 | /**
28 | * A plugin to add a repository type that writes to and from OSS.
29 | * Created by yangkongshi on 2017/11/24.
30 | */
31 | public class OssRepositoryPlugin extends Plugin implements RepositoryPlugin {
32 |
33 | private static final Logger logger = LogManager.getLogger(OssBlobContainer.class);
34 |
35 | static {
36 | SecurityManager sm = System.getSecurityManager();
37 | if (sm != null) {
38 | sm.checkPermission(new SpecialPermission());
39 | }
40 | AccessController.doPrivileged((PrivilegedAction)() -> {
41 | return null;
42 | });
43 | }
44 |
45 | protected OssService createStorageService(RepositoryMetaData metadata)
46 | throws CreateStsOssClientException {
47 | return new OssServiceImpl(metadata);
48 | }
49 |
50 | @Override
51 | public Map getRepositories(Environment env,
52 | NamedXContentRegistry namedXContentRegistry) {
53 | return Collections.singletonMap(OssRepository.TYPE,
54 | (metadata) -> new OssRepository(metadata, env, namedXContentRegistry,
55 | createStorageService(metadata)));
56 | }
57 |
58 | @Override
59 | public List> getSettings() {
60 | return Arrays.asList(OssClientSettings.ACCESS_KEY_ID, OssClientSettings.SECRET_ACCESS_KEY,
61 | OssClientSettings.ENDPOINT, OssClientSettings.BUCKET, OssClientSettings.SECURITY_TOKEN,
62 | OssClientSettings.BASE_PATH, OssClientSettings.COMPRESS, OssClientSettings.CHUNK_SIZE,
63 | OssClientSettings.AUTO_SNAPSHOT_BUCKET, OssClientSettings.ECS_RAM_ROLE, OssClientSettings.SUPPORT_CNAME);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/org/elasticsearch/repository/oss/OssRepository.java:
--------------------------------------------------------------------------------
1 | package org.elasticsearch.repository.oss;
2 |
3 | import java.io.File;
4 |
5 | import org.apache.commons.lang.StringUtils;
6 | import org.apache.logging.log4j.LogManager;
7 | import org.apache.logging.log4j.Logger;
8 | import org.elasticsearch.aliyun.oss.blobstore.OssBlobContainer;
9 | import org.elasticsearch.aliyun.oss.blobstore.OssBlobStore;
10 | import org.elasticsearch.aliyun.oss.service.OssClientSettings;
11 | import org.elasticsearch.aliyun.oss.service.OssService;
12 | import org.elasticsearch.cluster.metadata.RepositoryMetaData;
13 | import org.elasticsearch.common.Strings;
14 | import org.elasticsearch.common.blobstore.BlobPath;
15 | import org.elasticsearch.common.blobstore.BlobStore;
16 | import org.elasticsearch.common.settings.Setting;
17 | import org.elasticsearch.common.unit.ByteSizeValue;
18 | import org.elasticsearch.common.xcontent.NamedXContentRegistry;
19 | import org.elasticsearch.env.Environment;
20 | import org.elasticsearch.repositories.RepositoryException;
21 | import org.elasticsearch.repositories.blobstore.BlobStoreRepository;
22 |
23 | /**
24 | * An oss repository working for snapshot and restore.
25 | * Implementations are responsible for reading and writing both metadata and shard data to and from
26 | * a repository backend.
27 | * Created by yangkongshi on 2017/11/24.
28 | */
29 | public class OssRepository extends BlobStoreRepository {
30 |
31 | private static final Logger logger = LogManager.getLogger(OssBlobContainer.class);
32 |
33 | public static final String TYPE = "oss";
34 | private final BlobPath basePath;
35 | private final boolean compress;
36 | private final ByteSizeValue chunkSize;
37 | private final String bucket;
38 | private final OssService ossService;
39 |
40 | public OssRepository(RepositoryMetaData metadata, Environment env,
41 | NamedXContentRegistry namedXContentRegistry, OssService ossService) {
42 | super(metadata, env.settings(), namedXContentRegistry);
43 | this.ossService = ossService;
44 | String ecsRamRole = OssClientSettings.ECS_RAM_ROLE.get(metadata.settings()).toString();
45 | if (StringUtils.isNotEmpty(ecsRamRole)) {
46 | this.bucket = getSetting(OssClientSettings.AUTO_SNAPSHOT_BUCKET, metadata).toString();
47 | } else {
48 | this.bucket = getSetting(OssClientSettings.BUCKET, metadata);
49 | }
50 | String basePath = OssClientSettings.BASE_PATH.get(metadata.settings());
51 | if (Strings.hasLength(basePath)) {
52 | BlobPath path = new BlobPath();
53 | for (String elem : basePath.split(File.separator)) {
54 | path = path.add(elem);
55 | }
56 | this.basePath = path;
57 | } else {
58 | this.basePath = BlobPath.cleanPath();
59 | }
60 | this.compress = getSetting(OssClientSettings.COMPRESS, metadata);
61 | this.chunkSize = getSetting(OssClientSettings.CHUNK_SIZE, metadata);
62 | logger.info("Using base_path [{}], chunk_size [{}], compress [{}]",
63 | basePath, chunkSize, compress);
64 | }
65 |
66 | @Override
67 | protected BlobStore createBlobStore() throws Exception {
68 | return new OssBlobStore(bucket, ossService);
69 | }
70 |
71 | @Override
72 | protected BlobPath basePath() {
73 | return basePath;
74 | }
75 |
76 | @Override
77 | protected boolean isCompress() {
78 | return compress;
79 | }
80 |
81 | @Override
82 | protected ByteSizeValue chunkSize() {
83 | return chunkSize;
84 | }
85 |
86 | public static T getSetting(Setting setting, RepositoryMetaData metadata) {
87 | T value = setting.get(metadata.settings());
88 | if (value == null) {
89 | throw new RepositoryException(metadata.name(),
90 | "Setting [" + setting.getKey() + "] is not defined for repository");
91 | }
92 | if ((value instanceof String) && (Strings.hasText((String)value)) == false) {
93 | throw new RepositoryException(metadata.name(),
94 | "Setting [" + setting.getKey() + "] is empty for repository");
95 | }
96 | return value;
97 | }
98 |
99 | /** mainly for test **/
100 | public OssService getOssService() {
101 | return ossService;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/java/org/elasticsearch/utils/DateHelper.java:
--------------------------------------------------------------------------------
1 | package org.elasticsearch.utils;
2 |
3 | import java.text.ParseException;
4 | import java.text.SimpleDateFormat;
5 | import java.util.Date;
6 | import java.util.TimeZone;
7 |
8 | import org.apache.logging.log4j.LogManager;
9 | import org.apache.logging.log4j.Logger;
10 |
11 | /**
12 | * @author hanqing.zhq@alibaba-inc.com
13 | * @date 2018/4/16
14 | */
15 | public class DateHelper {
16 | private static final Logger logger = LogManager.getLogger(DateHelper.class);
17 |
18 | public static Date convertStringToDate(String dateString) {
19 | String dateRegex = "yyyy-MM-dd'T'HH:mm:ss'Z'";
20 | SimpleDateFormat sdf = new SimpleDateFormat(dateRegex);
21 | sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
22 | Date returnDate = null;
23 | try {
24 | returnDate = sdf.parse(dateString);
25 | } catch (ParseException e) {
26 | logger.error("convert String to Date type error", e);
27 | }
28 | return returnDate;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/org/elasticsearch/utils/HttpClientHelper.java:
--------------------------------------------------------------------------------
1 | package org.elasticsearch.utils;
2 |
3 | import java.io.IOException;
4 |
5 | import okhttp3.OkHttpClient;
6 | import okhttp3.Request;
7 | import okhttp3.Response;
8 |
9 | /**
10 | * @author hanqing.zhq@alibaba-inc.com
11 | * @date 2018/4/22
12 | */
13 | public class HttpClientHelper {
14 |
15 | private static OkHttpClient httpClient;
16 |
17 | private HttpClientHelper() { }
18 |
19 | public static synchronized OkHttpClient getHTTPClient() {
20 | if (null == httpClient) {
21 | try {
22 | PermissionHelper.doPrivileged(() -> {
23 | httpClient = new OkHttpClient();
24 | return httpClient;
25 | });
26 | } catch (IOException e) {
27 | return httpClient;
28 | }
29 | }
30 | return httpClient;
31 | }
32 |
33 | public static Response httpRequest(String url) throws IOException {
34 | return PermissionHelper.doPrivileged(() -> {
35 | Request request = new Request.Builder().get().url(url).build();
36 | return getHTTPClient().newCall(request).execute();
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/org/elasticsearch/utils/PermissionHelper.java:
--------------------------------------------------------------------------------
1 | package org.elasticsearch.utils;
2 |
3 | import java.io.IOException;
4 | import java.security.AccessController;
5 | import java.security.PrivilegedActionException;
6 | import java.security.PrivilegedExceptionAction;
7 |
8 | import org.elasticsearch.SpecialPermission;
9 |
10 | /**
11 | * @author hanqing.zhq@alibaba-inc.com
12 | * @date 2018/4/18
13 | */
14 | public class PermissionHelper {
15 |
16 | /**
17 | * Executes a {@link PrivilegedExceptionAction} with privileges enabled.
18 | */
19 |
20 | public static T doPrivileged(PrivilegedExceptionAction operation) throws IOException {
21 | SecurityManager sm = System.getSecurityManager();
22 | if (sm != null) {
23 | sm.checkPermission(new SpecialPermission());
24 | }
25 | try {
26 | return AccessController.doPrivileged((PrivilegedExceptionAction)operation::run);
27 | } catch (PrivilegedActionException e) {
28 | throw (IOException)e.getException();
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/resources/plugin-descriptor.properties:
--------------------------------------------------------------------------------
1 | # Elasticsearch plugin descriptor file
2 | # This file must exist as 'plugin-descriptor.properties' at
3 | # the root directory of all plugins.
4 | #
5 | # A plugin can be 'site', 'jvm', or both.
6 | #
7 | ### example site plugin for "foo":
8 | #
9 | # foo.zip <-- zip file for the plugin, with this structure:
10 | # _site/ <-- the contents that will be served
11 | # plugin-descriptor.properties <-- example contents below:
12 | #
13 | # site=true
14 | # description=My cool plugin
15 | # version=1.0
16 | #
17 | ### example jvm plugin for "foo"
18 | #
19 | # foo.zip <-- zip file for the plugin, with this structure:
20 | # .jar <-- classes, resources, dependencies
21 | # .jar <-- any number of jars
22 | # plugin-descriptor.properties <-- example contents below:
23 | #
24 | # jvm=true
25 | # classname=foo.bar.BazPlugin
26 | # description=My cool plugin
27 | # version=2.0.0-rc1
28 | # elasticsearch.version=2.0
29 | # java.version=1.7
30 | #
31 | ### mandatory elements for all plugins:
32 | #
33 | # 'description': simple summary of the plugin
34 | description=The OSS repository plugin adds support for using Alibaba Cloud OSS as a repository for Snapshot/Restore.
35 | #
36 | # 'version': plugin's version
37 | version=6.7.0
38 | #
39 | # 'name': the plugin name
40 | name=elasticsearch-repository-oss
41 |
42 | ### mandatory elements for site plugins:
43 | #
44 | # 'site': set to true to indicate contents of the _site/
45 | # directory in the root of the plugin should be served.
46 | #site=${elasticsearch.plugin.site}
47 | #
48 | ### mandatory elements for jvm plugins :
49 | #
50 | # 'jvm': true if the 'classname' class should be loaded
51 | # from jar files in the root directory of the plugin.
52 | # Note that only jar files in the root directory are
53 | # added to the classpath for the plugin! If you need
54 | # other resources, package them into a resources jar.
55 | # jvm=true
56 | #
57 | # 'classname': the name of the class to load, fully-qualified.
58 | classname=org.elasticsearch.plugin.repository.oss.OssRepositoryPlugin
59 | #
60 | # 'java.version' version of java the code is built against
61 | # use the system property java.specification.version
62 | # version string must be a sequence of nonnegative decimal integers
63 | # separated by "."'s and may have leading zeros
64 | java.version=1.8
65 | #
66 | # 'elasticsearch.version' version of elasticsearch compiled against
67 | # You will have to release a new version of the plugin for each new
68 | # elasticsearch release. This version is checked when the plugin
69 | # is loaded so Elasticsearch will refuse to start in the presence of
70 | # plugins with the incorrect elasticsearch.version.
71 | elasticsearch.version=6.7.0
72 | #
73 | ### deprecated elements for jvm plugins :
74 | #
75 | # 'isolated': true if the plugin should have its own classloader.
76 | # passing false is deprecated, and only intended to support plugins
77 | # that have hard dependencies against each other. If this is
78 | # not specified, then the plugin is isolated by default.
79 | # isolated=true
80 | # date:2017-06-27
81 |
82 |
--------------------------------------------------------------------------------
/src/main/resources/plugin-security.policy:
--------------------------------------------------------------------------------
1 | grant {
2 | permission java.security.AllPermission;
3 | permission java.net.SocketPermission "*", "connect,resolve";
4 | permission java.lang.RuntimePermission "accessDeclaredMembers";
5 | permission java.lang.RuntimePermission "getClassLoader";
6 | permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
7 | permission java.net.NetPermission "getProxySelector";
8 | };
9 |
--------------------------------------------------------------------------------
/src/test/java/org/elasticsearch/aliyun/oss/blobstore/MockOssService.java:
--------------------------------------------------------------------------------
1 | package org.elasticsearch.aliyun.oss.blobstore;
2 |
3 | import java.io.ByteArrayInputStream;
4 | import java.io.ByteArrayOutputStream;
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.util.Locale;
8 | import java.util.Map;
9 | import java.util.concurrent.ConcurrentHashMap;
10 |
11 | import com.aliyun.oss.ClientException;
12 | import com.aliyun.oss.OSSException;
13 | import com.aliyun.oss.model.CopyObjectResult;
14 | import com.aliyun.oss.model.DeleteObjectsRequest;
15 | import com.aliyun.oss.model.DeleteObjectsResult;
16 | import com.aliyun.oss.model.ListObjectsRequest;
17 | import com.aliyun.oss.model.OSSObject;
18 | import com.aliyun.oss.model.OSSObjectSummary;
19 | import com.aliyun.oss.model.ObjectListing;
20 | import com.aliyun.oss.model.ObjectMetadata;
21 | import com.aliyun.oss.model.PutObjectResult;
22 | import org.elasticsearch.aliyun.oss.service.OssService;
23 | import org.elasticsearch.aliyun.oss.service.exception.CreateStsOssClientException;
24 | import org.elasticsearch.common.Strings;
25 | import org.elasticsearch.common.component.AbstractComponent;
26 | import org.elasticsearch.common.io.Streams;
27 |
28 | /**
29 | * Created by yangkongshi on 2017/11/28.
30 | */
31 | public class MockOssService extends AbstractComponent implements OssService {
32 |
33 | protected final Map blobs = new ConcurrentHashMap<>();
34 |
35 | public MockOssService() {
36 | super();
37 | }
38 |
39 | @Override
40 | public DeleteObjectsResult deleteObjects(DeleteObjectsRequest deleteObjectsRequest)
41 | throws OSSException, ClientException {
42 | for (String key : deleteObjectsRequest.getKeys()) {
43 | blobs.remove(key);
44 | }
45 | return null;
46 | }
47 |
48 | @Override
49 | public boolean doesObjectExist(String bucketName, String key)
50 | throws OSSException, ClientException {
51 | return blobs.containsKey(key);
52 | }
53 |
54 | @Override
55 | public boolean doesBucketExist(String bucketName)
56 | throws OSSException, ClientException {
57 | return true;
58 | }
59 |
60 | @Override
61 | public ObjectListing listObjects(ListObjectsRequest listObjectsRequest)
62 | throws OSSException, ClientException {
63 | ObjectListing objectListing = new ObjectListing();
64 | objectListing.setTruncated(false);
65 | String prefix = listObjectsRequest.getPrefix();
66 | blobs.forEach((String blobName, OSSObject ossObject) -> {
67 | if (!Strings.hasLength(prefix) || startsWithIgnoreCase(blobName, prefix)) {
68 | OSSObjectSummary summary = new OSSObjectSummary();
69 | summary.setKey(blobName);
70 | summary.setSize(ossObject.getObjectMetadata().getContentLength());
71 | objectListing.addObjectSummary(summary);
72 | }
73 | });
74 | return objectListing;
75 | }
76 |
77 | @Override
78 | public OSSObject getObject(String bucketName, String key)
79 | throws OSSException, ClientException, IOException {
80 | OSSObject ossObject = new OSSObject();
81 | synchronized (this) {
82 | OSSObject oldObject = blobs.get(key);
83 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
84 | Streams.copy(oldObject.getObjectContent(), outputStream);
85 | oldObject.setObjectContent(new ByteArrayInputStream(outputStream.toByteArray()));
86 | ossObject.setObjectContent(new ByteArrayInputStream(outputStream.toByteArray()));
87 | }
88 | return ossObject;
89 | }
90 |
91 | @Override
92 | public PutObjectResult putObject(String bucketName, String key, InputStream input,
93 | ObjectMetadata metadata) throws OSSException, ClientException, IOException {
94 | synchronized (this) {
95 | OSSObject ossObject = new OSSObject();
96 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
97 | Streams.copy(input, outputStream);
98 | ossObject.setObjectContent(new ByteArrayInputStream(outputStream.toByteArray()));
99 | ossObject.setObjectMetadata(metadata);
100 | blobs.put(key, ossObject);
101 | }
102 | return null;
103 | }
104 |
105 | @Override
106 | public void deleteObject(String bucketName, String key)
107 | throws OSSException, ClientException {
108 | blobs.remove(key);
109 | }
110 |
111 | @Override
112 | public CopyObjectResult copyObject(String sourceBucketName, String sourceKey,
113 | String destinationBucketName, String destinationKey) throws OSSException, ClientException {
114 | OSSObject sourceOssObject = blobs.get(sourceKey);
115 | blobs.put(destinationKey, sourceOssObject);
116 | return null;
117 | }
118 |
119 | @Override
120 | public void shutdown() {
121 | blobs.clear();
122 | }
123 |
124 | @Override
125 | public void refreshStsOssClient() throws CreateStsOssClientException {
126 |
127 | }
128 |
129 | @Override
130 | public boolean isUseStsOssClient() {
131 | return false;
132 | }
133 |
134 | /**
135 | * Test if the given String starts with the specified prefix,
136 | * ignoring upper/lower case.
137 | *
138 | * @param str the String to check
139 | * @param prefix the prefix to look for
140 | * @see java.lang.String#startsWith
141 | */
142 | public static boolean startsWithIgnoreCase(String str, String prefix) {
143 | if (str == null || prefix == null) {
144 | return false;
145 | }
146 | if (str.startsWith(prefix)) {
147 | return true;
148 | }
149 | if (str.length() < prefix.length()) {
150 | return false;
151 | }
152 | String lcStr = str.substring(0, prefix.length()).toLowerCase(Locale.ROOT);
153 | String lcPrefix = prefix.toLowerCase(Locale.ROOT);
154 | return lcStr.equals(lcPrefix);
155 | }
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/src/test/java/org/elasticsearch/aliyun/oss/blobstore/OssBlobContainerTest.java:
--------------------------------------------------------------------------------
1 | package org.elasticsearch.aliyun.oss.blobstore;
2 |
3 | import java.io.IOException;
4 | import java.util.Locale;
5 |
6 | import org.elasticsearch.common.blobstore.BlobStore;
7 | import org.elasticsearch.repositories.ESBlobStoreContainerTestCase;
8 |
9 | /**
10 | * Created by yangkongshi on 2017/11/28.
11 | */
12 | public class OssBlobContainerTest extends ESBlobStoreContainerTestCase {
13 | @Override
14 | protected BlobStore newBlobStore() throws IOException {
15 | String bucket = randomAlphaOfLength(randomIntBetween(1, 10)).toLowerCase(Locale.ROOT);
16 | MockOssService client = new MockOssService();
17 | return new OssBlobStore(bucket, client);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/test/java/org/elasticsearch/aliyun/oss/blobstore/OssBlobStoreTest.java:
--------------------------------------------------------------------------------
1 | package org.elasticsearch.aliyun.oss.blobstore;
2 |
3 | import java.io.IOException;
4 | import java.util.Locale;
5 |
6 | import org.elasticsearch.common.blobstore.BlobStore;
7 | import org.elasticsearch.repositories.ESBlobStoreTestCase;
8 |
9 | /**
10 | * Created by yangkongshi on 2017/11/28.
11 | */
12 | public class OssBlobStoreTest extends ESBlobStoreTestCase {
13 |
14 | @Override
15 | protected BlobStore newBlobStore() throws IOException {
16 | String bucket = randomAlphaOfLength(randomIntBetween(1, 10)).toLowerCase(Locale.ROOT);
17 | MockOssService client = new MockOssService();
18 | return new OssBlobStore(bucket, client);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/test/java/org/elasticsearch/bootstrap/JarHell.java:
--------------------------------------------------------------------------------
1 | package org.elasticsearch.bootstrap;
2 |
3 | import java.net.URL;
4 | import java.util.HashSet;
5 | import java.util.Set;
6 | import java.util.function.Consumer;
7 |
8 | /**
9 | * Created by yangkongshi on 2017/12/4.
10 | */
11 | public class JarHell {
12 | private JarHell() {
13 | }
14 |
15 | public static void checkJarHell() throws Exception {
16 | }
17 |
18 | public static void checkJarHell(URL urls[]) throws Exception {
19 | }
20 |
21 | public static void checkJarHell(Consumer output) throws Exception {
22 | }
23 |
24 | public static void checkVersionFormat(String targetVersion) {
25 | }
26 |
27 | public static void checkJavaVersion(String resource, String targetVersion) {
28 | }
29 |
30 | public static Set parseClassPath() {return new HashSet<>();}
31 | }
32 |
--------------------------------------------------------------------------------
/src/test/java/org/elasticsearch/repository/oss/OssRepositoryTest.java:
--------------------------------------------------------------------------------
1 | package org.elasticsearch.repository.oss;
2 |
3 | import java.util.Arrays;
4 | import java.util.Collection;
5 |
6 | import org.apache.commons.lang.StringUtils;
7 | import org.elasticsearch.aliyun.oss.blobstore.MockOssService;
8 | import org.elasticsearch.aliyun.oss.service.OssClientSettings;
9 | import org.elasticsearch.aliyun.oss.service.OssService;
10 | import org.elasticsearch.cluster.metadata.RepositoryMetaData;
11 | import org.elasticsearch.common.settings.Settings;
12 | import org.elasticsearch.common.unit.ByteSizeUnit;
13 | import org.elasticsearch.plugin.repository.oss.OssRepositoryPlugin;
14 | import org.elasticsearch.plugins.Plugin;
15 | import org.elasticsearch.repositories.blobstore.ESBlobStoreRepositoryIntegTestCase;
16 |
17 | import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
18 |
19 | /**
20 | * Created by yangkongshi on 2017/11/28.
21 | */
22 | public class OssRepositoryTest extends ESBlobStoreRepositoryIntegTestCase {
23 | private static final String BUCKET = "oss-repository-test";
24 | private static final OssService client = new MockOssService();
25 |
26 | @Override
27 | protected Collection> nodePlugins() {
28 | return Arrays.asList(MockOssRepositoryPlugin.class);
29 | }
30 |
31 | @Override
32 | protected void createTestRepository(String name, boolean verify) {
33 | assertAcked(
34 | client().admin().cluster().preparePutRepository(name).setType(OssRepository.TYPE).setVerify(verify)
35 | .setSettings(Settings.builder().put(OssClientSettings.BUCKET.getKey(), BUCKET)
36 | .put(OssClientSettings.BASE_PATH.getKey(), StringUtils.EMPTY)
37 | .put(OssClientSettings.ACCESS_KEY_ID.getKey(), "test_access_key_id")
38 | .put(OssClientSettings.SECRET_ACCESS_KEY.getKey(), "test_secret_access_key")
39 | .put(OssClientSettings.ENDPOINT.getKey(), "test_endpoint")
40 | .put(OssClientSettings.COMPRESS.getKey(), randomBoolean())
41 | .put(OssClientSettings.SUPPORT_CNAME.getKey(), randomBoolean())
42 | .put(OssClientSettings.CHUNK_SIZE.getKey(), randomIntBetween(100, 1000),
43 | ByteSizeUnit.MB)));
44 | }
45 |
46 | public static class MockOssRepositoryPlugin extends OssRepositoryPlugin {
47 | @Override
48 | protected OssService createStorageService(RepositoryMetaData metadata) {
49 | return client;
50 | }
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------