SnapshotsInProgress.java 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  1. /*
  2. * Licensed to Elasticsearch under one or more contributor
  3. * license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright
  5. * ownership. Elasticsearch licenses this file to you under
  6. * the Apache License, Version 2.0 (the "License"); you may
  7. * not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. package org.elasticsearch.cluster;
  20. import com.carrotsearch.hppc.ObjectContainer;
  21. import com.carrotsearch.hppc.cursors.ObjectCursor;
  22. import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
  23. import org.elasticsearch.Version;
  24. import org.elasticsearch.cluster.ClusterState.Custom;
  25. import org.elasticsearch.common.Nullable;
  26. import org.elasticsearch.common.Strings;
  27. import org.elasticsearch.common.collect.ImmutableOpenMap;
  28. import org.elasticsearch.common.io.stream.StreamInput;
  29. import org.elasticsearch.common.io.stream.StreamOutput;
  30. import org.elasticsearch.common.unit.TimeValue;
  31. import org.elasticsearch.common.xcontent.ToXContent;
  32. import org.elasticsearch.common.xcontent.XContentBuilder;
  33. import org.elasticsearch.index.shard.ShardId;
  34. import org.elasticsearch.repositories.IndexId;
  35. import org.elasticsearch.repositories.RepositoryOperation;
  36. import org.elasticsearch.snapshots.Snapshot;
  37. import org.elasticsearch.snapshots.SnapshotsService;
  38. import java.io.IOException;
  39. import java.util.ArrayList;
  40. import java.util.Arrays;
  41. import java.util.Collections;
  42. import java.util.HashMap;
  43. import java.util.HashSet;
  44. import java.util.List;
  45. import java.util.Map;
  46. import java.util.Objects;
  47. import java.util.Set;
  48. import java.util.stream.Collectors;
  49. /**
  50. * Meta data about snapshots that are currently executing
  51. */
  52. public class SnapshotsInProgress extends AbstractNamedDiffable<Custom> implements Custom {
  53. private static final Version VERSION_IN_SNAPSHOT_VERSION = Version.V_7_7_0;
  54. public static final String TYPE = "snapshots";
  55. @Override
  56. public boolean equals(Object o) {
  57. if (this == o) return true;
  58. if (o == null || getClass() != o.getClass()) return false;
  59. return entries.equals(((SnapshotsInProgress) o).entries);
  60. }
  61. @Override
  62. public int hashCode() {
  63. return entries.hashCode();
  64. }
  65. @Override
  66. public String toString() {
  67. StringBuilder builder = new StringBuilder("SnapshotsInProgress[");
  68. for (int i = 0; i < entries.size(); i++) {
  69. builder.append(entries.get(i).snapshot().getSnapshotId().getName());
  70. if (i + 1 < entries.size()) {
  71. builder.append(",");
  72. }
  73. }
  74. return builder.append("]").toString();
  75. }
  76. public static class Entry implements ToXContent, RepositoryOperation {
  77. private final State state;
  78. private final Snapshot snapshot;
  79. private final boolean includeGlobalState;
  80. private final boolean partial;
  81. private final ImmutableOpenMap<ShardId, ShardSnapshotStatus> shards;
  82. private final List<IndexId> indices;
  83. private final ImmutableOpenMap<String, List<ShardId>> waitingIndices;
  84. private final long startTime;
  85. private final long repositoryStateId;
  86. // see #useShardGenerations
  87. private final Version version;
  88. @Nullable private final Map<String, Object> userMetadata;
  89. @Nullable private final String failure;
  90. public Entry(Snapshot snapshot, boolean includeGlobalState, boolean partial, State state, List<IndexId> indices,
  91. long startTime, long repositoryStateId, ImmutableOpenMap<ShardId, ShardSnapshotStatus> shards,
  92. String failure, Map<String, Object> userMetadata, Version version) {
  93. this.state = state;
  94. this.snapshot = snapshot;
  95. this.includeGlobalState = includeGlobalState;
  96. this.partial = partial;
  97. this.indices = indices;
  98. this.startTime = startTime;
  99. if (shards == null) {
  100. this.shards = ImmutableOpenMap.of();
  101. this.waitingIndices = ImmutableOpenMap.of();
  102. } else {
  103. this.shards = shards;
  104. this.waitingIndices = findWaitingIndices(shards);
  105. assert assertShardsConsistent(state, indices, shards);
  106. }
  107. this.repositoryStateId = repositoryStateId;
  108. this.failure = failure;
  109. this.userMetadata = userMetadata;
  110. this.version = version;
  111. }
  112. private static boolean assertShardsConsistent(State state, List<IndexId> indices,
  113. ImmutableOpenMap<ShardId, ShardSnapshotStatus> shards) {
  114. if ((state == State.INIT || state == State.ABORTED) && shards.isEmpty()) {
  115. return true;
  116. }
  117. final Set<String> indexNames = indices.stream().map(IndexId::getName).collect(Collectors.toSet());
  118. final Set<String> indexNamesInShards = new HashSet<>();
  119. shards.keysIt().forEachRemaining(s -> indexNamesInShards.add(s.getIndexName()));
  120. assert indexNames.equals(indexNamesInShards)
  121. : "Indices in shards " + indexNamesInShards + " differ from expected indices " + indexNames + " for state [" + state + "]";
  122. return true;
  123. }
  124. public Entry(Snapshot snapshot, boolean includeGlobalState, boolean partial, State state, List<IndexId> indices,
  125. long startTime, long repositoryStateId, ImmutableOpenMap<ShardId, ShardSnapshotStatus> shards,
  126. Map<String, Object> userMetadata, Version version) {
  127. this(snapshot, includeGlobalState, partial, state, indices, startTime, repositoryStateId, shards, null, userMetadata,
  128. version);
  129. }
  130. public Entry(Entry entry, State state, List<IndexId> indices, long repositoryStateId,
  131. ImmutableOpenMap<ShardId, ShardSnapshotStatus> shards, Version version, String failure) {
  132. this(entry.snapshot, entry.includeGlobalState, entry.partial, state, indices, entry.startTime, repositoryStateId, shards,
  133. failure, entry.userMetadata, version);
  134. }
  135. public Entry(Entry entry, State state, ImmutableOpenMap<ShardId, ShardSnapshotStatus> shards) {
  136. this(entry.snapshot, entry.includeGlobalState, entry.partial, state, entry.indices, entry.startTime,
  137. entry.repositoryStateId, shards, entry.failure, entry.userMetadata, entry.version);
  138. }
  139. public Entry(Entry entry, State state, ImmutableOpenMap<ShardId, ShardSnapshotStatus> shards, String failure) {
  140. this(entry.snapshot, entry.includeGlobalState, entry.partial, state, entry.indices, entry.startTime,
  141. entry.repositoryStateId, shards, failure, entry.userMetadata, entry.version);
  142. }
  143. public Entry(Entry entry, ImmutableOpenMap<ShardId, ShardSnapshotStatus> shards) {
  144. this(entry, entry.state, shards, entry.failure);
  145. }
  146. @Override
  147. public String repository() {
  148. return snapshot.getRepository();
  149. }
  150. public Snapshot snapshot() {
  151. return this.snapshot;
  152. }
  153. public ImmutableOpenMap<ShardId, ShardSnapshotStatus> shards() {
  154. return this.shards;
  155. }
  156. public State state() {
  157. return state;
  158. }
  159. public List<IndexId> indices() {
  160. return indices;
  161. }
  162. public ImmutableOpenMap<String, List<ShardId>> waitingIndices() {
  163. return waitingIndices;
  164. }
  165. public boolean includeGlobalState() {
  166. return includeGlobalState;
  167. }
  168. public Map<String, Object> userMetadata() {
  169. return userMetadata;
  170. }
  171. public boolean partial() {
  172. return partial;
  173. }
  174. public long startTime() {
  175. return startTime;
  176. }
  177. @Override
  178. public long repositoryStateId() {
  179. return repositoryStateId;
  180. }
  181. public String failure() {
  182. return failure;
  183. }
  184. /**
  185. * What version of metadata to use for the snapshot in the repository
  186. */
  187. public Version version() {
  188. return version;
  189. }
  190. @Override
  191. public boolean equals(Object o) {
  192. if (this == o) return true;
  193. if (o == null || getClass() != o.getClass()) return false;
  194. Entry entry = (Entry) o;
  195. if (includeGlobalState != entry.includeGlobalState) return false;
  196. if (partial != entry.partial) return false;
  197. if (startTime != entry.startTime) return false;
  198. if (!indices.equals(entry.indices)) return false;
  199. if (!shards.equals(entry.shards)) return false;
  200. if (!snapshot.equals(entry.snapshot)) return false;
  201. if (state != entry.state) return false;
  202. if (repositoryStateId != entry.repositoryStateId) return false;
  203. if (version.equals(entry.version) == false) return false;
  204. return true;
  205. }
  206. @Override
  207. public int hashCode() {
  208. int result = state.hashCode();
  209. result = 31 * result + snapshot.hashCode();
  210. result = 31 * result + (includeGlobalState ? 1 : 0);
  211. result = 31 * result + (partial ? 1 : 0);
  212. result = 31 * result + shards.hashCode();
  213. result = 31 * result + indices.hashCode();
  214. result = 31 * result + Long.hashCode(startTime);
  215. result = 31 * result + Long.hashCode(repositoryStateId);
  216. result = 31 * result + version.hashCode();
  217. return result;
  218. }
  219. @Override
  220. public String toString() {
  221. return Strings.toString(this);
  222. }
  223. @Override
  224. public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
  225. builder.startObject();
  226. builder.field(REPOSITORY, snapshot.getRepository());
  227. builder.field(SNAPSHOT, snapshot.getSnapshotId().getName());
  228. builder.field(UUID, snapshot.getSnapshotId().getUUID());
  229. builder.field(INCLUDE_GLOBAL_STATE, includeGlobalState());
  230. builder.field(PARTIAL, partial);
  231. builder.field(STATE, state);
  232. builder.startArray(INDICES);
  233. {
  234. for (IndexId index : indices) {
  235. index.toXContent(builder, params);
  236. }
  237. }
  238. builder.endArray();
  239. builder.humanReadableField(START_TIME_MILLIS, START_TIME, new TimeValue(startTime));
  240. builder.field(REPOSITORY_STATE_ID, repositoryStateId);
  241. builder.startArray(SHARDS);
  242. {
  243. for (ObjectObjectCursor<ShardId, ShardSnapshotStatus> shardEntry : shards) {
  244. ShardId shardId = shardEntry.key;
  245. ShardSnapshotStatus status = shardEntry.value;
  246. builder.startObject();
  247. {
  248. builder.field(INDEX, shardId.getIndex());
  249. builder.field(SHARD, shardId.getId());
  250. builder.field(STATE, status.state());
  251. builder.field(NODE, status.nodeId());
  252. }
  253. builder.endObject();
  254. }
  255. }
  256. builder.endArray();
  257. builder.endObject();
  258. return builder;
  259. }
  260. @Override
  261. public boolean isFragment() {
  262. return false;
  263. }
  264. private ImmutableOpenMap<String, List<ShardId>> findWaitingIndices(ImmutableOpenMap<ShardId, ShardSnapshotStatus> shards) {
  265. Map<String, List<ShardId>> waitingIndicesMap = new HashMap<>();
  266. for (ObjectObjectCursor<ShardId, ShardSnapshotStatus> entry : shards) {
  267. if (entry.value.state() == ShardState.WAITING) {
  268. waitingIndicesMap.computeIfAbsent(entry.key.getIndexName(), k -> new ArrayList<>()).add(entry.key);
  269. }
  270. }
  271. if (waitingIndicesMap.isEmpty()) {
  272. return ImmutableOpenMap.of();
  273. }
  274. ImmutableOpenMap.Builder<String, List<ShardId>> waitingIndicesBuilder = ImmutableOpenMap.builder();
  275. for (Map.Entry<String, List<ShardId>> entry : waitingIndicesMap.entrySet()) {
  276. waitingIndicesBuilder.put(entry.getKey(), Collections.unmodifiableList(entry.getValue()));
  277. }
  278. return waitingIndicesBuilder.build();
  279. }
  280. }
  281. /**
  282. * Checks if all shards in the list have completed
  283. *
  284. * @param shards list of shard statuses
  285. * @return true if all shards have completed (either successfully or failed), false otherwise
  286. */
  287. public static boolean completed(ObjectContainer<ShardSnapshotStatus> shards) {
  288. for (ObjectCursor<ShardSnapshotStatus> status : shards) {
  289. if (status.value.state().completed == false) {
  290. return false;
  291. }
  292. }
  293. return true;
  294. }
  295. public static class ShardSnapshotStatus {
  296. private final ShardState state;
  297. private final String nodeId;
  298. @Nullable
  299. private final String generation;
  300. @Nullable
  301. private final String reason;
  302. public ShardSnapshotStatus(String nodeId, String generation) {
  303. this(nodeId, ShardState.INIT, generation);
  304. }
  305. public ShardSnapshotStatus(String nodeId, ShardState state, String generation) {
  306. this(nodeId, state, null, generation);
  307. }
  308. public ShardSnapshotStatus(String nodeId, ShardState state, String reason, String generation) {
  309. this.nodeId = nodeId;
  310. this.state = state;
  311. this.reason = reason;
  312. this.generation = generation;
  313. // If the state is failed we have to have a reason for this failure
  314. assert state.failed() == false || reason != null;
  315. }
  316. public ShardSnapshotStatus(StreamInput in) throws IOException {
  317. nodeId = in.readOptionalString();
  318. state = ShardState.fromValue(in.readByte());
  319. if (SnapshotsService.useShardGenerations(in.getVersion())) {
  320. generation = in.readOptionalString();
  321. } else {
  322. generation = null;
  323. }
  324. reason = in.readOptionalString();
  325. }
  326. public ShardState state() {
  327. return state;
  328. }
  329. public String nodeId() {
  330. return nodeId;
  331. }
  332. public String generation() {
  333. return this.generation;
  334. }
  335. public String reason() {
  336. return reason;
  337. }
  338. public void writeTo(StreamOutput out) throws IOException {
  339. out.writeOptionalString(nodeId);
  340. out.writeByte(state.value);
  341. if (SnapshotsService.useShardGenerations(out.getVersion())) {
  342. out.writeOptionalString(generation);
  343. }
  344. out.writeOptionalString(reason);
  345. }
  346. @Override
  347. public boolean equals(Object o) {
  348. if (this == o) return true;
  349. if (o == null || getClass() != o.getClass()) return false;
  350. ShardSnapshotStatus status = (ShardSnapshotStatus) o;
  351. return Objects.equals(nodeId, status.nodeId) && Objects.equals(reason, status.reason)
  352. && Objects.equals(generation, status.generation) && state == status.state;
  353. }
  354. @Override
  355. public int hashCode() {
  356. int result = state != null ? state.hashCode() : 0;
  357. result = 31 * result + (nodeId != null ? nodeId.hashCode() : 0);
  358. result = 31 * result + (reason != null ? reason.hashCode() : 0);
  359. result = 31 * result + (generation != null ? generation.hashCode() : 0);
  360. return result;
  361. }
  362. @Override
  363. public String toString() {
  364. return "ShardSnapshotStatus[state=" + state + ", nodeId=" + nodeId + ", reason=" + reason + ", generation=" + generation + "]";
  365. }
  366. }
  367. public enum State {
  368. INIT((byte) 0, false),
  369. STARTED((byte) 1, false),
  370. SUCCESS((byte) 2, true),
  371. FAILED((byte) 3, true),
  372. ABORTED((byte) 4, false);
  373. private final byte value;
  374. private final boolean completed;
  375. State(byte value, boolean completed) {
  376. this.value = value;
  377. this.completed = completed;
  378. }
  379. public byte value() {
  380. return value;
  381. }
  382. public boolean completed() {
  383. return completed;
  384. }
  385. public static State fromValue(byte value) {
  386. switch (value) {
  387. case 0:
  388. return INIT;
  389. case 1:
  390. return STARTED;
  391. case 2:
  392. return SUCCESS;
  393. case 3:
  394. return FAILED;
  395. case 4:
  396. return ABORTED;
  397. default:
  398. throw new IllegalArgumentException("No snapshot state for value [" + value + "]");
  399. }
  400. }
  401. }
  402. private final List<Entry> entries;
  403. public SnapshotsInProgress(List<Entry> entries) {
  404. this.entries = entries;
  405. }
  406. public SnapshotsInProgress(Entry... entries) {
  407. this.entries = Arrays.asList(entries);
  408. }
  409. public List<Entry> entries() {
  410. return this.entries;
  411. }
  412. public Entry snapshot(final Snapshot snapshot) {
  413. for (Entry entry : entries) {
  414. final Snapshot curr = entry.snapshot();
  415. if (curr.equals(snapshot)) {
  416. return entry;
  417. }
  418. }
  419. return null;
  420. }
  421. @Override
  422. public String getWriteableName() {
  423. return TYPE;
  424. }
  425. @Override
  426. public Version getMinimalSupportedVersion() {
  427. return Version.CURRENT.minimumCompatibilityVersion();
  428. }
  429. public static NamedDiff<Custom> readDiffFrom(StreamInput in) throws IOException {
  430. return readDiffFrom(Custom.class, TYPE, in);
  431. }
  432. public SnapshotsInProgress(StreamInput in) throws IOException {
  433. Entry[] entries = new Entry[in.readVInt()];
  434. for (int i = 0; i < entries.length; i++) {
  435. Snapshot snapshot = new Snapshot(in);
  436. boolean includeGlobalState = in.readBoolean();
  437. boolean partial = in.readBoolean();
  438. State state = State.fromValue(in.readByte());
  439. int indices = in.readVInt();
  440. List<IndexId> indexBuilder = new ArrayList<>();
  441. for (int j = 0; j < indices; j++) {
  442. indexBuilder.add(new IndexId(in.readString(), in.readString()));
  443. }
  444. long startTime = in.readLong();
  445. ImmutableOpenMap.Builder<ShardId, ShardSnapshotStatus> builder = ImmutableOpenMap.builder();
  446. int shards = in.readVInt();
  447. for (int j = 0; j < shards; j++) {
  448. ShardId shardId = new ShardId(in);
  449. builder.put(shardId, new ShardSnapshotStatus(in));
  450. }
  451. long repositoryStateId = in.readLong();
  452. final String failure = in.readOptionalString();
  453. final Map<String, Object> userMetadata = in.readMap();
  454. final Version version;
  455. if (in.getVersion().onOrAfter(VERSION_IN_SNAPSHOT_VERSION)) {
  456. version = Version.readVersion(in);
  457. } else {
  458. // If an older master informs us that shard generations are supported we use the minimum shard generation compatible
  459. // version. If shard generations are not supported yet we use a placeholder for a version that does not use shard
  460. // generations.
  461. version = in.readBoolean() ? SnapshotsService.SHARD_GEN_IN_REPO_DATA_VERSION : SnapshotsService.OLD_SNAPSHOT_FORMAT;
  462. }
  463. entries[i] = new Entry(snapshot,
  464. includeGlobalState,
  465. partial,
  466. state,
  467. Collections.unmodifiableList(indexBuilder),
  468. startTime,
  469. repositoryStateId,
  470. builder.build(),
  471. failure,
  472. userMetadata,
  473. version
  474. );
  475. }
  476. this.entries = Arrays.asList(entries);
  477. }
  478. @Override
  479. public void writeTo(StreamOutput out) throws IOException {
  480. out.writeVInt(entries.size());
  481. for (Entry entry : entries) {
  482. entry.snapshot().writeTo(out);
  483. out.writeBoolean(entry.includeGlobalState());
  484. out.writeBoolean(entry.partial());
  485. out.writeByte(entry.state().value());
  486. out.writeVInt(entry.indices().size());
  487. for (IndexId index : entry.indices()) {
  488. index.writeTo(out);
  489. }
  490. out.writeLong(entry.startTime());
  491. out.writeVInt(entry.shards().size());
  492. for (ObjectObjectCursor<ShardId, ShardSnapshotStatus> shardEntry : entry.shards()) {
  493. shardEntry.key.writeTo(out);
  494. shardEntry.value.writeTo(out);
  495. }
  496. out.writeLong(entry.repositoryStateId);
  497. out.writeOptionalString(entry.failure);
  498. out.writeMap(entry.userMetadata);
  499. if (out.getVersion().onOrAfter(VERSION_IN_SNAPSHOT_VERSION)) {
  500. Version.writeVersion(entry.version, out);
  501. } else {
  502. out.writeBoolean(SnapshotsService.useShardGenerations(entry.version));
  503. }
  504. }
  505. }
  506. private static final String REPOSITORY = "repository";
  507. private static final String SNAPSHOTS = "snapshots";
  508. private static final String SNAPSHOT = "snapshot";
  509. private static final String UUID = "uuid";
  510. private static final String INCLUDE_GLOBAL_STATE = "include_global_state";
  511. private static final String PARTIAL = "partial";
  512. private static final String STATE = "state";
  513. private static final String INDICES = "indices";
  514. private static final String START_TIME_MILLIS = "start_time_millis";
  515. private static final String START_TIME = "start_time";
  516. private static final String REPOSITORY_STATE_ID = "repository_state_id";
  517. private static final String SHARDS = "shards";
  518. private static final String INDEX = "index";
  519. private static final String SHARD = "shard";
  520. private static final String NODE = "node";
  521. @Override
  522. public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
  523. builder.startArray(SNAPSHOTS);
  524. for (Entry entry : entries) {
  525. entry.toXContent(builder, params);
  526. }
  527. builder.endArray();
  528. return builder;
  529. }
  530. public enum ShardState {
  531. INIT((byte) 0, false, false),
  532. SUCCESS((byte) 2, true, false),
  533. FAILED((byte) 3, true, true),
  534. ABORTED((byte) 4, false, true),
  535. MISSING((byte) 5, true, true),
  536. WAITING((byte) 6, false, false);
  537. private final byte value;
  538. private final boolean completed;
  539. private final boolean failed;
  540. ShardState(byte value, boolean completed, boolean failed) {
  541. this.value = value;
  542. this.completed = completed;
  543. this.failed = failed;
  544. }
  545. public boolean completed() {
  546. return completed;
  547. }
  548. public boolean failed() {
  549. return failed;
  550. }
  551. public static ShardState fromValue(byte value) {
  552. switch (value) {
  553. case 0:
  554. return INIT;
  555. case 2:
  556. return SUCCESS;
  557. case 3:
  558. return FAILED;
  559. case 4:
  560. return ABORTED;
  561. case 5:
  562. return MISSING;
  563. case 6:
  564. return WAITING;
  565. default:
  566. throw new IllegalArgumentException("No shard snapshot state for value [" + value + "]");
  567. }
  568. }
  569. }
  570. }