blob: d37b4668581ed5109b3c43a194f9b8fe1145dd04 [file] [log] [blame]
Jin Gan79f75372017-01-05 15:08:11 -08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Jin Gan79f75372017-01-05 15:08:11 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
jingan7c5bf1f2017-02-09 02:58:09 -080016
Jin Gan79f75372017-01-05 15:08:11 -080017package org.onosproject.restconf.restconfmanager;
18
19import com.fasterxml.jackson.databind.node.ObjectNode;
20import com.google.common.util.concurrent.ThreadFactoryBuilder;
Jin Gan79f75372017-01-05 15:08:11 -080021import org.glassfish.jersey.server.ChunkedOutput;
Henry Yu14af7782017-03-09 19:33:36 -050022import org.onosproject.config.DynamicConfigService;
23import org.onosproject.config.FailedException;
24import org.onosproject.config.Filter;
Yuta HIGUCHIfa105b22018-03-01 21:16:51 -080025import org.onosproject.d.config.ResourceIds;
Sean Condon13b16812018-01-25 10:31:49 +000026import org.onosproject.restconf.api.RestconfError;
Henry Yu14af7782017-03-09 19:33:36 -050027import org.onosproject.restconf.api.RestconfException;
Henry Yuc10f7fc2017-07-26 13:42:08 -040028import org.onosproject.restconf.api.RestconfRpcOutput;
Henry Yu14af7782017-03-09 19:33:36 -050029import org.onosproject.restconf.api.RestconfService;
Henry Yuc10f7fc2017-07-26 13:42:08 -040030import org.onosproject.restconf.utils.RestconfUtils;
Henry Yu14af7782017-03-09 19:33:36 -050031import org.onosproject.yang.model.DataNode;
Henry Yuc10f7fc2017-07-26 13:42:08 -040032import org.onosproject.yang.model.DefaultResourceData;
sonugupta-huaweif0af7aa2017-03-17 00:54:52 +053033import org.onosproject.yang.model.InnerNode;
34import org.onosproject.yang.model.KeyLeaf;
35import org.onosproject.yang.model.ListKey;
36import org.onosproject.yang.model.NodeKey;
Henry Yu14af7782017-03-09 19:33:36 -050037import org.onosproject.yang.model.ResourceData;
38import org.onosproject.yang.model.ResourceId;
Henry Yuc10f7fc2017-07-26 13:42:08 -040039import org.onosproject.yang.model.RpcInput;
40import org.onosproject.yang.model.RpcOutput;
sonugupta-huaweif0af7aa2017-03-17 00:54:52 +053041import org.onosproject.yang.model.SchemaId;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070042import org.osgi.service.component.annotations.Activate;
43import org.osgi.service.component.annotations.Component;
44import org.osgi.service.component.annotations.Deactivate;
45import org.osgi.service.component.annotations.Reference;
46import org.osgi.service.component.annotations.ReferenceCardinality;
Jin Gan79f75372017-01-05 15:08:11 -080047import org.slf4j.Logger;
48import org.slf4j.LoggerFactory;
49
Sean Condon13b16812018-01-25 10:31:49 +000050import javax.ws.rs.core.Response;
Henry Yuc10f7fc2017-07-26 13:42:08 -040051import java.net.URI;
Sean Condon13b16812018-01-25 10:31:49 +000052import java.util.Arrays;
jingan7c5bf1f2017-02-09 02:58:09 -080053import java.util.List;
Henry Yuc10f7fc2017-07-26 13:42:08 -040054import java.util.Map;
Sean Condon13b16812018-01-25 10:31:49 +000055import java.util.Optional;
Henry Yuc10f7fc2017-07-26 13:42:08 -040056import java.util.concurrent.CompletableFuture;
Jin Gan79f75372017-01-05 15:08:11 -080057import java.util.concurrent.ExecutorService;
58import java.util.concurrent.Executors;
Henry Yu14af7782017-03-09 19:33:36 -050059
Sean Condon13b16812018-01-25 10:31:49 +000060import static javax.ws.rs.core.Response.Status.CONFLICT;
Jin Gan79f75372017-01-05 15:08:11 -080061import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
Henry Yu830b5dc2017-11-16 10:44:45 -050062import static org.onosproject.d.config.ResourceIds.parentOf;
jingan7c5bf1f2017-02-09 02:58:09 -080063import static org.onosproject.restconf.utils.RestconfUtils.convertDataNodeToJson;
Henry Yu14af7782017-03-09 19:33:36 -050064import static org.onosproject.restconf.utils.RestconfUtils.convertJsonToDataNode;
Henry Yuc10f7fc2017-07-26 13:42:08 -040065import static org.onosproject.restconf.utils.RestconfUtils.rmLastPathSegment;
sonugupta-huaweif0af7aa2017-03-17 00:54:52 +053066import static org.onosproject.yang.model.DataNode.Type.MULTI_INSTANCE_NODE;
67import static org.onosproject.yang.model.DataNode.Type.SINGLE_INSTANCE_LEAF_VALUE_NODE;
68import static org.onosproject.yang.model.DataNode.Type.SINGLE_INSTANCE_NODE;
jingan7c5bf1f2017-02-09 02:58:09 -080069
Jin Gan79f75372017-01-05 15:08:11 -080070/*
jingan7c5bf1f2017-02-09 02:58:09 -080071 * ONOS RESTCONF application. The RESTCONF Manager
72 * implements the main logic of the RESTCONF application.
Jin Gan79f75372017-01-05 15:08:11 -080073 *
74 * The design of the RESTCONF subsystem contains 2 major bundles:
jingan7c5bf1f2017-02-09 02:58:09 -080075 * This bundle module is the back-end of the server.
Jin Gan79f75372017-01-05 15:08:11 -080076 * It provides the main logic of the RESTCONF server. It interacts with
jingan7c5bf1f2017-02-09 02:58:09 -080077 * the Dynamic Config Service and yang runtime service to run operations
78 * on the YANG data objects (i.e., resource id, yang data node).
Jin Gan79f75372017-01-05 15:08:11 -080079 */
80
Ray Milkeyd84f89b2018-08-17 14:54:17 -070081@Component(immediate = true, service = RestconfService.class)
Jin Gan79f75372017-01-05 15:08:11 -080082public class RestconfManager implements RestconfService {
83
84 private static final String RESTCONF_ROOT = "/onos/restconf";
Jin Gan79f75372017-01-05 15:08:11 -080085
86 private final int maxNumOfWorkerThreads = 5;
87
88 private final Logger log = LoggerFactory.getLogger(getClass());
89
Ray Milkeyd84f89b2018-08-17 14:54:17 -070090 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Henry Yu14af7782017-03-09 19:33:36 -050091 protected DynamicConfigService dynamicConfigService;
Jin Gan79f75372017-01-05 15:08:11 -080092
Jin Gan79f75372017-01-05 15:08:11 -080093 private ExecutorService workerThreadPool;
94
95 @Activate
96 protected void activate() {
97 workerThreadPool = Executors
98 .newFixedThreadPool(maxNumOfWorkerThreads,
99 new ThreadFactoryBuilder()
100 .setNameFormat("restconf-worker")
101 .build());
102 log.info("Started");
103 }
104
105 @Deactivate
106 protected void deactivate() {
Henry Yuc10f7fc2017-07-26 13:42:08 -0400107 workerThreadPool.shutdownNow();
Jin Gan79f75372017-01-05 15:08:11 -0800108 log.info("Stopped");
109 }
110
111 @Override
Henry Yuc10f7fc2017-07-26 13:42:08 -0400112 public ObjectNode runGetOperationOnDataResource(URI uri)
Jin Gan79f75372017-01-05 15:08:11 -0800113 throws RestconfException {
Henry Yu830b5dc2017-11-16 10:44:45 -0500114 DataResourceLocator rl = DataResourceLocator.newInstance(uri);
jingan7c5bf1f2017-02-09 02:58:09 -0800115 // TODO: define Filter (if there is any requirement).
Yuta HIGUCHIac85ee12017-08-03 20:07:35 -0700116 Filter filter = Filter.builder().build();
jingan7c5bf1f2017-02-09 02:58:09 -0800117 DataNode dataNode;
Henry Yuc10f7fc2017-07-26 13:42:08 -0400118
jingan7c5bf1f2017-02-09 02:58:09 -0800119 try {
Henry Yu830b5dc2017-11-16 10:44:45 -0500120 if (!dynamicConfigService.nodeExist(rl.ridForDynConfig())) {
Henry Yuc10f7fc2017-07-26 13:42:08 -0400121 return null;
122 }
Henry Yu830b5dc2017-11-16 10:44:45 -0500123 dataNode = dynamicConfigService.readNode(rl.ridForDynConfig(), filter);
jingan7c5bf1f2017-02-09 02:58:09 -0800124 } catch (FailedException e) {
125 log.error("ERROR: DynamicConfigService: ", e);
Sean Condon13b16812018-01-25 10:31:49 +0000126 throw new RestconfException("ERROR: DynamicConfigService", e,
127 RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
128 Optional.of(uri.getPath()));
jingan7c5bf1f2017-02-09 02:58:09 -0800129 }
Henry Yu830b5dc2017-11-16 10:44:45 -0500130 ObjectNode rootNode = convertDataNodeToJson(rl.ridForYangRuntime(), dataNode);
jingan7c5bf1f2017-02-09 02:58:09 -0800131 return rootNode;
Jin Gan79f75372017-01-05 15:08:11 -0800132 }
133
134 @Override
Henry Yuc10f7fc2017-07-26 13:42:08 -0400135 public void runPostOperationOnDataResource(URI uri, ObjectNode rootNode)
Jin Gan79f75372017-01-05 15:08:11 -0800136 throws RestconfException {
Henry Yu830b5dc2017-11-16 10:44:45 -0500137 DataResourceLocator rl = DataResourceLocator.newInstance(uri);
138 ResourceData receivedData = convertJsonToDataNode(rl.uriForYangRuntime(), rootNode);
Henry Yuc10f7fc2017-07-26 13:42:08 -0400139 ResourceId rid = receivedData.resourceId();
140 List<DataNode> dataNodeList = receivedData.dataNodes();
jingan7c5bf1f2017-02-09 02:58:09 -0800141 if (dataNodeList.size() > 1) {
Henry Yuc10f7fc2017-07-26 13:42:08 -0400142 log.warn("There are more than one Data Node can be proceed: {}", dataNodeList.size());
jingan7c5bf1f2017-02-09 02:58:09 -0800143 }
144 DataNode dataNode = dataNodeList.get(0);
Henry Yuc10f7fc2017-07-26 13:42:08 -0400145
146 if (rid == null) {
147 rid = ResourceId.builder().addBranchPointSchema("/", null).build();
148 dataNode = removeTopNode(dataNode);
149 }
150
jingan7c5bf1f2017-02-09 02:58:09 -0800151 try {
Henry Yu830b5dc2017-11-16 10:44:45 -0500152 dynamicConfigService.createNode(rl.ridForDynConfig(), dataNode);
Yuta HIGUCHIfa105b22018-03-01 21:16:51 -0800153 } catch (Exception e) {
Sean Condon13b16812018-01-25 10:31:49 +0000154 if (e.getMessage().startsWith("Requested node already present")) {
155 throw new RestconfException("Already exists", e,
156 RestconfError.ErrorTag.DATA_EXISTS, CONFLICT,
157 Optional.of(uri.getPath()));
158 } else {
Yuta HIGUCHIfa105b22018-03-01 21:16:51 -0800159 log.error("ERROR: DynamicConfigService: creating {} with {}",
160 ResourceIds.toInstanceIdentifier(rl.ridForDynConfig()),
161 dataNode,
162 e);
Sean Condon13b16812018-01-25 10:31:49 +0000163 throw new RestconfException("ERROR: DynamicConfigService", e,
164 RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
165 Optional.of(uri.getPath()));
166 }
jingan7c5bf1f2017-02-09 02:58:09 -0800167 }
Jin Gan79f75372017-01-05 15:08:11 -0800168 }
169
170 @Override
Henry Yuc10f7fc2017-07-26 13:42:08 -0400171 public void runPutOperationOnDataResource(URI uri, ObjectNode rootNode)
Jin Gan79f75372017-01-05 15:08:11 -0800172 throws RestconfException {
Henry Yu830b5dc2017-11-16 10:44:45 -0500173 DataResourceLocator rl = DataResourceLocator.newInstance(uri);
174 ResourceData receivedData = convertJsonToDataNode(rmLastPathSegment(rl.uriForYangRuntime()), rootNode);
Henry Yuc10f7fc2017-07-26 13:42:08 -0400175 List<DataNode> dataNodeList = receivedData.dataNodes();
176 if (dataNodeList.size() > 1) {
177 log.warn("There are more than one Data Node can be proceed: {}", dataNodeList.size());
178 }
179 DataNode dataNode = dataNodeList.get(0);
180
jingan7c5bf1f2017-02-09 02:58:09 -0800181 try {
Henry Yuc10f7fc2017-07-26 13:42:08 -0400182 /*
183 * If the data node already exists, then replace it.
184 * Otherwise, create it.
185 */
Henry Yu830b5dc2017-11-16 10:44:45 -0500186 if (dynamicConfigService.nodeExist(rl.ridForDynConfig())) {
187 dynamicConfigService.replaceNode(parentOf(rl.ridForDynConfig()), dataNode);
Henry Yuc10f7fc2017-07-26 13:42:08 -0400188 } else {
Henry Yu830b5dc2017-11-16 10:44:45 -0500189 dynamicConfigService.createNode(parentOf(rl.ridForDynConfig()), dataNode);
Henry Yuc10f7fc2017-07-26 13:42:08 -0400190 }
191
jingan7c5bf1f2017-02-09 02:58:09 -0800192 } catch (FailedException e) {
193 log.error("ERROR: DynamicConfigService: ", e);
Sean Condon13b16812018-01-25 10:31:49 +0000194 throw new RestconfException("ERROR: DynamicConfigService", e,
195 RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
196 Optional.of(uri.getPath()));
jingan7c5bf1f2017-02-09 02:58:09 -0800197 }
Jin Gan79f75372017-01-05 15:08:11 -0800198 }
199
200 @Override
Henry Yuc10f7fc2017-07-26 13:42:08 -0400201 public void runDeleteOperationOnDataResource(URI uri)
Jin Gan79f75372017-01-05 15:08:11 -0800202 throws RestconfException {
Henry Yu830b5dc2017-11-16 10:44:45 -0500203 DataResourceLocator rl = DataResourceLocator.newInstance(uri);
Henry Yuc10f7fc2017-07-26 13:42:08 -0400204 try {
Henry Yu830b5dc2017-11-16 10:44:45 -0500205 if (dynamicConfigService.nodeExist(rl.ridForDynConfig())) {
206 dynamicConfigService.deleteNode(rl.ridForDynConfig());
Henry Yuc10f7fc2017-07-26 13:42:08 -0400207 }
208 } catch (FailedException e) {
209 log.error("ERROR: DynamicConfigService: ", e);
Sean Condon13b16812018-01-25 10:31:49 +0000210 throw new RestconfException("ERROR: DynamicConfigService", e,
211 RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
212 Optional.of(uri.getPath()));
Henry Yuc10f7fc2017-07-26 13:42:08 -0400213 }
214 }
215
216 @Override
217 public void runPatchOperationOnDataResource(URI uri, ObjectNode rootNode)
218 throws RestconfException {
Henry Yu830b5dc2017-11-16 10:44:45 -0500219 DataResourceLocator rl = DataResourceLocator.newInstance(uri);
220 ResourceData receivedData = convertJsonToDataNode(rmLastPathSegment(rl.uriForYangRuntime()), rootNode);
Henry Yuc10f7fc2017-07-26 13:42:08 -0400221 ResourceId rid = receivedData.resourceId();
222 List<DataNode> dataNodeList = receivedData.dataNodes();
223 if (dataNodeList.size() > 1) {
224 log.warn("There are more than one Data Node can be proceed: {}", dataNodeList.size());
225 }
226 DataNode dataNode = dataNodeList.get(0);
227
228 if (rid == null) {
229 rid = ResourceId.builder().addBranchPointSchema("/", null).build();
230 dataNode = removeTopNode(dataNode);
231 }
232
233 try {
Henry Yu830b5dc2017-11-16 10:44:45 -0500234 dynamicConfigService.updateNode(parentOf(rl.ridForDynConfig()), dataNode);
Henry Yuc10f7fc2017-07-26 13:42:08 -0400235 } catch (FailedException e) {
236 log.error("ERROR: DynamicConfigService: ", e);
Sean Condon13b16812018-01-25 10:31:49 +0000237 throw new RestconfException("ERROR: DynamicConfigService", e,
238 RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
239 Optional.of(uri.getPath()));
Henry Yuc10f7fc2017-07-26 13:42:08 -0400240 }
241 }
242
243 private DataNode removeTopNode(DataNode dataNode) {
244 if (dataNode instanceof InnerNode && dataNode.key().schemaId().name().equals("/")) {
245 Map.Entry<NodeKey, DataNode> entry = ((InnerNode) dataNode).childNodes().entrySet().iterator().next();
246 dataNode = entry.getValue();
247 }
248 return dataNode;
Jin Gan79f75372017-01-05 15:08:11 -0800249 }
250
251 @Override
252 public String getRestconfRootPath() {
253 return RESTCONF_ROOT;
254 }
255
Jin Gan79f75372017-01-05 15:08:11 -0800256 @Override
Henry Yuc10f7fc2017-07-26 13:42:08 -0400257 public void subscribeEventStream(String streamId, String clientIpAddr,
Jin Gan79f75372017-01-05 15:08:11 -0800258 ChunkedOutput<String> output)
259 throws RestconfException {
Henry Yuc10f7fc2017-07-26 13:42:08 -0400260 //TODO: to be completed
Sean Condon13b16812018-01-25 10:31:49 +0000261 throw new RestconfException("Not implemented",
262 RestconfError.ErrorTag.OPERATION_NOT_SUPPORTED,
263 Response.Status.NOT_IMPLEMENTED,
264 Optional.empty(), Optional.of("subscribeEventStream not yet implemented"));
Henry Yuc10f7fc2017-07-26 13:42:08 -0400265 }
266
267 @Override
268 public CompletableFuture<RestconfRpcOutput> runRpc(URI uri,
269 ObjectNode input,
270 String clientIpAddress) {
271 CompletableFuture<RestconfRpcOutput> result =
272 CompletableFuture.supplyAsync(() -> executeRpc(uri, input, clientIpAddress));
273 return result;
274 }
275
276 private RestconfRpcOutput executeRpc(URI uri, ObjectNode input, String clientIpAddress) {
277 ResourceData rpcInputNode = convertJsonToDataNode(uri, input);
278 ResourceId resourceId = rpcInputNode.resourceId();
279 List<DataNode> inputDataNodeList = rpcInputNode.dataNodes();
280 DataNode inputDataNode = inputDataNodeList.get(0);
Gaurav Agrawal142ceb02018-02-16 12:19:08 +0530281 RpcInput rpcInput = new RpcInput(resourceId, inputDataNode);
Henry Yuc10f7fc2017-07-26 13:42:08 -0400282
283 RestconfRpcOutput restconfOutput = null;
284 try {
285 CompletableFuture<RpcOutput> rpcFuture =
Gaurav Agrawal142ceb02018-02-16 12:19:08 +0530286 dynamicConfigService.invokeRpc(rpcInput);
Henry Yuc10f7fc2017-07-26 13:42:08 -0400287 RpcOutput rpcOutput = rpcFuture.get();
288 restconfOutput = RestconfUtils.convertRpcOutput(resourceId, rpcOutput);
289 } catch (InterruptedException e) {
290 log.error("ERROR: computeResultQ.take() has been interrupted.");
291 log.debug("executeRpc Exception:", e);
Sean Condon13b16812018-01-25 10:31:49 +0000292 RestconfError error =
293 RestconfError.builder(RestconfError.ErrorType.RPC,
294 RestconfError.ErrorTag.OPERATION_FAILED)
295 .errorMessage("RPC execution has been interrupted")
296 .errorPath(uri.getPath())
297 .build();
298 restconfOutput = new RestconfRpcOutput(INTERNAL_SERVER_ERROR,
299 RestconfError.wrapErrorAsJson(Arrays.asList(error)));
Henry Yuc10f7fc2017-07-26 13:42:08 -0400300 restconfOutput.reason("RPC execution has been interrupted");
301 } catch (Exception e) {
302 log.error("ERROR: executeRpc: {}", e.getMessage());
303 log.debug("executeRpc Exception:", e);
Sean Condon13b16812018-01-25 10:31:49 +0000304 RestconfError error =
305 RestconfError.builder(RestconfError.ErrorType.RPC,
306 RestconfError.ErrorTag.OPERATION_FAILED)
307 .errorMessage(e.getMessage())
308 .errorPath(uri.getPath())
309 .build();
310 restconfOutput = new RestconfRpcOutput(INTERNAL_SERVER_ERROR,
311 RestconfError.wrapErrorAsJson(Arrays.asList(error)));
Henry Yuc10f7fc2017-07-26 13:42:08 -0400312 restconfOutput.reason(e.getMessage());
Jin Gan79f75372017-01-05 15:08:11 -0800313 }
314
Henry Yuc10f7fc2017-07-26 13:42:08 -0400315 return restconfOutput;
Jin Gan79f75372017-01-05 15:08:11 -0800316 }
317
sonugupta-huaweif0af7aa2017-03-17 00:54:52 +0530318 private ResourceData getDataForStore(ResourceData resourceData) {
319 List<DataNode> nodes = resourceData.dataNodes();
320 ResourceId rid = resourceData.resourceId();
321 DataNode.Builder dbr = null;
322 ResourceId parentId = null;
323 try {
324 NodeKey lastKey = rid.nodeKeys().get(rid.nodeKeys().size() - 1);
325 SchemaId sid = lastKey.schemaId();
326 if (lastKey instanceof ListKey) {
327 dbr = InnerNode.builder(
328 sid.name(), sid.namespace()).type(MULTI_INSTANCE_NODE);
329 for (KeyLeaf keyLeaf : ((ListKey) lastKey).keyLeafs()) {
330 Object val = keyLeaf.leafValue();
331 dbr = dbr.addKeyLeaf(keyLeaf.leafSchema().name(),
332 sid.namespace(), val);
333 dbr = dbr.createChildBuilder(keyLeaf.leafSchema().name(),
334 sid.namespace(), val)
335 .type(SINGLE_INSTANCE_LEAF_VALUE_NODE);
sonugupta-huawei6119ac72017-03-21 16:25:40 +0530336 //Exit for key leaf node
337 dbr = dbr.exitNode();
sonugupta-huaweif0af7aa2017-03-17 00:54:52 +0530338 }
339 } else {
340 dbr = InnerNode.builder(
341 sid.name(), sid.namespace()).type(SINGLE_INSTANCE_NODE);
342 }
343 if (nodes != null && !nodes.isEmpty()) {
344 // adding the parent node for given list of nodes
345 for (DataNode node : nodes) {
346 dbr = ((InnerNode.Builder) dbr).addNode(node);
347 }
348 }
349 parentId = rid.copyBuilder().removeLastKey().build();
350 } catch (CloneNotSupportedException e) {
Ray Milkey74e59132018-01-17 15:24:52 -0800351 log.error("getDataForStore()", e);
352 return null;
sonugupta-huaweif0af7aa2017-03-17 00:54:52 +0530353 }
354 ResourceData.Builder resData = DefaultResourceData.builder();
355 resData.addDataNode(dbr.build());
356 resData.resourceId(parentId);
357 return resData.build();
358 }
Jin Gan79f75372017-01-05 15:08:11 -0800359}