You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datalab.apache.org by of...@apache.org on 2020/11/24 17:50:52 UTC

[incubator-datalab] branch develop updated (5cea7a1 -> 3db6fb1)

This is an automated email from the ASF dual-hosted git repository.

ofuks pushed a change to branch develop
in repository https://gitbox.apache.org/repos/asf/incubator-datalab.git.


    from 5cea7a1  [DATALAB-2152] -- added cluster instances to scheduler status sync (#989)
     new 43b7e18  merge odahu (provisioning)
     new 9499a58  merge odahu (provisioning)
     new 05943f4  merge odahu (self-service)
     new 97ce7b9  Merge remote-tracking branch 'origin/develop' into develop
     new 3db6fb1  merge odahu (self-service)

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../ProjectResult.java => odahu/OdahuResult.java}  |  21 +-
 .../ActionOdahuDTO.java}                           |  21 +-
 .../CreateOdahuDTO.java}                           |  13 +-
 .../java/com/epam/datalab/model/ResourceEnum.java  |   3 +-
 .../backendapi/ProvisioningServiceApplication.java |   2 +
 .../core/commands/CommandExecutorMockAsync.java    |   2 +-
 .../response/handlers/OdahuCallbackHandler.java    |  94 ++++++++
 .../backendapi/modules/ProductionModule.java       |   3 +
 .../backendapi/modules/ProvisioningDevModule.java  |   3 +
 .../backendapi/resources/OdahuResource.java}       |  47 ++--
 .../datalab/backendapi/service/OdahuService.java}  |  15 +-
 .../backendapi/service/impl/OdahuServiceImpl.java  | 115 ++++++++++
 .../datalab/backendapi/SelfServiceApplication.java |  24 +-
 .../dao/{BackupDAO.java => OdahuDAO.java}          |  23 +-
 .../epam/datalab/backendapi/dao/OdahuDAOImpl.java  | 167 ++++++++++++++
 ...teProjectBudgetDTO.java => OdahuActionDTO.java} |  13 +-
 ...teProjectBudgetDTO.java => OdahuCreateDTO.java} |  16 +-
 .../{UpdateProjectDTO.java => OdahuDTO.java}       |  26 ++-
 .../{AuditCreateDTO.java => OdahuFieldsDTO.java}   |  19 +-
 .../epam/datalab/backendapi/domain/ProjectDTO.java |   3 +-
 .../epam/datalab/backendapi/modules/DevModule.java |  32 +--
 .../backendapi/modules/ProductionModule.java       |  40 ++--
 .../backendapi/resources/OdahuResource.java        |  97 ++++++++
 .../{BackupCallback.java => OdahuCallback.java}    |  54 ++---
 .../resources/dto/ProjectInfrastructureInfo.java   |  27 ++-
 .../{BillingService.java => OdahuService.java}     |  27 ++-
 .../service/impl/EndpointServiceImpl.java          |  21 +-
 .../service/impl/EnvironmentServiceImpl.java       | 100 +++++----
 .../impl/InfrastructureInfoServiceImpl.java        |  13 +-
 .../backendapi/service/impl/OdahuServiceImpl.java  | 203 +++++++++++++++++
 .../service/impl/ProjectServiceImpl.java           |  19 +-
 .../datalab/backendapi/util/RequestBuilder.java    |  31 +++
 .../src/main/resources/mongo/aws/mongo_roles.json  |   3 +-
 .../main/resources/mongo/azure/mongo_roles.json    |   3 +-
 .../src/main/resources/mongo/gcp/mongo_roles.json  |   3 +-
 .../backendapi/service/ProjectServiceImplTest.java | 202 ++++++++++-------
 .../service/impl/EndpointServiceImplTest.java      |  93 ++++----
 .../impl/InfrastructureInfoServiceImplTest.java    |  52 +++--
 .../service/impl/OdahuServiceImplTest.java         | 250 +++++++++++++++++++++
 39 files changed, 1505 insertions(+), 395 deletions(-)
 copy services/datalab-model/src/main/java/com/epam/datalab/dto/base/{project/ProjectResult.java => odahu/OdahuResult.java} (67%)
 copy services/datalab-model/src/main/java/com/epam/datalab/dto/{project/ProjectCreateDTO.java => odahu/ActionOdahuDTO.java} (68%)
 copy services/datalab-model/src/main/java/com/epam/datalab/dto/{project/ProjectCreateDTO.java => odahu/CreateOdahuDTO.java} (84%)
 create mode 100644 services/provisioning-service/src/main/java/com/epam/datalab/backendapi/core/response/handlers/OdahuCallbackHandler.java
 copy services/{self-service/src/main/java/com/epam/datalab/backendapi/resources/UserRoleResource.java => provisioning-service/src/main/java/com/epam/datalab/backendapi/resources/OdahuResource.java} (54%)
 copy services/{self-service/src/main/java/com/epam/datalab/backendapi/service/UserSettingService.java => provisioning-service/src/main/java/com/epam/datalab/backendapi/service/OdahuService.java} (70%)
 create mode 100644 services/provisioning-service/src/main/java/com/epam/datalab/backendapi/service/impl/OdahuServiceImpl.java
 copy services/self-service/src/main/java/com/epam/datalab/backendapi/dao/{BackupDAO.java => OdahuDAO.java} (55%)
 create mode 100644 services/self-service/src/main/java/com/epam/datalab/backendapi/dao/OdahuDAOImpl.java
 copy services/self-service/src/main/java/com/epam/datalab/backendapi/domain/{UpdateProjectBudgetDTO.java => OdahuActionDTO.java} (85%)
 copy services/self-service/src/main/java/com/epam/datalab/backendapi/domain/{UpdateProjectBudgetDTO.java => OdahuCreateDTO.java} (79%)
 copy services/self-service/src/main/java/com/epam/datalab/backendapi/domain/{UpdateProjectDTO.java => OdahuDTO.java} (66%)
 copy services/self-service/src/main/java/com/epam/datalab/backendapi/domain/{AuditCreateDTO.java => OdahuFieldsDTO.java} (75%)
 create mode 100644 services/self-service/src/main/java/com/epam/datalab/backendapi/resources/OdahuResource.java
 copy services/self-service/src/main/java/com/epam/datalab/backendapi/resources/callback/{BackupCallback.java => OdahuCallback.java} (55%)
 copy services/self-service/src/main/java/com/epam/datalab/backendapi/service/{BillingService.java => OdahuService.java} (54%)
 create mode 100644 services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/OdahuServiceImpl.java
 create mode 100644 services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/OdahuServiceImplTest.java


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datalab.apache.org
For additional commands, e-mail: commits-help@datalab.apache.org


[incubator-datalab] 04/05: Merge remote-tracking branch 'origin/develop' into develop

Posted by of...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ofuks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/incubator-datalab.git

commit 97ce7b9d48b43196034ac7bd4d10792978c81436
Merge: 05943f4 5cea7a1
Author: Oleh Fuks <ol...@gmail.com>
AuthorDate: Tue Nov 24 19:48:02 2020 +0200

    Merge remote-tracking branch 'origin/develop' into develop

 .../src/general/files/aws/base_Dockerfile          |   2 +-
 .../src/general/files/azure/base_Dockerfile        |   2 +-
 .../src/general/files/gcp/base_Dockerfile          |   2 +-
 .../com/epam/datalab/dto/base/DataEngineType.java  |   5 +-
 .../handlers/ResourcesStatusCallbackHandler.java   |   2 +-
 .../CheckInfrastructureStatusScheduler.java        | 207 ++++++++++++---------
 .../management-grid/management-grid.component.html |   6 +-
 .../management-grid/management-grid.component.ts   |  57 ++++--
 .../resources-grid/resources-grid.component.ts     |   9 +-
 .../confirmation-dialog.component.html             |   2 +-
 .../confirmation-dialog.component.ts               |  14 +-
 11 files changed, 171 insertions(+), 137 deletions(-)


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datalab.apache.org
For additional commands, e-mail: commits-help@datalab.apache.org


[incubator-datalab] 03/05: merge odahu (self-service)

Posted by of...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ofuks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/incubator-datalab.git

commit 05943f4cf5fa8cbf789571acc52835a4adb44432
Author: Oleh Fuks <ol...@gmail.com>
AuthorDate: Sun Nov 22 23:24:28 2020 +0200

    merge odahu (self-service)
---
 .../datalab/backendapi/SelfServiceApplication.java |  24 +-
 .../com/epam/datalab/backendapi/dao/OdahuDAO.java  |  44 ++++
 .../epam/datalab/backendapi/dao/OdahuDAOImpl.java  | 167 ++++++++++++++
 .../datalab/backendapi/domain/OdahuActionDTO.java  |  36 +++
 .../OdahuCreateDTO.java}                           |  42 ++--
 .../OdahuDTO.java}                                 |  45 ++--
 .../OdahuFieldsDTO.java}                           |  43 ++--
 .../epam/datalab/backendapi/domain/ProjectDTO.java |   3 +-
 .../epam/datalab/backendapi/modules/DevModule.java |  32 +--
 .../backendapi/modules/ProductionModule.java       |  40 ++--
 .../backendapi/resources/OdahuResource.java        |  97 ++++++++
 .../resources/callback/OdahuCallback.java          |  59 +++++
 .../resources/dto/ProjectInfrastructureInfo.java   |  27 ++-
 .../datalab/backendapi/service/OdahuService.java   |  47 ++++
 .../service/impl/EndpointServiceImpl.java          |  21 +-
 .../service/impl/EnvironmentServiceImpl.java       | 100 +++++----
 .../impl/InfrastructureInfoServiceImpl.java        |  13 +-
 .../backendapi/service/impl/OdahuServiceImpl.java  | 196 ++++++++++++++++
 .../service/impl/ProjectServiceImpl.java           |  19 +-
 .../datalab/backendapi/util/RequestBuilder.java    |  31 +++
 .../backendapi/service/ProjectServiceImplTest.java | 202 ++++++++++-------
 .../service/impl/EndpointServiceImplTest.java      |  93 ++++----
 .../impl/InfrastructureInfoServiceImplTest.java    |  52 +++--
 .../service/impl/OdahuServiceImplTest.java         | 250 +++++++++++++++++++++
 24 files changed, 1335 insertions(+), 348 deletions(-)

diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/SelfServiceApplication.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/SelfServiceApplication.java
index 0b8a414..65059f8 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/SelfServiceApplication.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/SelfServiceApplication.java
@@ -39,6 +39,7 @@ import com.epam.datalab.backendapi.resources.InfrastructureInfoResource;
 import com.epam.datalab.backendapi.resources.InfrastructureTemplateResource;
 import com.epam.datalab.backendapi.resources.KeycloakResource;
 import com.epam.datalab.backendapi.resources.LibExploratoryResource;
+import com.epam.datalab.backendapi.resources.OdahuResource;
 import com.epam.datalab.backendapi.resources.ProjectResource;
 import com.epam.datalab.backendapi.resources.SchedulerJobResource;
 import com.epam.datalab.backendapi.resources.SystemInfoResource;
@@ -53,6 +54,7 @@ import com.epam.datalab.backendapi.resources.callback.ExploratoryCallback;
 import com.epam.datalab.backendapi.resources.callback.GitCredsCallback;
 import com.epam.datalab.backendapi.resources.callback.ImageCallback;
 import com.epam.datalab.backendapi.resources.callback.LibraryCallback;
+import com.epam.datalab.backendapi.resources.callback.OdahuCallback;
 import com.epam.datalab.backendapi.resources.callback.ProjectCallback;
 import com.epam.datalab.backendapi.resources.callback.ReuploadKeyCallback;
 import com.epam.datalab.backendapi.schedulers.internal.ManagedScheduler;
@@ -184,16 +186,18 @@ public class SelfServiceApplication extends Application<SelfServiceApplicationCo
         jersey.register(injector.getInstance(BackupCallback.class));
         jersey.register(injector.getInstance(EnvironmentResource.class));
         jersey.register(injector.getInstance(ReuploadKeyCallback.class));
-        jersey.register(injector.getInstance(CheckInactivityCallback.class));
-        jersey.register(injector.getInstance(SystemInfoResource.class));
-        jersey.register(injector.getInstance(UserGroupResource.class));
-        jersey.register(injector.getInstance(UserRoleResource.class));
-        jersey.register(injector.getInstance(ApplicationSettingResource.class));
-        jersey.register(injector.getInstance(KeycloakResource.class));
-        jersey.register(injector.getInstance(EndpointResource.class));
-        jersey.register(injector.getInstance(ProjectResource.class));
-        jersey.register(injector.getInstance(AuditResource.class));
-        jersey.register(injector.getInstance(ProjectCallback.class));
+	    jersey.register(injector.getInstance(CheckInactivityCallback.class));
+	    jersey.register(injector.getInstance(SystemInfoResource.class));
+	    jersey.register(injector.getInstance(UserGroupResource.class));
+	    jersey.register(injector.getInstance(UserRoleResource.class));
+	    jersey.register(injector.getInstance(ApplicationSettingResource.class));
+	    jersey.register(injector.getInstance(KeycloakResource.class));
+	    jersey.register(injector.getInstance(EndpointResource.class));
+	    jersey.register(injector.getInstance(ProjectResource.class));
+	    jersey.register(injector.getInstance(AuditResource.class));
+	    jersey.register(injector.getInstance(ProjectCallback.class));
+	    jersey.register(injector.getInstance(OdahuResource.class));
+	    jersey.register(injector.getInstance(OdahuCallback.class));
     }
 
     private void disableGzipHandlerForGuacamoleServlet(Server server) {
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/OdahuDAO.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/OdahuDAO.java
new file mode 100644
index 0000000..29106ce
--- /dev/null
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/OdahuDAO.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.epam.datalab.backendapi.dao;
+
+import com.epam.datalab.backendapi.domain.OdahuDTO;
+import com.epam.datalab.backendapi.domain.OdahuFieldsDTO;
+import com.epam.datalab.dto.UserInstanceStatus;
+import com.epam.datalab.dto.base.odahu.OdahuResult;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface OdahuDAO {
+    Optional<OdahuDTO> getByProjectEndpoint(String project, String endpoint);
+
+    OdahuFieldsDTO getFields(String name, String project, String endpoint);
+
+    List<OdahuDTO> findOdahuClusters();
+
+    List<OdahuDTO> findOdahuClusters(String project, String endpoint);
+
+    boolean create(OdahuDTO odahuDTO);
+
+    void updateStatus(String name, String project, String endpoint, UserInstanceStatus status);
+
+    void updateStatusAndUrls(OdahuResult result, UserInstanceStatus status);
+}
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/OdahuDAOImpl.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/OdahuDAOImpl.java
new file mode 100644
index 0000000..3f57184
--- /dev/null
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/OdahuDAOImpl.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.epam.datalab.backendapi.dao;
+
+import com.epam.datalab.backendapi.domain.OdahuDTO;
+import com.epam.datalab.backendapi.domain.OdahuFieldsDTO;
+import com.epam.datalab.backendapi.domain.ProjectDTO;
+import com.epam.datalab.dto.ResourceURL;
+import com.epam.datalab.dto.UserInstanceStatus;
+import com.epam.datalab.dto.base.odahu.OdahuResult;
+import com.epam.datalab.exceptions.DatalabException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.mongodb.BasicDBObject;
+import com.mongodb.client.result.UpdateResult;
+import org.bson.Document;
+import org.bson.conversions.Bson;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static com.mongodb.client.model.Filters.and;
+import static com.mongodb.client.model.Filters.eq;
+import static com.mongodb.client.model.Projections.elemMatch;
+import static com.mongodb.client.model.Projections.excludeId;
+import static com.mongodb.client.model.Projections.fields;
+import static com.mongodb.client.model.Projections.include;
+import static com.mongodb.client.model.Updates.push;
+import static java.util.stream.Collectors.toList;
+
+public class OdahuDAOImpl extends BaseDAO implements OdahuDAO {
+
+    private static final String PROJECTS_COLLECTION = "Projects";
+    private static final String ENDPOINTS = "endpoints";
+    private static final String ODAHU_FIELD = "odahu";
+    private static final String NAME_FIELD = "name";
+    private static final String ENDPOINT_FIELD = "endpoint";
+    private static final String PROJECT_FIELD = "project";
+    private static final String STATUS_FIELD = "status";
+    private static final String GRAFANA_ADMIN_FIELD = "grafana_admin";
+    private static final String GRAFANA_PASSWORD_FIELD = "grafana_pass";
+    private static final String OAUTH_COOKIE_SECRET_FIELD = "oauth_cookie_secret";
+    private static final String DECRYPT_TOKEN_FIELD = "odahuflow_connection_decrypt_token";
+    private static final String URLS_FIELD = "urls";
+    private static final String COMPUTATIONAL_URL_DESC = "description";
+    private static final String COMPUTATIONAL_URL_URL = "url";
+
+    @Override
+    public Optional<OdahuDTO> getByProjectEndpoint(String project, String endpoint) {
+        Optional<ProjectDTO> projectDTO = findOne(PROJECTS_COLLECTION, odahuProjectEndpointCondition(project, endpoint),
+                fields(include(ODAHU_FIELD), excludeId()),
+                ProjectDTO.class);
+
+        return projectDTO.flatMap(p -> p.getOdahu().stream()
+                .filter(odahu -> project.equals(odahu.getProject()) && endpoint.equals(odahu.getEndpoint()))
+                .findAny());
+    }
+
+    @Override
+    public List<OdahuDTO> findOdahuClusters(String project, String endpoint) {
+        Optional<ProjectDTO> projectDTO = findOne(PROJECTS_COLLECTION, odahuProjectEndpointCondition(project, endpoint),
+                fields(include(ODAHU_FIELD), excludeId()),
+                ProjectDTO.class);
+
+        return projectDTO.map(p -> p.getOdahu().stream()
+                .filter(odahu -> project.equals(odahu.getProject()) && endpoint.equals(odahu.getEndpoint()))
+                .collect(Collectors.toList()))
+                .orElseThrow(() -> new DatalabException("Unable to find the odahu clusters in the " + project));
+    }
+
+    @Override
+    public OdahuFieldsDTO getFields(String name, String project, String endpoint) {
+        Document odahuDocument = findOne(PROJECTS_COLLECTION, odahuProjectEndpointCondition(name, project, endpoint),
+                fields(include(ODAHU_FIELD), excludeId()))
+                .orElseThrow(() -> new DatalabException(project.toString() + " does not contain odahu " + name.toString() + " cluster"));
+
+        List<OdahuFieldsDTO> list = convertFromDocument(odahuDocument.get(ODAHU_FIELD, ArrayList.class), new TypeReference<List<OdahuFieldsDTO>>() {
+        });
+        return list.stream()
+                .filter(odahuFieldsDTO -> name.equals(odahuFieldsDTO.getName()))
+                .findAny()
+                .orElseThrow(() -> new DatalabException("Unable to find the " + name + " cluster fields"));
+    }
+
+    @Override
+    public List<OdahuDTO> findOdahuClusters() {
+        List<ProjectDTO> projectDTOS = find(PROJECTS_COLLECTION, ProjectDTO.class);
+        return projectDTOS.stream()
+                .map(ProjectDTO::getOdahu)
+                .flatMap(List::stream)
+                .collect(toList());
+    }
+
+    @Override
+    public boolean create(OdahuDTO odahuDTO) {
+        UpdateResult updateResult = updateOne(PROJECTS_COLLECTION, projectEndpointCondition(odahuDTO.getProject(),
+                odahuDTO.getEndpoint()),
+                push(ODAHU_FIELD, convertToBson(odahuDTO)));
+        return updateResult.getModifiedCount() > 0;
+    }
+
+    @Override
+    public void updateStatus(String name, String project, String endpoint, UserInstanceStatus status) {
+        BasicDBObject dbObject = new BasicDBObject();
+        dbObject.put(ODAHU_FIELD + ".$." + STATUS_FIELD, status.name());
+        updateOne(PROJECTS_COLLECTION, and(elemMatch(ODAHU_FIELD, eq(NAME_FIELD, name)),
+                odahuProjectEndpointCondition(project, endpoint)), new Document(SET, dbObject));
+    }
+
+    @Override
+    public void updateStatusAndUrls(OdahuResult result, UserInstanceStatus status) {
+        BasicDBObject dbObject = new BasicDBObject();
+        dbObject.put(ODAHU_FIELD + ".$." + STATUS_FIELD, status.name());
+        dbObject.put(ODAHU_FIELD + ".$." + URLS_FIELD, getResourceUrlData(result.getResourceUrls()));
+        dbObject.put(ODAHU_FIELD + ".$." + GRAFANA_ADMIN_FIELD, result.getGrafanaAdmin());
+        dbObject.put(ODAHU_FIELD + ".$." + GRAFANA_PASSWORD_FIELD, result.getGrafanaPassword());
+        dbObject.put(ODAHU_FIELD + ".$." + OAUTH_COOKIE_SECRET_FIELD, result.getOauthCookieSecret());
+        dbObject.put(ODAHU_FIELD + ".$." + DECRYPT_TOKEN_FIELD, result.getDecryptToken());
+        updateOne(PROJECTS_COLLECTION, odahuProjectEndpointCondition(result.getName(), result.getProjectName(), result.getEndpointName()),
+                new Document(SET, dbObject));
+    }
+
+    private Bson odahuProjectEndpointCondition(String name, String projectName, String endpointName) {
+        return and(elemMatch(ODAHU_FIELD, eq(NAME_FIELD, name)), odahuProjectEndpointCondition(projectName, endpointName));
+    }
+
+    private Bson odahuProjectEndpointCondition(String projectName, String endpointName) {
+        return elemMatch(ODAHU_FIELD, and(eq(ENDPOINT_FIELD, endpointName), eq(PROJECT_FIELD, projectName)));
+    }
+
+    private Bson projectEndpointCondition(String projectName, String endpointName) {
+        return and(eq(NAME_FIELD, projectName), and(elemMatch(ENDPOINTS, eq(NAME_FIELD, endpointName))));
+    }
+
+    private List<Map<String, String>> getResourceUrlData(List<ResourceURL> urls) {
+        return urls.stream()
+                .map(this::toUrlDocument)
+                .collect(toList());
+    }
+
+    private LinkedHashMap<String, String> toUrlDocument(ResourceURL url) {
+        LinkedHashMap<String, String> map = new LinkedHashMap<>();
+        map.put(COMPUTATIONAL_URL_URL, url.getUrl());
+        map.put(COMPUTATIONAL_URL_DESC, url.getDescription());
+        return map;
+    }
+}
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/domain/OdahuActionDTO.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/domain/OdahuActionDTO.java
new file mode 100644
index 0000000..c8b4960
--- /dev/null
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/domain/OdahuActionDTO.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.epam.datalab.backendapi.domain;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class OdahuActionDTO {
+	@NotNull
+	private final String name;
+	@NotNull
+	private final String project;
+	@NotNull
+	private final String endpoint;
+}
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectInfrastructureInfo.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/domain/OdahuCreateDTO.java
similarity index 51%
copy from services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectInfrastructureInfo.java
copy to services/self-service/src/main/java/com/epam/datalab/backendapi/domain/OdahuCreateDTO.java
index 20605d2..4e13404 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectInfrastructureInfo.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/domain/OdahuCreateDTO.java
@@ -17,35 +17,23 @@
  * under the License.
  */
 
-package com.epam.datalab.backendapi.resources.dto;
+package com.epam.datalab.backendapi.domain;
 
-import com.epam.datalab.backendapi.domain.BillingReport;
-import com.epam.datalab.backendapi.domain.EndpointDTO;
-import com.epam.datalab.dto.UserInstanceDTO;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonProperty;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
+import lombok.Data;
 
-import java.util.List;
-import java.util.Map;
+import javax.validation.constraints.NotNull;
 
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode
-@ToString
-public class ProjectInfrastructureInfo {
-    @JsonProperty
-    private String project;
-    @JsonProperty
-    private int billingQuoteUsed;
-    @JsonProperty
-    private Map<String, Map<String, String>> shared;
-    @JsonProperty
-    private List<UserInstanceDTO> exploratory;
-    @JsonProperty
-    private List<BillingReport> exploratoryBilling;
-    @JsonProperty
-    private List<EndpointDTO> endpoints;
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class OdahuCreateDTO {
+	@NotNull
+	private final String name;
+	@NotNull
+	private final String project;
+	@NotNull
+	private final String endpoint;
+	@JsonProperty("custom_tag")
+	private final String customTag;
 }
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectInfrastructureInfo.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/domain/OdahuDTO.java
similarity index 53%
copy from services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectInfrastructureInfo.java
copy to services/self-service/src/main/java/com/epam/datalab/backendapi/domain/OdahuDTO.java
index 20605d2..5d8faa8 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectInfrastructureInfo.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/domain/OdahuDTO.java
@@ -17,35 +17,30 @@
  * under the License.
  */
 
-package com.epam.datalab.backendapi.resources.dto;
+package com.epam.datalab.backendapi.domain;
 
-import com.epam.datalab.backendapi.domain.BillingReport;
-import com.epam.datalab.backendapi.domain.EndpointDTO;
-import com.epam.datalab.dto.UserInstanceDTO;
+import com.epam.datalab.dto.ResourceURL;
+import com.epam.datalab.dto.UserInstanceStatus;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonProperty;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
+import lombok.Data;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode
-@ToString
-public class ProjectInfrastructureInfo {
-    @JsonProperty
-    private String project;
-    @JsonProperty
-    private int billingQuoteUsed;
-    @JsonProperty
-    private Map<String, Map<String, String>> shared;
-    @JsonProperty
-    private List<UserInstanceDTO> exploratory;
-    @JsonProperty
-    private List<BillingReport> exploratoryBilling;
-    @JsonProperty
-    private List<EndpointDTO> endpoints;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class OdahuDTO {
+    private final String name;
+    private final String project;
+    private final String endpoint;
+    @JsonProperty("grafana_admin")
+    private String grafanaAdmin;
+    @JsonProperty("grafana_pass")
+    private String grafanaPassword;
+    private final List<ResourceURL> urls = new ArrayList<>();
+    private final UserInstanceStatus status;
+    private final Map<String, String> tags;
 }
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectInfrastructureInfo.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/domain/OdahuFieldsDTO.java
similarity index 51%
copy from services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectInfrastructureInfo.java
copy to services/self-service/src/main/java/com/epam/datalab/backendapi/domain/OdahuFieldsDTO.java
index 20605d2..b5843b2 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectInfrastructureInfo.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/domain/OdahuFieldsDTO.java
@@ -17,35 +17,22 @@
  * under the License.
  */
 
-package com.epam.datalab.backendapi.resources.dto;
+package com.epam.datalab.backendapi.domain;
 
-import com.epam.datalab.backendapi.domain.BillingReport;
-import com.epam.datalab.backendapi.domain.EndpointDTO;
-import com.epam.datalab.dto.UserInstanceDTO;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonProperty;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
+import lombok.Data;
 
-import java.util.List;
-import java.util.Map;
-
-@AllArgsConstructor
-@Builder
-@EqualsAndHashCode
-@ToString
-public class ProjectInfrastructureInfo {
-    @JsonProperty
-    private String project;
-    @JsonProperty
-    private int billingQuoteUsed;
-    @JsonProperty
-    private Map<String, Map<String, String>> shared;
-    @JsonProperty
-    private List<UserInstanceDTO> exploratory;
-    @JsonProperty
-    private List<BillingReport> exploratoryBilling;
-    @JsonProperty
-    private List<EndpointDTO> endpoints;
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class OdahuFieldsDTO {
+	private String name;
+	@JsonProperty("grafana_admin")
+	private String grafanaAdmin;
+	@JsonProperty("grafana_pass")
+	private String grafanaPassword;
+	@JsonProperty("oauth_cookie_secret")
+	private String oauthCookieSecret;
+	@JsonProperty("odahuflow_connection_decrypt_token")
+	private String decryptToken;
 }
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/domain/ProjectDTO.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/domain/ProjectDTO.java
index 32bd695..ca3e9b8 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/domain/ProjectDTO.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/domain/ProjectDTO.java
@@ -27,6 +27,7 @@ import lombok.Data;
 
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Pattern;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 
@@ -47,7 +48,7 @@ public class ProjectDTO {
     private final BudgetDTO budget;
     private final List<ProjectEndpointDTO> endpoints;
     private final boolean sharedImageEnabled;
-
+    private final List<OdahuDTO> odahu = new ArrayList<>();
 
     public enum Status {
         CREATING,
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/modules/DevModule.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/modules/DevModule.java
index 9897087..047a7f6 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/modules/DevModule.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/modules/DevModule.java
@@ -33,6 +33,8 @@ import com.epam.datalab.backendapi.dao.EndpointDAO;
 import com.epam.datalab.backendapi.dao.EndpointDAOImpl;
 import com.epam.datalab.backendapi.dao.ImageExploratoryDAO;
 import com.epam.datalab.backendapi.dao.ImageExploratoryDAOImpl;
+import com.epam.datalab.backendapi.dao.OdahuDAO;
+import com.epam.datalab.backendapi.dao.OdahuDAOImpl;
 import com.epam.datalab.backendapi.dao.ProjectDAO;
 import com.epam.datalab.backendapi.dao.ProjectDAOImpl;
 import com.epam.datalab.backendapi.dao.UserGroupDAO;
@@ -57,6 +59,7 @@ import com.epam.datalab.backendapi.service.InactivityService;
 import com.epam.datalab.backendapi.service.KeycloakService;
 import com.epam.datalab.backendapi.service.KeycloakServiceImpl;
 import com.epam.datalab.backendapi.service.LibraryService;
+import com.epam.datalab.backendapi.service.OdahuService;
 import com.epam.datalab.backendapi.service.ProjectService;
 import com.epam.datalab.backendapi.service.ReuploadKeyService;
 import com.epam.datalab.backendapi.service.SchedulerJobService;
@@ -84,6 +87,7 @@ import com.epam.datalab.backendapi.service.impl.ImageExploratoryServiceImpl;
 import com.epam.datalab.backendapi.service.impl.InactivityServiceImpl;
 import com.epam.datalab.backendapi.service.impl.LibraryServiceImpl;
 import com.epam.datalab.backendapi.service.impl.MavenCentralLibraryService;
+import com.epam.datalab.backendapi.service.impl.OdahuServiceImpl;
 import com.epam.datalab.backendapi.service.impl.ProjectServiceImpl;
 import com.epam.datalab.backendapi.service.impl.ReuploadKeyServiceImpl;
 import com.epam.datalab.backendapi.service.impl.SchedulerJobServiceImpl;
@@ -166,19 +170,21 @@ public class DevModule extends ModuleBase<SelfServiceApplicationConfiguration> i
         bind(SystemInfoService.class).to(SystemInfoServiceImpl.class);
         bind(UserGroupService.class).to(UserGroupServiceImpl.class);
         bind(UserRoleService.class).to(UserRoleServiceImpl.class);
-        bind(UserRoleDAO.class).to(UserRoleDAOImpl.class);
-        bind(UserGroupDAO.class).to(UserGroupDAOImpl.class);
-        bind(ApplicationSettingService.class).to(ApplicationSettingServiceImpl.class);
-        bind(UserSettingService.class).to(UserSettingServiceImpl.class);
-        bind(GuacamoleService.class).to(GuacamoleServiceImpl.class);
-        bind(EndpointService.class).to(EndpointServiceImpl.class);
-        bind(EndpointDAO.class).to(EndpointDAOImpl.class);
-        bind(ProjectService.class).to(ProjectServiceImpl.class);
-        bind(AuditService.class).to(AuditServiceImpl.class);
-        bind(ProjectDAO.class).to(ProjectDAOImpl.class);
-        bind(BillingDAO.class).to(BaseBillingDAO.class);
-        bind(AuditDAO.class).to(AuditDAOImpl.class);
-        bind(BucketService.class).to(BucketServiceImpl.class);
+	    bind(UserRoleDAO.class).to(UserRoleDAOImpl.class);
+	    bind(UserGroupDAO.class).to(UserGroupDAOImpl.class);
+	    bind(ApplicationSettingService.class).to(ApplicationSettingServiceImpl.class);
+	    bind(UserSettingService.class).to(UserSettingServiceImpl.class);
+	    bind(GuacamoleService.class).to(GuacamoleServiceImpl.class);
+	    bind(EndpointService.class).to(EndpointServiceImpl.class);
+	    bind(EndpointDAO.class).to(EndpointDAOImpl.class);
+	    bind(ProjectService.class).to(ProjectServiceImpl.class);
+	    bind(AuditService.class).to(AuditServiceImpl.class);
+	    bind(ProjectDAO.class).to(ProjectDAOImpl.class);
+	    bind(OdahuDAO.class).to(OdahuDAOImpl.class);
+	    bind(OdahuService.class).to(OdahuServiceImpl.class);
+	    bind(BillingDAO.class).to(BaseBillingDAO.class);
+	    bind(AuditDAO.class).to(AuditDAOImpl.class);
+	    bind(BucketService.class).to(BucketServiceImpl.class);
     }
 
     private void configureCors(Environment environment) {
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/modules/ProductionModule.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/modules/ProductionModule.java
index 0d01c4d..47ba87e 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/modules/ProductionModule.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/modules/ProductionModule.java
@@ -32,6 +32,8 @@ import com.epam.datalab.backendapi.dao.EndpointDAO;
 import com.epam.datalab.backendapi.dao.EndpointDAOImpl;
 import com.epam.datalab.backendapi.dao.ImageExploratoryDAO;
 import com.epam.datalab.backendapi.dao.ImageExploratoryDAOImpl;
+import com.epam.datalab.backendapi.dao.OdahuDAO;
+import com.epam.datalab.backendapi.dao.OdahuDAOImpl;
 import com.epam.datalab.backendapi.dao.ProjectDAO;
 import com.epam.datalab.backendapi.dao.ProjectDAOImpl;
 import com.epam.datalab.backendapi.dao.UserGroupDAO;
@@ -56,6 +58,7 @@ import com.epam.datalab.backendapi.service.InactivityService;
 import com.epam.datalab.backendapi.service.KeycloakService;
 import com.epam.datalab.backendapi.service.KeycloakServiceImpl;
 import com.epam.datalab.backendapi.service.LibraryService;
+import com.epam.datalab.backendapi.service.OdahuService;
 import com.epam.datalab.backendapi.service.ProjectService;
 import com.epam.datalab.backendapi.service.ReuploadKeyService;
 import com.epam.datalab.backendapi.service.SchedulerJobService;
@@ -83,6 +86,7 @@ import com.epam.datalab.backendapi.service.impl.ImageExploratoryServiceImpl;
 import com.epam.datalab.backendapi.service.impl.InactivityServiceImpl;
 import com.epam.datalab.backendapi.service.impl.LibraryServiceImpl;
 import com.epam.datalab.backendapi.service.impl.MavenCentralLibraryService;
+import com.epam.datalab.backendapi.service.impl.OdahuServiceImpl;
 import com.epam.datalab.backendapi.service.impl.ProjectServiceImpl;
 import com.epam.datalab.backendapi.service.impl.ReuploadKeyServiceImpl;
 import com.epam.datalab.backendapi.service.impl.SchedulerJobServiceImpl;
@@ -155,22 +159,24 @@ public class ProductionModule extends ModuleBase<SelfServiceApplicationConfigura
         bind(UserGroupService.class).to(UserGroupServiceImpl.class);
         bind(UserRoleService.class).to(UserRoleServiceImpl.class);
         bind(UserRoleDAO.class).to(UserRoleDAOImpl.class);
-        bind(UserGroupDAO.class).to(UserGroupDAOImpl.class);
-        bind(InactivityService.class).to(InactivityServiceImpl.class);
-        bind(ApplicationSettingService.class).to(ApplicationSettingServiceImpl.class);
-        bind(UserSettingService.class).to(UserSettingServiceImpl.class);
-        bind(GuacamoleService.class).to(GuacamoleServiceImpl.class);
-        bind(EndpointService.class).to(EndpointServiceImpl.class);
-        bind(EndpointDAO.class).to(EndpointDAOImpl.class);
-        bind(ProjectService.class).to(ProjectServiceImpl.class);
-        bind(AuditService.class).to(AuditServiceImpl.class);
-        bind(ProjectDAO.class).to(ProjectDAOImpl.class);
-        bind(BillingDAO.class).to(BaseBillingDAO.class);
-        bind(AuditDAO.class).to(AuditDAOImpl.class);
-        bind(BucketService.class).to(BucketServiceImpl.class);
-        bind(TagService.class).to(TagServiceImpl.class);
-        bind(SecurityService.class).to(SecurityServiceImpl.class);
-        bind(KeycloakService.class).to(KeycloakServiceImpl.class);
-        bind(Client.class).toInstance(httpClient);
+	    bind(UserGroupDAO.class).to(UserGroupDAOImpl.class);
+	    bind(InactivityService.class).to(InactivityServiceImpl.class);
+	    bind(ApplicationSettingService.class).to(ApplicationSettingServiceImpl.class);
+	    bind(UserSettingService.class).to(UserSettingServiceImpl.class);
+	    bind(GuacamoleService.class).to(GuacamoleServiceImpl.class);
+	    bind(EndpointService.class).to(EndpointServiceImpl.class);
+	    bind(EndpointDAO.class).to(EndpointDAOImpl.class);
+	    bind(ProjectService.class).to(ProjectServiceImpl.class);
+	    bind(AuditService.class).to(AuditServiceImpl.class);
+	    bind(ProjectDAO.class).to(ProjectDAOImpl.class);
+	    bind(OdahuDAO.class).to(OdahuDAOImpl.class);
+	    bind(OdahuService.class).to(OdahuServiceImpl.class);
+	    bind(BillingDAO.class).to(BaseBillingDAO.class);
+	    bind(AuditDAO.class).to(AuditDAOImpl.class);
+	    bind(BucketService.class).to(BucketServiceImpl.class);
+	    bind(TagService.class).to(TagServiceImpl.class);
+	    bind(SecurityService.class).to(SecurityServiceImpl.class);
+	    bind(KeycloakService.class).to(KeycloakServiceImpl.class);
+	    bind(Client.class).toInstance(httpClient);
     }
 }
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/OdahuResource.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/OdahuResource.java
new file mode 100644
index 0000000..14c977d
--- /dev/null
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/OdahuResource.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.epam.datalab.backendapi.resources;
+
+import com.epam.datalab.auth.UserInfo;
+import com.epam.datalab.backendapi.domain.OdahuActionDTO;
+import com.epam.datalab.backendapi.domain.OdahuCreateDTO;
+import com.epam.datalab.backendapi.service.OdahuService;
+import com.google.inject.Inject;
+import io.dropwizard.auth.Auth;
+import io.swagger.v3.oas.annotations.Parameter;
+
+import javax.annotation.security.RolesAllowed;
+import javax.validation.Valid;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.net.URI;
+
+@Path("odahu")
+@Consumes(MediaType.APPLICATION_JSON)
+public class OdahuResource {
+
+	private final OdahuService odahuService;
+
+	@Inject
+	public OdahuResource(OdahuService odahuService) {
+		this.odahuService = odahuService;
+	}
+
+	@GET
+	@RolesAllowed("/api/odahu")
+	@Produces(MediaType.APPLICATION_JSON)
+	public Response getOdahuClusters(@Parameter(hidden = true) @Auth UserInfo userInfo) {
+		return Response.ok(odahuService.findOdahu()).build();
+	}
+
+	@POST
+	@RolesAllowed("/api/odahu")
+	public Response createOdahuCluster(@Parameter(hidden = true) @Auth UserInfo userInfo,
+	                                   @Parameter(hidden = true) @Context UriInfo uriInfo,
+	                                   @Valid OdahuCreateDTO odahuCreateDTO) {
+		odahuService.create(odahuCreateDTO.getProject(), odahuCreateDTO, userInfo);
+		final URI uri = uriInfo.getRequestUriBuilder().path(odahuCreateDTO.getName()).build();
+		return Response.created(uri).build();
+	}
+
+	@Path("start")
+	@POST
+	@RolesAllowed("/api/odahu")
+	public Response startOdahuCluster(@Parameter(hidden = true) @Auth UserInfo userInfo,
+	                                  @Valid OdahuActionDTO startOdahuDTO) {
+		odahuService.start(startOdahuDTO.getName(), startOdahuDTO.getProject(), startOdahuDTO.getEndpoint(), userInfo);
+		return Response.accepted().build();
+	}
+
+	@Path("stop")
+	@POST
+	@RolesAllowed("/api/odahu")
+	public Response stopOdahuCluster(@Parameter(hidden = true) @Auth UserInfo userInfo,
+	                                 @Valid OdahuActionDTO stopOdahuDTO) {
+		odahuService.stop(stopOdahuDTO.getName(), stopOdahuDTO.getProject(), stopOdahuDTO.getEndpoint(), userInfo);
+		return Response.accepted().build();
+	}
+
+	@Path("terminate")
+	@POST
+	@RolesAllowed("/api/odahu")
+	public Response terminateOdahuCluster(@Parameter(hidden = true) @Auth UserInfo userInfo,
+	                                      @Valid OdahuActionDTO terminateOdahuDTO) {
+		odahuService.terminate(terminateOdahuDTO.getName(), terminateOdahuDTO.getProject(), terminateOdahuDTO.getEndpoint(), userInfo);
+		return Response.accepted().build();
+	}
+}
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/callback/OdahuCallback.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/callback/OdahuCallback.java
new file mode 100644
index 0000000..8696fad
--- /dev/null
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/callback/OdahuCallback.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.epam.datalab.backendapi.resources.callback;
+
+import com.epam.datalab.backendapi.domain.RequestId;
+import com.epam.datalab.backendapi.service.OdahuService;
+import com.epam.datalab.dto.UserInstanceStatus;
+import com.epam.datalab.dto.base.odahu.OdahuResult;
+import com.epam.datalab.exceptions.DatalabException;
+import com.google.inject.Inject;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.Optional;
+
+@Path("odahu/status")
+@Consumes(MediaType.APPLICATION_JSON)
+public class OdahuCallback {
+
+	private final OdahuService odahuService;
+	private final RequestId requestId;
+
+	@Inject
+	public OdahuCallback(OdahuService odahuService, RequestId requestId) {
+		this.odahuService = odahuService;
+		this.requestId = requestId;
+	}
+
+	@POST
+	public Response updateOdahuStatus(OdahuResult result) {
+		requestId.checkAndRemove(result.getRequestId());
+		final UserInstanceStatus status = UserInstanceStatus.of(result.getStatus());
+		Optional.ofNullable(status)
+				.orElseThrow(() -> new DatalabException(String.format("Cannot convert %s to UserInstanceStatus", status)));
+
+		odahuService.updateStatus(result, status);
+		return Response.ok().build();
+	}
+}
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectInfrastructureInfo.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectInfrastructureInfo.java
index 20605d2..e66408c 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectInfrastructureInfo.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectInfrastructureInfo.java
@@ -21,6 +21,7 @@ package com.epam.datalab.backendapi.resources.dto;
 
 import com.epam.datalab.backendapi.domain.BillingReport;
 import com.epam.datalab.backendapi.domain.EndpointDTO;
+import com.epam.datalab.backendapi.domain.OdahuDTO;
 import com.epam.datalab.dto.UserInstanceDTO;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.AllArgsConstructor;
@@ -36,16 +37,18 @@ import java.util.Map;
 @EqualsAndHashCode
 @ToString
 public class ProjectInfrastructureInfo {
-    @JsonProperty
-    private String project;
-    @JsonProperty
-    private int billingQuoteUsed;
-    @JsonProperty
-    private Map<String, Map<String, String>> shared;
-    @JsonProperty
-    private List<UserInstanceDTO> exploratory;
-    @JsonProperty
-    private List<BillingReport> exploratoryBilling;
-    @JsonProperty
-    private List<EndpointDTO> endpoints;
+	@JsonProperty
+	private String project;
+	@JsonProperty
+	private int billingQuoteUsed;
+	@JsonProperty
+	private Map<String, Map<String, String>> shared;
+	@JsonProperty
+	private List<UserInstanceDTO> exploratory;
+	@JsonProperty
+	private List<BillingReport> exploratoryBilling;
+	@JsonProperty
+	private List<OdahuDTO> odahu;
+	@JsonProperty
+	private List<EndpointDTO> endpoints;
 }
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/OdahuService.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/OdahuService.java
new file mode 100644
index 0000000..509083d
--- /dev/null
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/OdahuService.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.epam.datalab.backendapi.service;
+
+import com.epam.datalab.auth.UserInfo;
+import com.epam.datalab.backendapi.domain.OdahuCreateDTO;
+import com.epam.datalab.backendapi.domain.OdahuDTO;
+import com.epam.datalab.dto.UserInstanceStatus;
+import com.epam.datalab.dto.base.odahu.OdahuResult;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface OdahuService {
+	List<OdahuDTO> findOdahu();
+
+	Optional<OdahuDTO> get(String project, String endpoint);
+
+	void create(String project, OdahuCreateDTO createOdahuDTO, UserInfo userInfo);
+
+	void start(String name, String project, String endpoint, UserInfo user);
+
+	void stop(String name, String project, String endpoint, UserInfo user);
+
+	void terminate(String name, String project, String endpoint, UserInfo user);
+
+	void updateStatus(OdahuResult odahuResult, UserInstanceStatus status);
+
+	boolean inProgress(String project, String endpoint);
+}
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/EndpointServiceImpl.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/EndpointServiceImpl.java
index 7d82807..77ce551 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/EndpointServiceImpl.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/EndpointServiceImpl.java
@@ -30,6 +30,7 @@ import com.epam.datalab.backendapi.domain.EndpointDTO;
 import com.epam.datalab.backendapi.domain.EndpointResourcesDTO;
 import com.epam.datalab.backendapi.domain.ProjectDTO;
 import com.epam.datalab.backendapi.service.EndpointService;
+import com.epam.datalab.backendapi.service.OdahuService;
 import com.epam.datalab.backendapi.service.ProjectService;
 import com.epam.datalab.cloud.CloudProvider;
 import com.epam.datalab.constants.ServiceConsts;
@@ -64,17 +65,19 @@ public class EndpointServiceImpl implements EndpointService {
     private final ExploratoryDAO exploratoryDAO;
     private final RESTService provisioningService;
     private final UserRoleDAO userRoleDao;
+    private final OdahuService odahuService;
 
     @Inject
     public EndpointServiceImpl(EndpointDAO endpointDAO, ProjectService projectService, ExploratoryDAO exploratoryDAO,
                                @Named(ServiceConsts.PROVISIONING_SERVICE_NAME) RESTService provisioningService,
-                               UserRoleDAO userRoleDao) {
+                               UserRoleDAO userRoleDao, OdahuService odahuService) {
 
         this.endpointDAO = endpointDAO;
         this.projectService = projectService;
         this.exploratoryDAO = exploratoryDAO;
         this.provisioningService = provisioningService;
         this.userRoleDao = userRoleDao;
+        this.odahuService = odahuService;
     }
 
     @Override
@@ -183,12 +186,16 @@ public class EndpointServiceImpl implements EndpointService {
     }
 
     private void checkProjectEndpointResourcesStatuses(List<ProjectDTO> projects, String endpoint) {
-        boolean isTerminationEnabled = projects.stream()
-                .anyMatch(p -> !projectService.checkExploratoriesAndComputationalProgress(p.getName(), Collections.singletonList(endpoint)) ||
-                        p.getEndpoints().stream()
-                                .anyMatch(e -> e.getName().equals(endpoint) &&
-                                        Arrays.asList(UserInstanceStatus.CREATING, UserInstanceStatus.STARTING, UserInstanceStatus.STOPPING,
-                                                UserInstanceStatus.TERMINATING).contains(e.getStatus())));
+        boolean isTerminationEnabled = projects
+                .stream()
+                .anyMatch(p ->
+                        odahuService.inProgress(p.getName(), endpoint) ||
+                                !projectService.checkExploratoriesAndComputationalProgress(p.getName(), Collections.singletonList(endpoint)) ||
+                                p.getEndpoints()
+                                        .stream()
+                                        .anyMatch(e -> e.getName().equals(endpoint) &&
+                                                Arrays.asList(UserInstanceStatus.CREATING, UserInstanceStatus.STARTING, UserInstanceStatus.STOPPING,
+                                                        UserInstanceStatus.TERMINATING).contains(e.getStatus())));
 
         if (isTerminationEnabled) {
             throw new ResourceConflictException(("Can not terminate resources of endpoint because one of project " +
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/EnvironmentServiceImpl.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/EnvironmentServiceImpl.java
index aedf269..abc139a 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/EnvironmentServiceImpl.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/EnvironmentServiceImpl.java
@@ -26,6 +26,7 @@ import com.epam.datalab.backendapi.annotation.User;
 import com.epam.datalab.backendapi.dao.EnvDAO;
 import com.epam.datalab.backendapi.dao.ExploratoryDAO;
 import com.epam.datalab.backendapi.dao.UserSettingsDAO;
+import com.epam.datalab.backendapi.domain.OdahuDTO;
 import com.epam.datalab.backendapi.domain.ProjectDTO;
 import com.epam.datalab.backendapi.resources.dto.UserDTO;
 import com.epam.datalab.backendapi.resources.dto.UserResourceInfo;
@@ -49,6 +50,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import static com.epam.datalab.backendapi.resources.dto.UserDTO.Status.ACTIVE;
@@ -58,7 +60,6 @@ import static com.epam.datalab.dto.UserInstanceStatus.CREATING_IMAGE;
 import static com.epam.datalab.dto.UserInstanceStatus.RUNNING;
 import static com.epam.datalab.dto.UserInstanceStatus.STARTING;
 import static com.epam.datalab.rest.contracts.ComputationalAPI.AUDIT_MESSAGE;
-import static java.util.stream.Collectors.toList;
 
 @Singleton
 @Slf4j
@@ -101,19 +102,19 @@ public class EnvironmentServiceImpl implements EnvironmentService {
         final Stream<UserDTO> notActiveUsersStream = notActiveUsers
                 .stream()
                 .map(u -> toUserDTO(u, NOT_ACTIVE));
-        return Stream.concat(activeUsersStream, notActiveUsersStream)
-                .collect(toList());
+	    return Stream.concat(activeUsersStream, notActiveUsersStream)
+			    .collect(Collectors.toList());
     }
 
     @Override
     public List<UserResourceInfo> getAllEnv(UserInfo user) {
         log.debug("Getting all user's environment...");
         List<UserInstanceDTO> expList = exploratoryDAO.getInstances();
-        return projectService.getProjects(user)
-                .stream()
-                .map(projectDTO -> getProjectEnv(projectDTO, expList))
-                .flatMap(Collection::stream)
-                .collect(toList());
+	    return projectService.getProjects(user)
+			    .stream()
+			    .map(projectDTO -> getProjectEnv(projectDTO, expList))
+			    .flatMap(Collection::stream)
+			    .collect(Collectors.toList());
     }
 
     @Override
@@ -224,23 +225,29 @@ public class EnvironmentServiceImpl implements EnvironmentService {
     }
 
     private List<UserResourceInfo> getProjectEnv(ProjectDTO projectDTO, List<UserInstanceDTO> allInstances) {
-        final Stream<UserResourceInfo> userResources = allInstances
-                .stream()
-                .filter(instance -> instance.getProject().equals(projectDTO.getName()))
-                .map(this::toUserResourceInfo);
-        if (projectDTO.getEndpoints() != null) {
-            final Stream<UserResourceInfo> edges = projectDTO.getEndpoints()
-                    .stream()
-                    .map(e -> UserResourceInfo.builder()
-                            .resourceType(ResourceEnum.EDGE_NODE)
-                            .resourceStatus(e.getStatus().toString())
-                            .project(projectDTO.getName())
-                            .endpoint(e.getName())
-                            .ip(e.getEdgeInfo() != null ? e.getEdgeInfo().getPublicIp() : null)
-                            .build());
-            return Stream.concat(edges, userResources).collect(toList());
-        } else {
-            return userResources.collect(toList());
+	    final Stream<UserResourceInfo> userResources = allInstances
+			    .stream()
+			    .filter(instance -> instance.getProject().equals(projectDTO.getName()))
+			    .map(this::toUserResourceInfo);
+
+	    Stream<UserResourceInfo> odahuResources = projectDTO.getOdahu()
+			    .stream()
+			    .map(this::toUserResourceInfo);
+
+	    if (projectDTO.getEndpoints() != null) {
+		    final Stream<UserResourceInfo> edges = projectDTO.getEndpoints()
+				    .stream()
+				    .map(e -> UserResourceInfo.builder()
+						    .resourceType(ResourceEnum.EDGE_NODE)
+						    .resourceStatus(e.getStatus().toString())
+						    .project(projectDTO.getName())
+						    .endpoint(e.getName())
+						    .ip(e.getEdgeInfo() != null ? e.getEdgeInfo().getPublicIp() : null)
+						    .build());
+		    return Stream.concat(edges, Stream.concat(odahuResources, userResources))
+				    .collect(Collectors.toList());
+	    } else {
+		    return userResources.collect(Collectors.toList());
         }
     }
 
@@ -248,24 +255,33 @@ public class EnvironmentServiceImpl implements EnvironmentService {
         return UserResourceInfo.builder()
                 .resourceType(ResourceEnum.NOTEBOOK)
                 .resourceName(userInstance.getExploratoryName())
-                .resourceShape(userInstance.getShape())
-                .resourceStatus(userInstance.getStatus())
-                .computationalResources(userInstance.getResources())
-                .user(userInstance.getUser())
-                .project(userInstance.getProject())
-                .endpoint(userInstance.getEndpoint())
-                .cloudProvider(userInstance.getCloudProvider())
-                .exploratoryUrls(userInstance.getResourceUrl())
-                .build();
+		        .resourceShape(userInstance.getShape())
+		        .resourceStatus(userInstance.getStatus())
+		        .computationalResources(userInstance.getResources())
+		        .user(userInstance.getUser())
+		        .project(userInstance.getProject())
+		        .endpoint(userInstance.getEndpoint())
+		        .cloudProvider(userInstance.getCloudProvider())
+		        .exploratoryUrls(userInstance.getResourceUrl())
+		        .build();
     }
 
-    private void checkProjectResourceConditions(String project, String action) {
-        final List<UserInstanceDTO> userInstances = exploratoryDAO.fetchProjectExploratoriesWhereStatusIn(project,
-                Arrays.asList(CREATING, STARTING, CREATING_IMAGE), CREATING, STARTING, CREATING_IMAGE);
+	private UserResourceInfo toUserResourceInfo(OdahuDTO odahuDTO) {
+		return UserResourceInfo.builder()
+				.resourceType(ResourceEnum.ODAHU)
+				.resourceName(odahuDTO.getName())
+				.resourceStatus(odahuDTO.getStatus().toString())
+				.project(odahuDTO.getProject())
+				.build();
+	}
 
-        if (!userInstances.isEmpty()) {
-            log.error(String.format(ERROR_MSG_FORMAT, action));
-            throw new ResourceConflictException(String.format(ERROR_MSG_FORMAT, action));
-        }
-    }
+	private void checkProjectResourceConditions(String project, String action) {
+		final List<UserInstanceDTO> userInstances = exploratoryDAO.fetchProjectExploratoriesWhereStatusIn(project,
+				Arrays.asList(CREATING, STARTING, CREATING_IMAGE), CREATING, STARTING, CREATING_IMAGE);
+
+		if (!userInstances.isEmpty()) {
+			log.error(String.format(ERROR_MSG_FORMAT, action));
+			throw new ResourceConflictException(String.format(ERROR_MSG_FORMAT, action));
+		}
+	}
 }
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/InfrastructureInfoServiceImpl.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/InfrastructureInfoServiceImpl.java
index e0c63d0..69973c1 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/InfrastructureInfoServiceImpl.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/InfrastructureInfoServiceImpl.java
@@ -103,12 +103,13 @@ public class InfrastructureInfoServiceImpl implements InfrastructureInfoService
                 .map(p -> {
                     List<UserInstanceDTO> exploratories = expDAO.findExploratories(user.getName(), p.getName());
                     return ProjectInfrastructureInfo.builder()
-                            .project(p.getName())
-                            .billingQuoteUsed(billingService.getBillingProjectQuoteUsed(p.getName()))
-                            .shared(getSharedInfo(p.getName()))
-                            .exploratory(exploratories)
-                            .exploratoryBilling(getExploratoryBillingData(exploratories))
-                            .endpoints(getEndpoints(allEndpoints, p))
+		                    .project(p.getName())
+		                    .billingQuoteUsed(billingService.getBillingProjectQuoteUsed(p.getName()))
+		                    .shared(getSharedInfo(p.getName()))
+		                    .exploratory(exploratories)
+		                    .exploratoryBilling(getExploratoryBillingData(exploratories))
+		                    .endpoints(getEndpoints(allEndpoints, p))
+		                    .odahu(p.getOdahu())
                             .build();
                 })
                 .collect(Collectors.toList());
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/OdahuServiceImpl.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/OdahuServiceImpl.java
new file mode 100644
index 0000000..d69b8ca
--- /dev/null
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/OdahuServiceImpl.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.epam.datalab.backendapi.service.impl;
+
+import com.epam.datalab.auth.UserInfo;
+import com.epam.datalab.backendapi.annotation.BudgetLimited;
+import com.epam.datalab.backendapi.annotation.Project;
+import com.epam.datalab.backendapi.dao.OdahuDAO;
+import com.epam.datalab.backendapi.domain.EndpointDTO;
+import com.epam.datalab.backendapi.domain.OdahuCreateDTO;
+import com.epam.datalab.backendapi.domain.OdahuDTO;
+import com.epam.datalab.backendapi.domain.OdahuFieldsDTO;
+import com.epam.datalab.backendapi.domain.ProjectDTO;
+import com.epam.datalab.backendapi.domain.RequestId;
+import com.epam.datalab.backendapi.service.EndpointService;
+import com.epam.datalab.backendapi.service.OdahuService;
+import com.epam.datalab.backendapi.service.ProjectService;
+import com.epam.datalab.backendapi.util.RequestBuilder;
+import com.epam.datalab.constants.ServiceConsts;
+import com.epam.datalab.dto.UserInstanceStatus;
+import com.epam.datalab.dto.base.odahu.OdahuResult;
+import com.epam.datalab.exceptions.DatalabException;
+import com.epam.datalab.exceptions.ResourceConflictException;
+import com.epam.datalab.rest.client.RESTService;
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+@Slf4j
+public class OdahuServiceImpl implements OdahuService {
+
+	private static final String CREATE_ODAHU_API = "infrastructure/odahu";
+	private static final String START_ODAHU_API = "infrastructure/odahu/start";
+	private static final String STOP_ODAHU_API = "infrastructure/odahu/stop";
+	private static final String TERMINATE_ODAHU_API = "infrastructure/odahu/terminate";
+
+	private final ProjectService projectService;
+	private final EndpointService endpointService;
+	private final OdahuDAO odahuDAO;
+	private final RESTService provisioningService;
+	private final RequestBuilder requestBuilder;
+	private final RequestId requestId;
+
+	@Inject
+	public OdahuServiceImpl(ProjectService projectService, EndpointService endpointService, OdahuDAO odahuDAO,
+	                        @Named(ServiceConsts.PROVISIONING_SERVICE_NAME) RESTService provisioningService,
+	                        RequestBuilder requestBuilder, RequestId requestId) {
+		this.projectService = projectService;
+		this.endpointService = endpointService;
+		this.odahuDAO = odahuDAO;
+		this.provisioningService = provisioningService;
+		this.requestBuilder = requestBuilder;
+		this.requestId = requestId;
+	}
+
+
+	@Override
+	public List<OdahuDTO> findOdahu() {
+		return odahuDAO.findOdahuClusters();
+	}
+
+	@Override
+	public Optional<OdahuDTO> get(String project, String endpoint) {
+		return odahuDAO.getByProjectEndpoint(project, endpoint);
+	}
+
+	@BudgetLimited
+	@Override
+	public void create(@Project String project, OdahuCreateDTO odahuCreateDTO, UserInfo user) {
+		log.info("Trying to create odahu cluster for project: " + project);
+		boolean activeCluster = odahuDAO.findOdahuClusters(odahuCreateDTO.getProject(), odahuCreateDTO.getEndpoint()).stream()
+				.noneMatch(o -> !o.getStatus().equals(UserInstanceStatus.FAILED) && !o.getStatus().equals(UserInstanceStatus.TERMINATED));
+		if (!activeCluster) {
+			throw new ResourceConflictException(String.format("Odahu cluster already exist in system for project %s " +
+					"and endpoint %s", odahuCreateDTO.getProject(), odahuCreateDTO.getEndpoint()));
+		}
+		ProjectDTO projectDTO = projectService.get(project);
+		boolean isAdded = odahuDAO.create(new OdahuDTO(odahuCreateDTO.getName(), odahuCreateDTO.getProject(),
+				odahuCreateDTO.getEndpoint(), UserInstanceStatus.CREATING, getTags(odahuCreateDTO)));
+		if (isAdded) {
+			String url = null;
+			EndpointDTO endpointDTO = endpointService.get(odahuCreateDTO.getEndpoint());
+			try {
+				url = endpointDTO.getUrl() + CREATE_ODAHU_API;
+				String uuid =
+						provisioningService.post(url, user.getAccessToken(),
+								requestBuilder.newOdahuCreate(user.getName(), odahuCreateDTO, projectDTO, endpointDTO), String.class);
+				requestId.put(user.getName(), uuid);
+			} catch (Exception e) {
+				log.error("Can not perform {} due to: {}, {}", url, e.getMessage(), e);
+				odahuDAO.updateStatus(odahuCreateDTO.getName(), odahuCreateDTO.getProject(), odahuCreateDTO.getEndpoint(),
+						UserInstanceStatus.FAILED);
+			}
+		} else {
+			throw new DatalabException(String.format("The odahu fields of the %s can not be updated in DB.", project));
+		}
+	}
+
+	@BudgetLimited
+	@Override
+	public void start(String name, @Project String project, String endpoint, UserInfo user) {
+		log.info("Trying to start odahu cluster for project: " + project);
+		odahuDAO.updateStatus(name, project, endpoint, UserInstanceStatus.STARTING);
+		actionOnCloud(user, START_ODAHU_API, name, project, endpoint);
+	}
+
+	@Override
+	public void stop(String name, String project, String endpoint, UserInfo user) {
+		log.info("Trying to stop odahu cluster for project: " + project);
+		odahuDAO.updateStatus(name, project, endpoint, UserInstanceStatus.STOPPING);
+		actionOnCloud(user, STOP_ODAHU_API, name, project, endpoint);
+	}
+
+	@Override
+	public void terminate(String name, String project, String endpoint, UserInfo user) {
+		log.info("Trying to terminate odahu cluster for project: " + project);
+		odahuDAO.findOdahuClusters(project, endpoint).stream()
+				.filter(odahuDTO -> name.equals(odahuDTO.getName())
+						&& !odahuDTO.getStatus().equals(UserInstanceStatus.FAILED))
+				.forEach(odahuDTO -> {
+					if (UserInstanceStatus.RUNNING == odahuDTO.getStatus()) {
+						odahuDAO.updateStatus(name, project, endpoint, UserInstanceStatus.TERMINATING);
+						actionOnCloud(user, TERMINATE_ODAHU_API, name, project, endpoint);
+					} else {
+						log.error("Cannot terminate odahu cluster {}", odahuDTO);
+						throw new DatalabException(String.format("Cannot terminate odahu cluster %s", odahuDTO));
+					}
+				});
+	}
+
+	@Override
+	public void updateStatus(OdahuResult result, UserInstanceStatus status) {
+		if (Objects.nonNull(result.getResourceUrls()) && !result.getResourceUrls().isEmpty()) {
+			odahuDAO.updateStatusAndUrls(result, status);
+		} else {
+			odahuDAO.updateStatus(result.getName(), result.getProjectName(), result.getEndpointName(), status);
+		}
+	}
+
+	@Override
+	public boolean inProgress(String project, String endpoint) {
+		return get(project, endpoint)
+				.filter(odahu -> Arrays.asList(UserInstanceStatus.CREATING, UserInstanceStatus.STARTING,
+						UserInstanceStatus.STOPPING, UserInstanceStatus.TERMINATING).contains(odahu.getStatus()))
+				.isPresent();
+	}
+
+	private void actionOnCloud(UserInfo user, String uri, String name, String project, String endpoint) {
+		String url = null;
+		EndpointDTO endpointDTO = endpointService.get(endpoint);
+		ProjectDTO projectDTO = projectService.get(project);
+		try {
+			OdahuFieldsDTO fields = odahuDAO.getFields(name, project, endpoint);
+			url = endpointDTO.getUrl() + uri;
+			String uuid =
+					provisioningService.post(url, user.getAccessToken(),
+							requestBuilder.newOdahuAction(user.getName(), name, projectDTO, endpointDTO, fields), String.class);
+			requestId.put(user.getName(), uuid);
+		} catch (Exception e) {
+			log.error("Can not perform {} due to: {}, {}", url, e.getMessage(), e);
+			odahuDAO.updateStatus(name, project, project, UserInstanceStatus.FAILED);
+		}
+	}
+
+	private Map<String, String> getTags(OdahuCreateDTO odahuCreateDTO) {
+		Map<String, String> tags = new HashMap<>();
+		tags.put("custom_tag", odahuCreateDTO.getCustomTag());
+		tags.put("project_tag", odahuCreateDTO.getProject());
+		tags.put("endpoint_tag", odahuCreateDTO.getEndpoint());
+		return tags;
+	}
+}
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/ProjectServiceImpl.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/ProjectServiceImpl.java
index c76dedb..dc39c36 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/ProjectServiceImpl.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/ProjectServiceImpl.java
@@ -41,6 +41,7 @@ import com.epam.datalab.backendapi.domain.UpdateProjectDTO;
 import com.epam.datalab.backendapi.roles.UserRoles;
 import com.epam.datalab.backendapi.service.EndpointService;
 import com.epam.datalab.backendapi.service.ExploratoryService;
+import com.epam.datalab.backendapi.service.OdahuService;
 import com.epam.datalab.backendapi.service.ProjectService;
 import com.epam.datalab.backendapi.util.RequestBuilder;
 import com.epam.datalab.constants.ServiceConsts;
@@ -94,14 +95,13 @@ public class ProjectServiceImpl implements ProjectService {
     private final ProjectDAO projectDAO;
     private final UserGroupDAO userGroupDAO;
     private final ExploratoryDAO exploratoryDAO;
-
     private final ExploratoryService exploratoryService;
     private final RESTService provisioningService;
     private final EndpointService endpointService;
-
     private final RequestId requestId;
     private final RequestBuilder requestBuilder;
     private final SelfServiceApplicationConfiguration configuration;
+    private final OdahuService odahuService;
 
 
     @Inject
@@ -109,7 +109,8 @@ public class ProjectServiceImpl implements ProjectService {
                               UserGroupDAO userGroupDAO,
                               @Named(ServiceConsts.PROVISIONING_SERVICE_NAME) RESTService provisioningService,
                               RequestId requestId, RequestBuilder requestBuilder, EndpointService endpointService,
-                              ExploratoryDAO exploratoryDAO, SelfServiceApplicationConfiguration configuration) {
+                              ExploratoryDAO exploratoryDAO, SelfServiceApplicationConfiguration configuration,
+                              OdahuService odahuService) {
         this.projectDAO = projectDAO;
         this.exploratoryService = exploratoryService;
         this.userGroupDAO = userGroupDAO;
@@ -119,6 +120,7 @@ public class ProjectServiceImpl implements ProjectService {
         this.endpointService = endpointService;
         this.exploratoryDAO = exploratoryDAO;
         this.configuration = configuration;
+        this.odahuService = odahuService;
     }
 
     @Override
@@ -170,10 +172,12 @@ public class ProjectServiceImpl implements ProjectService {
     @Audit(action = TERMINATE, type = EDGE_NODE)
     @Override
     public void terminateEndpoint(@User UserInfo userInfo, @ResourceName String endpoint, @Project String name) {
-
         projectActionOnCloud(userInfo, name, TERMINATE_PRJ_API, endpoint);
         projectDAO.updateEdgeStatus(name, endpoint, UserInstanceStatus.TERMINATING);
         exploratoryService.updateProjectExploratoryStatuses(userInfo, name, endpoint, UserInstanceStatus.TERMINATING);
+        odahuService.get(name, endpoint)
+                .filter(o -> UserInstanceStatus.RUNNING == o.getStatus())
+                .ifPresent(odahu -> odahuService.terminate(odahu.getName(), name, endpoint, userInfo));
     }
 
     @ProjectAdmin
@@ -337,17 +341,18 @@ public class ProjectServiceImpl implements ProjectService {
     }
 
     private void checkProjectRelatedResourcesInProgress(String projectName, List<ProjectEndpointDTO> endpoints, String action) {
-        boolean edgeProgress = endpoints
+        boolean edgeAndOdahuProgress = endpoints
                 .stream()
                 .anyMatch(e ->
                         Arrays.asList(UserInstanceStatus.CREATING, UserInstanceStatus.STARTING, UserInstanceStatus.STOPPING,
-                                UserInstanceStatus.TERMINATING).contains(e.getStatus()));
+                                UserInstanceStatus.TERMINATING).contains(e.getStatus())
+                                || odahuService.inProgress(projectName, e.getName()));
 
         List<String> endpointNames = endpoints
                 .stream()
                 .map(ProjectEndpointDTO::getName)
                 .collect(Collectors.toList());
-        if (edgeProgress || !checkExploratoriesAndComputationalProgress(projectName, endpointNames)) {
+        if (edgeAndOdahuProgress || !checkExploratoriesAndComputationalProgress(projectName, endpointNames)) {
             throw new ResourceConflictException((String.format("Can not %s environment because one of project " +
                     "resource is in processing stage", action)));
         }
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/util/RequestBuilder.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/util/RequestBuilder.java
index 6ece8db..04804ff 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/util/RequestBuilder.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/util/RequestBuilder.java
@@ -23,6 +23,8 @@ import com.epam.datalab.auth.UserInfo;
 import com.epam.datalab.backendapi.conf.SelfServiceApplicationConfiguration;
 import com.epam.datalab.backendapi.dao.SettingsDAO;
 import com.epam.datalab.backendapi.domain.EndpointDTO;
+import com.epam.datalab.backendapi.domain.OdahuCreateDTO;
+import com.epam.datalab.backendapi.domain.OdahuFieldsDTO;
 import com.epam.datalab.backendapi.domain.ProjectDTO;
 import com.epam.datalab.backendapi.resources.dto.BackupFormDTO;
 import com.epam.datalab.backendapi.resources.dto.ComputationalCreateFormDTO;
@@ -71,6 +73,8 @@ import com.epam.datalab.dto.gcp.computational.ComputationalCreateGcp;
 import com.epam.datalab.dto.gcp.computational.GcpComputationalTerminateDTO;
 import com.epam.datalab.dto.gcp.computational.SparkComputationalCreateGcp;
 import com.epam.datalab.dto.gcp.exploratory.ExploratoryCreateGcp;
+import com.epam.datalab.dto.odahu.ActionOdahuDTO;
+import com.epam.datalab.dto.odahu.CreateOdahuDTO;
 import com.epam.datalab.dto.project.ProjectActionDTO;
 import com.epam.datalab.dto.project.ProjectCreateDTO;
 import com.epam.datalab.dto.status.EnvResourceList;
@@ -628,6 +632,33 @@ public class RequestBuilder {
 				.withCloudSettings(cloudSettings(userInfo.getName(), endpointDTO.getCloudProvider()));
 	}
 
+	public CreateOdahuDTO newOdahuCreate(String user, OdahuCreateDTO odahuCreateDTO, ProjectDTO projectDTO, EndpointDTO endpointDTO) {
+		return CreateOdahuDTO.builder()
+				.name(odahuCreateDTO.getName())
+				.project(projectDTO.getName())
+				.endpoint(odahuCreateDTO.getEndpoint())
+				.key(projectDTO.getKey().replace("\n", ""))
+				.build()
+				.withEdgeUserName(getEdgeUserName(user, endpointDTO.getCloudProvider()))
+				.withCloudSettings(cloudSettings(user, endpointDTO.getCloudProvider()));
+	}
+
+	public ActionOdahuDTO newOdahuAction(String user, String name, ProjectDTO projectDTO, EndpointDTO endpointDTO,
+	                                     OdahuFieldsDTO odahuFields) {
+		return ActionOdahuDTO.builder()
+				.name(name)
+				.project(projectDTO.getName())
+				.key(projectDTO.getKey().replace("\n", ""))
+				.endpoint(endpointDTO.getName())
+				.grafanaAdmin(odahuFields.getGrafanaAdmin())
+				.grafanaPassword(odahuFields.getGrafanaPassword())
+				.oauthCookieSecret(odahuFields.getOauthCookieSecret())
+				.decryptToken(odahuFields.getDecryptToken())
+				.build()
+				.withEdgeUserName(getEdgeUserName(user, endpointDTO.getCloudProvider()))
+				.withCloudSettings(cloudSettings(user, endpointDTO.getCloudProvider()));
+	}
+
 	public UserEnvironmentResources newInfrastructureStatus(String user, CloudProvider cloudProvider, EnvResourceList resourceList) {
 		return newResourceSysBaseDTO(user, cloudProvider, UserEnvironmentResources.class)
 				.withResourceList(resourceList);
diff --git a/services/self-service/src/test/java/com/epam/datalab/backendapi/service/ProjectServiceImplTest.java b/services/self-service/src/test/java/com/epam/datalab/backendapi/service/ProjectServiceImplTest.java
index d7752d7..b2fc3d6 100644
--- a/services/self-service/src/test/java/com/epam/datalab/backendapi/service/ProjectServiceImplTest.java
+++ b/services/self-service/src/test/java/com/epam/datalab/backendapi/service/ProjectServiceImplTest.java
@@ -25,6 +25,7 @@ import com.epam.datalab.backendapi.dao.ExploratoryDAO;
 import com.epam.datalab.backendapi.dao.ProjectDAO;
 import com.epam.datalab.backendapi.dao.UserGroupDAO;
 import com.epam.datalab.backendapi.domain.EndpointDTO;
+import com.epam.datalab.backendapi.domain.OdahuDTO;
 import com.epam.datalab.backendapi.domain.ProjectDTO;
 import com.epam.datalab.backendapi.domain.ProjectEndpointDTO;
 import com.epam.datalab.backendapi.domain.RequestId;
@@ -93,25 +94,27 @@ public class ProjectServiceImplTest extends TestBase {
     private RESTService provisioningService;
     @Mock
     private RequestBuilder requestBuilder;
-    @Mock
-    private RequestId requestId;
-    @Mock
-    private ExploratoryService exploratoryService;
-    @Mock
-    private ExploratoryDAO exploratoryDAO;
-    @Mock
-    private UserGroupDAO userGroupDao;
-    @Mock
-    private SelfServiceApplicationConfiguration configuration;
-    @InjectMocks
-    private ProjectServiceImpl projectService;
+	@Mock
+	private RequestId requestId;
+	@Mock
+	private ExploratoryService exploratoryService;
+	@Mock
+	private ExploratoryDAO exploratoryDAO;
+	@Mock
+	private UserGroupDAO userGroupDao;
+	@Mock
+	private SelfServiceApplicationConfiguration configuration;
+	@Mock
+	private OdahuService odahuService;
+	@InjectMocks
+	private ProjectServiceImpl projectService;
 
-    @Test
-    public void getProjects() {
-        List<ProjectDTO> projectsMock = getProjectDTOs();
-        when(projectDAO.getProjects()).thenReturn(projectsMock);
+	@Test
+	public void getProjects() {
+		List<ProjectDTO> projectsMock = getProjectDTOs();
+		when(projectDAO.getProjects()).thenReturn(projectsMock);
 
-        List<ProjectDTO> projects = projectService.getProjects();
+		List<ProjectDTO> projects = projectService.getProjects();
 
         assertEquals(projects, projectsMock);
         verify(projectDAO).getProjects();
@@ -226,79 +229,102 @@ public class ProjectServiceImplTest extends TestBase {
 
     @Test
     public void terminateEndpoint() {
-        when(endpointService.get(anyString())).thenReturn(getEndpointDTO());
-        when(provisioningService.post(anyString(), anyString(), any(), any())).thenReturn(UUID);
-        when(requestBuilder.newProjectAction(any(UserInfo.class), anyString(), any(EndpointDTO.class))).thenReturn(getProjectActionDTO());
-
-        projectService.terminateEndpoint(getUserInfo(), ENDPOINT_NAME, NAME1);
-
-        verify(exploratoryService).updateProjectExploratoryStatuses(getUserInfo(), NAME1, ENDPOINT_NAME, UserInstanceStatus.TERMINATING);
-        verify(projectDAO).updateEdgeStatus(NAME1, ENDPOINT_NAME, UserInstanceStatus.TERMINATING);
-        verify(endpointService).get(ENDPOINT_NAME);
-        verify(provisioningService).post(ENDPOINT_URL + TERMINATE_PRJ_API, TOKEN, getProjectActionDTO(), String.class);
-        verify(requestBuilder).newProjectAction(getUserInfo(), NAME1, getEndpointDTO());
-        verify(requestId).put(USER.toLowerCase(), UUID);
-        verifyNoMoreInteractions(projectDAO, endpointService, provisioningService, requestBuilder, exploratoryService);
+	    when(endpointService.get(anyString())).thenReturn(getEndpointDTO());
+	    when(provisioningService.post(anyString(), anyString(), any(), any())).thenReturn(UUID);
+	    when(requestBuilder.newProjectAction(any(UserInfo.class), anyString(), any(EndpointDTO.class))).thenReturn(getProjectActionDTO());
+	    when(odahuService.get(anyString(), anyString())).thenReturn(getOdahu());
+
+	    projectService.terminateEndpoint(getUserInfo(), ENDPOINT_NAME, NAME1);
+
+	    verify(exploratoryService).updateProjectExploratoryStatuses(getUserInfo(), NAME1, ENDPOINT_NAME, UserInstanceStatus.TERMINATING);
+	    verify(odahuService).get(NAME1, ENDPOINT_NAME);
+	    verify(odahuService).terminate("name", NAME1, ENDPOINT_NAME, getUserInfo());
+	    verify(projectDAO).updateEdgeStatus(NAME1, ENDPOINT_NAME, UserInstanceStatus.TERMINATING);
+	    verify(endpointService).get(ENDPOINT_NAME);
+	    verify(provisioningService).post(ENDPOINT_URL + TERMINATE_PRJ_API, TOKEN, getProjectActionDTO(), String.class);
+	    verify(requestBuilder).newProjectAction(getUserInfo(), NAME1, getEndpointDTO());
+	    verify(requestId).put(USER.toLowerCase(), UUID);
+	    verifyNoMoreInteractions(projectDAO, endpointService, provisioningService, requestBuilder, exploratoryService, odahuService);
     }
 
     @Test
     public void terminateEndpointWithException() {
-        when(endpointService.get(anyString())).thenReturn(getEndpointDTO());
-        when(requestBuilder.newProjectAction(any(UserInfo.class), anyString(), any(EndpointDTO.class))).thenReturn(getProjectActionDTO());
-        when(provisioningService.post(anyString(), anyString(), any(), any())).thenThrow(new DatalabException("Exception message"));
-
-        projectService.terminateEndpoint(getUserInfo(), ENDPOINT_NAME, NAME1);
-
-        verify(exploratoryService).updateProjectExploratoryStatuses(getUserInfo(), NAME1, ENDPOINT_NAME, UserInstanceStatus.TERMINATING);
-        verify(projectDAO).updateEdgeStatus(NAME1, ENDPOINT_NAME, UserInstanceStatus.TERMINATING);
-        verify(projectDAO).updateStatus(NAME1, ProjectDTO.Status.FAILED);
-        verify(endpointService).get(ENDPOINT_NAME);
-        verify(provisioningService).post(ENDPOINT_URL + TERMINATE_PRJ_API, TOKEN, getProjectActionDTO(), String.class);
-        verify(requestBuilder).newProjectAction(getUserInfo(), NAME1, getEndpointDTO());
-        verifyNoMoreInteractions(projectDAO, endpointService, provisioningService, requestBuilder, exploratoryService);
+	    when(endpointService.get(anyString())).thenReturn(getEndpointDTO());
+	    when(requestBuilder.newProjectAction(any(UserInfo.class), anyString(), any(EndpointDTO.class))).thenReturn(getProjectActionDTO());
+	    when(provisioningService.post(anyString(), anyString(), any(), any())).thenThrow(new DatalabException("Exception message"));
+	    when(odahuService.get(anyString(), anyString())).thenReturn(getOdahu());
+
+	    projectService.terminateEndpoint(getUserInfo(), ENDPOINT_NAME, NAME1);
+
+	    verify(exploratoryService).updateProjectExploratoryStatuses(getUserInfo(), NAME1, ENDPOINT_NAME, UserInstanceStatus.TERMINATING);
+	    verify(odahuService).get(NAME1, ENDPOINT_NAME);
+	    verify(odahuService).terminate("name", NAME1, ENDPOINT_NAME, getUserInfo());
+	    verify(projectDAO).updateEdgeStatus(NAME1, ENDPOINT_NAME, UserInstanceStatus.TERMINATING);
+	    verify(projectDAO).updateStatus(NAME1, ProjectDTO.Status.FAILED);
+	    verify(endpointService).get(ENDPOINT_NAME);
+	    verify(provisioningService).post(ENDPOINT_URL + TERMINATE_PRJ_API, TOKEN, getProjectActionDTO(), String.class);
+	    verify(requestBuilder).newProjectAction(getUserInfo(), NAME1, getEndpointDTO());
+	    verifyNoMoreInteractions(projectDAO, endpointService, provisioningService, requestBuilder, exploratoryService, odahuService);
     }
 
     @Test
     public void testTerminateEndpoint() {
-        when(endpointService.get(anyString())).thenReturn(getEndpointDTO());
-        when(provisioningService.post(anyString(), anyString(), any(), any())).thenReturn(UUID);
-        when(requestBuilder.newProjectAction(any(UserInfo.class), anyString(), any(EndpointDTO.class))).thenReturn(getProjectActionDTO());
-        when(projectDAO.get(anyString())).thenReturn(Optional.of(getProjectRunningDTO()));
-        when(exploratoryDAO.fetchProjectEndpointExploratoriesWhereStatusIn(anyString(), anyListOf(String.class), anyListOf(UserInstanceStatus.class)))
-                .thenReturn(Collections.emptyList());
+	    when(endpointService.get(anyString())).thenReturn(getEndpointDTO());
+	    when(provisioningService.post(anyString(), anyString(), any(), any())).thenReturn(UUID);
+	    when(odahuService.get(anyString(), anyString())).thenReturn(getOdahu());
+	    when(odahuService.inProgress(anyString(), anyString())).thenReturn(Boolean.FALSE);
+	    when(requestBuilder.newProjectAction(any(UserInfo.class), anyString(), any(EndpointDTO.class))).thenReturn(getProjectActionDTO());
+	    when(projectDAO.get(anyString())).thenReturn(Optional.of(getProjectRunningDTO()));
+	    when(exploratoryDAO.fetchProjectEndpointExploratoriesWhereStatusIn(anyString(), anyListOf(String.class), anyListOf(UserInstanceStatus.class)))
+			    .thenReturn(Collections.emptyList());
 
-        projectService.terminateEndpoint(getUserInfo(), Collections.singletonList(ENDPOINT_NAME), NAME1);
+	    projectService.terminateEndpoint(getUserInfo(), Collections.singletonList(ENDPOINT_NAME), NAME1);
 
-        verify(projectDAO).get(NAME1);
-        verify(exploratoryDAO).fetchProjectEndpointExploratoriesWhereStatusIn(NAME1, Collections.singletonList(ENDPOINT_NAME), notebookStatuses, computeStatuses);
-        verify(exploratoryService).updateProjectExploratoryStatuses(getUserInfo(), NAME1, ENDPOINT_NAME, UserInstanceStatus.TERMINATING);
-        verify(projectDAO).updateEdgeStatus(NAME1, ENDPOINT_NAME, UserInstanceStatus.TERMINATING);
-        verify(endpointService).get(ENDPOINT_NAME);
-        verify(provisioningService).post(ENDPOINT_URL + TERMINATE_PRJ_API, TOKEN, getProjectActionDTO(), String.class);
-        verify(requestBuilder).newProjectAction(getUserInfo(), NAME1, getEndpointDTO());
-        verify(requestId).put(USER.toLowerCase(), UUID);
-        verifyNoMoreInteractions(projectDAO, endpointService, provisioningService, requestBuilder, exploratoryDAO);
+	    verify(projectDAO).get(NAME1);
+	    verify(exploratoryDAO).fetchProjectEndpointExploratoriesWhereStatusIn(NAME1, Collections.singletonList(ENDPOINT_NAME), notebookStatuses, computeStatuses);
+	    verify(exploratoryService).updateProjectExploratoryStatuses(getUserInfo(), NAME1, ENDPOINT_NAME, UserInstanceStatus.TERMINATING);
+	    verify(projectDAO).updateEdgeStatus(NAME1, ENDPOINT_NAME, UserInstanceStatus.TERMINATING);
+	    verify(endpointService).get(ENDPOINT_NAME);
+	    verify(odahuService).get(NAME1, ENDPOINT_NAME);
+	    verify(odahuService).terminate("name", NAME1, ENDPOINT_NAME, getUserInfo());
+	    verify(odahuService).inProgress(NAME1, ENDPOINT_NAME);
+	    verify(provisioningService).post(ENDPOINT_URL + TERMINATE_PRJ_API, TOKEN, getProjectActionDTO(), String.class);
+	    verify(requestBuilder).newProjectAction(getUserInfo(), NAME1, getEndpointDTO());
+	    verify(requestId).put(USER.toLowerCase(), UUID);
+	    verifyNoMoreInteractions(projectDAO, endpointService, provisioningService, requestBuilder, exploratoryDAO, odahuService);
     }
 
-    @Test(expected = ResourceConflictException.class)
-    public void testTerminateEndpointWithException1() {
-        when(projectDAO.get(anyString())).thenReturn(Optional.of(getProjectCreatingDTO()));
+	@Test(expected = ResourceConflictException.class)
+	public void testTerminateEndpointWithException1() {
+		when(projectDAO.get(anyString())).thenReturn(Optional.of(getProjectCreatingDTO()));
 
-        projectService.terminateEndpoint(getUserInfo(), Collections.singletonList(ENDPOINT_NAME), NAME1);
+		projectService.terminateEndpoint(getUserInfo(), Collections.singletonList(ENDPOINT_NAME), NAME1);
 
-        verify(projectDAO).get(NAME1);
-        verifyNoMoreInteractions(projectDAO);
-    }
+		verify(projectDAO).get(NAME1);
+		verifyNoMoreInteractions(projectDAO);
+	}
 
-    @Test
-    public void start() {
-        when(endpointService.get(anyString())).thenReturn(getEndpointDTO());
-        when(provisioningService.post(anyString(), anyString(), any(), any())).thenReturn(UUID);
-        when(requestBuilder.newProjectAction(any(UserInfo.class), anyString(), any(EndpointDTO.class))).thenReturn(getProjectActionDTO());
+	@Test(expected = ResourceConflictException.class)
+	public void testTerminateEndpointWithException2() {
+		when(projectDAO.get(anyString())).thenReturn(Optional.of(getProjectRunningDTO()));
+		when(odahuService.inProgress(anyString(), anyString())).thenReturn(Boolean.TRUE);
 
-        projectService.start(getUserInfo(), ENDPOINT_NAME, NAME1);
+		projectService.terminateEndpoint(getUserInfo(), Collections.singletonList(ENDPOINT_NAME), NAME1);
 
-        verify(projectDAO).updateEdgeStatus(NAME1, ENDPOINT_NAME, UserInstanceStatus.STARTING);
+		verify(projectDAO).get(NAME1);
+		verify(odahuService).inProgress(NAME1, ENDPOINT_NAME);
+		verifyNoMoreInteractions(projectDAO, odahuService);
+	}
+
+	@Test
+	public void start() {
+		when(endpointService.get(anyString())).thenReturn(getEndpointDTO());
+		when(provisioningService.post(anyString(), anyString(), any(), any())).thenReturn(UUID);
+		when(requestBuilder.newProjectAction(any(UserInfo.class), anyString(), any(EndpointDTO.class))).thenReturn(getProjectActionDTO());
+
+		projectService.start(getUserInfo(), ENDPOINT_NAME, NAME1);
+
+		verify(projectDAO).updateEdgeStatus(NAME1, ENDPOINT_NAME, UserInstanceStatus.STARTING);
         verify(endpointService).get(ENDPOINT_NAME);
         verify(provisioningService).post(ENDPOINT_URL + START_PRJ_API, TOKEN, getProjectActionDTO(), String.class);
         verify(requestBuilder).newProjectAction(getUserInfo(), NAME1, getEndpointDTO());
@@ -427,21 +453,25 @@ public class ProjectServiceImplTest extends TestBase {
     }
 
     private Set<String> getGroup(String group) {
-        return Collections.singleton(group);
+	    return Collections.singleton(group);
     }
 
-    private ProjectCreateDTO newProjectCreate() {
-        return ProjectCreateDTO.builder()
-                .name(NAME1)
-                .endpoint(ENDPOINT_NAME)
-                .build();
-    }
+	private ProjectCreateDTO newProjectCreate() {
+		return ProjectCreateDTO.builder()
+				.name(NAME1)
+				.endpoint(ENDPOINT_NAME)
+				.build();
+	}
 
-    private ProjectActionDTO getProjectActionDTO() {
-        return new ProjectActionDTO(NAME1, ENDPOINT_NAME);
-    }
+	private Optional<OdahuDTO> getOdahu() {
+		return Optional.of(new OdahuDTO("name", NAME1, ENDPOINT_NAME, UserInstanceStatus.RUNNING, Collections.emptyMap()));
+	}
 
-    private UpdateProjectBudgetDTO getUpdateProjectBudgetDTO() {
-        return new UpdateProjectBudgetDTO(NAME1, 10, true);
-    }
+	private ProjectActionDTO getProjectActionDTO() {
+		return new ProjectActionDTO(NAME1, ENDPOINT_NAME);
+	}
+
+	private UpdateProjectBudgetDTO getUpdateProjectBudgetDTO() {
+		return new UpdateProjectBudgetDTO(NAME1, 10, true);
+	}
 }
\ No newline at end of file
diff --git a/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/EndpointServiceImplTest.java b/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/EndpointServiceImplTest.java
index 48bcefe..d3bf3a9 100644
--- a/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/EndpointServiceImplTest.java
+++ b/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/EndpointServiceImplTest.java
@@ -27,6 +27,7 @@ import com.epam.datalab.backendapi.domain.EndpointResourcesDTO;
 import com.epam.datalab.backendapi.domain.ProjectDTO;
 import com.epam.datalab.backendapi.domain.ProjectEndpointDTO;
 import com.epam.datalab.backendapi.resources.TestBase;
+import com.epam.datalab.backendapi.service.OdahuService;
 import com.epam.datalab.backendapi.service.ProjectService;
 import com.epam.datalab.cloud.CloudProvider;
 import com.epam.datalab.dto.UserInstanceDTO;
@@ -68,24 +69,27 @@ public class EndpointServiceImplTest extends TestBase {
     private static final String PROJECT_NAME_1 = "projectName";
     private static final String PROJECT_NAME_2 = "projectName_2";
 
-    @Mock
-    private EndpointDAO endpointDAO;
-    @Mock
-    private ProjectService projectService;
-    @Mock
-    private ExploratoryDAO exploratoryDAO;
-    @Mock
-    private RESTService provisioningService;
-    @Mock
-    private UserRoleDAO userRoleDao;
+	@Mock
+	private EndpointDAO endpointDAO;
+	@Mock
+	private ProjectService projectService;
+	@Mock
+	private ExploratoryDAO exploratoryDAO;
+	@Mock
+	private RESTService provisioningService;
+	@Mock
+	private UserRoleDAO userRoleDAO;
+	@Mock
+	private OdahuService odahuService;
 
-    @InjectMocks
-    private EndpointServiceImpl endpointService;
 
-    @Test
-    public void getEndpoints() {
-        List<EndpointDTO> endpoints = getEndpointDTOs();
-        when(endpointDAO.getEndpoints()).thenReturn(endpoints);
+	@InjectMocks
+	private EndpointServiceImpl endpointService;
+
+	@Test
+	public void getEndpoints() {
+		List<EndpointDTO> endpoints = getEndpointDTOs();
+		when(endpointDAO.getEndpoints()).thenReturn(endpoints);
 
         List<EndpointDTO> actualEndpoints = endpointService.getEndpoints();
 
@@ -147,18 +151,18 @@ public class EndpointServiceImplTest extends TestBase {
         Response response = mock(Response.class);
         when(endpointDAO.get(anyString())).thenReturn(Optional.empty());
         when(endpointDAO.getEndpointWithUrl(anyString())).thenReturn(Optional.empty());
-        when(provisioningService.get(anyString(), anyString(), any(Class.class))).thenReturn(response);
-        when(response.readEntity(any(Class.class))).thenReturn(CloudProvider.AWS);
-        when(response.getStatus()).thenReturn(HttpStatus.SC_OK);
-
-        endpointService.create(getUserInfo(), ENDPOINT_NAME, getEndpointDTO());
-
-        verify(endpointDAO).get(ENDPOINT_NAME);
-        verify(endpointDAO).getEndpointWithUrl(ENDPOINT_URL);
-        verify(provisioningService).get(ENDPOINT_URL + HEALTH_CHECK, TOKEN, Response.class);
-        verify(endpointDAO).create(getEndpointDTO());
-        verify(userRoleDao).updateMissingRoles(CloudProvider.AWS);
-        verifyNoMoreInteractions(endpointDAO, provisioningService, userRoleDao);
+	    when(provisioningService.get(anyString(), anyString(), any(Class.class))).thenReturn(response);
+	    when(response.readEntity(any(Class.class))).thenReturn(CloudProvider.AWS);
+	    when(response.getStatus()).thenReturn(HttpStatus.SC_OK);
+
+	    endpointService.create(getUserInfo(), ENDPOINT_NAME, getEndpointDTO());
+
+	    verify(endpointDAO).get(ENDPOINT_NAME);
+	    verify(endpointDAO).getEndpointWithUrl(ENDPOINT_URL);
+	    verify(provisioningService).get(ENDPOINT_URL + HEALTH_CHECK, TOKEN, Response.class);
+	    verify(endpointDAO).create(getEndpointDTO());
+	    verify(userRoleDAO).updateMissingRoles(CloudProvider.AWS);
+	    verifyNoMoreInteractions(endpointDAO, provisioningService, userRoleDAO);
     }
 
     @Test(expected = ResourceConflictException.class)
@@ -232,23 +236,26 @@ public class EndpointServiceImplTest extends TestBase {
     public void remove() {
         List<ProjectDTO> projectDTOs = getProjectDTOs();
         List<EndpointDTO> endpointDTOs = getEndpointDTOs();
-        when(endpointDAO.get(anyString())).thenReturn(Optional.of(getEndpointDTO()));
-        when(projectService.getProjectsByEndpoint(anyString())).thenReturn(projectDTOs);
-        when(projectService.checkExploratoriesAndComputationalProgress(anyString(), anyListOf(String.class))).thenReturn(Boolean.TRUE);
-        when(endpointDAO.getEndpoints()).thenReturn(endpointDTOs);
+	    when(endpointDAO.get(anyString())).thenReturn(Optional.of(getEndpointDTO()));
+	    when(projectService.getProjectsByEndpoint(anyString())).thenReturn(projectDTOs);
+	    when(odahuService.inProgress(anyString(), anyString())).thenReturn(Boolean.FALSE);
+	    when(projectService.checkExploratoriesAndComputationalProgress(anyString(), anyListOf(String.class))).thenReturn(Boolean.TRUE);
+	    when(endpointDAO.getEndpoints()).thenReturn(endpointDTOs);
 
-        endpointService.remove(getUserInfo(), ENDPOINT_NAME);
+	    endpointService.remove(getUserInfo(), ENDPOINT_NAME);
 
-        verify(endpointDAO).get(ENDPOINT_NAME);
-        verify(projectService).getProjectsByEndpoint(ENDPOINT_NAME);
-        verify(projectService).checkExploratoriesAndComputationalProgress(PROJECT_NAME_1, Collections.singletonList(ENDPOINT_NAME));
-        verify(projectService).checkExploratoriesAndComputationalProgress(PROJECT_NAME_2, Collections.singletonList(ENDPOINT_NAME));
-        verify(projectService).terminateEndpoint(getUserInfo(), ENDPOINT_NAME, PROJECT_NAME_1);
-        verify(projectService).terminateEndpoint(getUserInfo(), ENDPOINT_NAME, PROJECT_NAME_2);
-        verify(endpointDAO).remove(ENDPOINT_NAME);
-        verify(endpointDAO).getEndpoints();
-        verify(userRoleDao).removeUnnecessaryRoles(CloudProvider.AWS, Arrays.asList(CloudProvider.AWS, CloudProvider.GCP));
-        verifyNoMoreInteractions(endpointDAO, projectService, userRoleDao);
+	    verify(endpointDAO).get(ENDPOINT_NAME);
+	    verify(projectService).getProjectsByEndpoint(ENDPOINT_NAME);
+	    verify(odahuService).inProgress(PROJECT_NAME_1, ENDPOINT_NAME);
+	    verify(odahuService).inProgress(PROJECT_NAME_2, ENDPOINT_NAME);
+	    verify(projectService).checkExploratoriesAndComputationalProgress(PROJECT_NAME_1, Collections.singletonList(ENDPOINT_NAME));
+	    verify(projectService).checkExploratoriesAndComputationalProgress(PROJECT_NAME_2, Collections.singletonList(ENDPOINT_NAME));
+	    verify(projectService).terminateEndpoint(getUserInfo(), ENDPOINT_NAME, PROJECT_NAME_1);
+	    verify(projectService).terminateEndpoint(getUserInfo(), ENDPOINT_NAME, PROJECT_NAME_2);
+	    verify(endpointDAO).remove(ENDPOINT_NAME);
+	    verify(endpointDAO).getEndpoints();
+	    verify(userRoleDAO).removeUnnecessaryRoles(CloudProvider.AWS, Arrays.asList(CloudProvider.AWS, CloudProvider.GCP));
+	    verifyNoMoreInteractions(endpointDAO, projectService, userRoleDAO, odahuService);
     }
 
     @Test(expected = ResourceNotFoundException.class)
diff --git a/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/InfrastructureInfoServiceImplTest.java b/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/InfrastructureInfoServiceImplTest.java
index 85f6380..c9da975 100644
--- a/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/InfrastructureInfoServiceImplTest.java
+++ b/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/InfrastructureInfoServiceImplTest.java
@@ -260,12 +260,13 @@ public class InfrastructureInfoServiceImplTest extends TestBase {
     private List<ProjectInfrastructureInfo> getProjectInfrastructureInfo() {
         List<ProjectInfrastructureInfo> objects = new ArrayList<>();
         objects.add(ProjectInfrastructureInfo.builder()
-                .project(PROJECT)
-                .billingQuoteUsed(10)
-                .shared(Collections.singletonMap(ENDPOINT_NAME, Collections.emptyMap()))
-                .exploratory(getUserInstanceDTOs())
-                .exploratoryBilling(Collections.singletonList(getReport()))
-                .endpoints(Collections.singletonList(getEndpointDTO()))
+		        .project(PROJECT)
+		        .billingQuoteUsed(10)
+		        .shared(Collections.singletonMap(ENDPOINT_NAME, Collections.emptyMap()))
+		        .exploratory(getUserInstanceDTOs())
+		        .exploratoryBilling(Collections.singletonList(getReport()))
+		        .endpoints(Collections.singletonList(getEndpointDTO()))
+		        .odahu(Collections.emptyList())
                 .build());
         return objects;
     }
@@ -273,12 +274,13 @@ public class InfrastructureInfoServiceImplTest extends TestBase {
     private List<ProjectInfrastructureInfo> getAwsProjectInfrastructureInfo() {
         List<ProjectInfrastructureInfo> objects = new ArrayList<>();
         objects.add(ProjectInfrastructureInfo.builder()
-                .project(PROJECT)
-                .billingQuoteUsed(10)
-                .shared(Collections.singletonMap(ENDPOINT_NAME, getAwsEdgeInfo()))
-                .exploratory(getUserInstanceDTOs())
-                .exploratoryBilling(Collections.singletonList(getReport()))
-                .endpoints(Collections.singletonList(getEndpointDTO()))
+		        .project(PROJECT)
+		        .billingQuoteUsed(10)
+		        .shared(Collections.singletonMap(ENDPOINT_NAME, getAwsEdgeInfo()))
+		        .exploratory(getUserInstanceDTOs())
+		        .exploratoryBilling(Collections.singletonList(getReport()))
+		        .endpoints(Collections.singletonList(getEndpointDTO()))
+		        .odahu(Collections.emptyList())
                 .build());
         return objects;
     }
@@ -286,12 +288,13 @@ public class InfrastructureInfoServiceImplTest extends TestBase {
     private List<ProjectInfrastructureInfo> getAzureProjectInfrastructureInfo() {
         List<ProjectInfrastructureInfo> objects = new ArrayList<>();
         objects.add(ProjectInfrastructureInfo.builder()
-                .project(PROJECT)
-                .billingQuoteUsed(10)
-                .shared(Collections.singletonMap(ENDPOINT_NAME, getAzureEdgeInfo()))
-                .exploratory(getUserInstanceDTOs())
-                .exploratoryBilling(Collections.singletonList(getReport()))
-                .endpoints(Collections.singletonList(getEndpointDTO()))
+		        .project(PROJECT)
+		        .billingQuoteUsed(10)
+		        .shared(Collections.singletonMap(ENDPOINT_NAME, getAzureEdgeInfo()))
+		        .exploratory(getUserInstanceDTOs())
+		        .exploratoryBilling(Collections.singletonList(getReport()))
+		        .endpoints(Collections.singletonList(getEndpointDTO()))
+		        .odahu(Collections.emptyList())
                 .build());
         return objects;
     }
@@ -299,12 +302,13 @@ public class InfrastructureInfoServiceImplTest extends TestBase {
     private List<ProjectInfrastructureInfo> getGcpProjectInfrastructureInfo() {
         List<ProjectInfrastructureInfo> objects = new ArrayList<>();
         objects.add(ProjectInfrastructureInfo.builder()
-                .project(PROJECT)
-                .billingQuoteUsed(10)
-                .shared(Collections.singletonMap(ENDPOINT_NAME, getGcpEdgeInfo()))
-                .exploratory(getUserInstanceDTOs())
-                .exploratoryBilling(Collections.singletonList(getReport()))
-                .endpoints(Collections.singletonList(getEndpointDTO()))
+		        .project(PROJECT)
+		        .billingQuoteUsed(10)
+		        .shared(Collections.singletonMap(ENDPOINT_NAME, getGcpEdgeInfo()))
+		        .exploratory(getUserInstanceDTOs())
+		        .exploratoryBilling(Collections.singletonList(getReport()))
+		        .endpoints(Collections.singletonList(getEndpointDTO()))
+		        .odahu(Collections.emptyList())
                 .build());
         return objects;
     }
diff --git a/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/OdahuServiceImplTest.java b/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/OdahuServiceImplTest.java
new file mode 100644
index 0000000..c5dff3b
--- /dev/null
+++ b/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/OdahuServiceImplTest.java
@@ -0,0 +1,250 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.epam.datalab.backendapi.service.impl;
+
+import com.epam.datalab.auth.UserInfo;
+import com.epam.datalab.backendapi.dao.OdahuDAO;
+import com.epam.datalab.backendapi.domain.BudgetDTO;
+import com.epam.datalab.backendapi.domain.EndpointDTO;
+import com.epam.datalab.backendapi.domain.OdahuCreateDTO;
+import com.epam.datalab.backendapi.domain.OdahuDTO;
+import com.epam.datalab.backendapi.domain.OdahuFieldsDTO;
+import com.epam.datalab.backendapi.domain.ProjectDTO;
+import com.epam.datalab.backendapi.domain.ProjectEndpointDTO;
+import com.epam.datalab.backendapi.domain.RequestId;
+import com.epam.datalab.backendapi.service.EndpointService;
+import com.epam.datalab.backendapi.service.ProjectService;
+import com.epam.datalab.backendapi.util.RequestBuilder;
+import com.epam.datalab.cloud.CloudProvider;
+import com.epam.datalab.dto.UserInstanceStatus;
+import com.epam.datalab.dto.base.edge.EdgeInfo;
+import com.epam.datalab.exceptions.DatalabException;
+import com.epam.datalab.exceptions.ResourceConflictException;
+import com.epam.datalab.rest.client.RESTService;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Collections.singletonList;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class OdahuServiceImplTest {
+
+	private static final String USER = "testUser";
+	private static final String TOKEN = "testToken";
+	private static final String PROJECT = "testProject";
+	private static final String ENDPOINT_URL = "https://localhsot:8080/";
+	private static final String ENDPOINT_NAME = "testEndpoint";
+	private static final String tag = "testTag";
+	private static final String uuid = "34dsr324";
+	private static final String ODAHU_NAME = "odahuTest";
+	private static UserInfo userInfo;
+
+	@Mock
+	private OdahuDAO odahuDAO;
+	@Mock
+	private ProjectService projectService;
+	@Mock
+	private EndpointService endpointService;
+	@Mock
+	private RequestId requestId;
+	@Mock
+	private RESTService provisioningService;
+	@Mock
+	private RequestBuilder requestBuilder;
+	@InjectMocks
+	private OdahuServiceImpl odahuService;
+
+	@BeforeClass
+	public static void setUp() {
+		userInfo = new UserInfo(USER, TOKEN);
+	}
+
+	@Test
+	public void findOdahuTest() {
+		List<OdahuDTO> odahuDTOList = odahuService.findOdahu();
+		assertNotNull(odahuDTOList);
+
+		verify(odahuDAO).findOdahuClusters();
+	}
+
+	@Test
+	public void createTest() {
+		EndpointDTO endpointDTO = getEndpointDTO();
+		ProjectDTO projectDTO = getProjectDTO(UserInstanceStatus.RUNNING);
+		OdahuCreateDTO odahuCreateDTO = getOdahuCreateDTO();
+
+		when(projectService.get(anyString())).thenReturn(projectDTO);
+		when(odahuDAO.create(new OdahuDTO(odahuCreateDTO.getName(), odahuCreateDTO.getProject(),
+				odahuCreateDTO.getEndpoint(), UserInstanceStatus.CREATING, getTags()))).thenReturn(true);
+		when(endpointService.get(odahuCreateDTO.getEndpoint())).thenReturn(endpointDTO);
+		when(requestId.put(userInfo.getName(), uuid)).thenReturn(uuid);
+
+		odahuService.create(PROJECT, odahuCreateDTO, userInfo);
+
+		verify(odahuDAO).findOdahuClusters(odahuCreateDTO.getProject(), odahuCreateDTO.getEndpoint());
+		verify(odahuDAO).create(new OdahuDTO(odahuCreateDTO.getName(), odahuCreateDTO.getProject(),
+				odahuCreateDTO.getEndpoint(), UserInstanceStatus.CREATING, getTags()));
+		verify(endpointService).get(odahuCreateDTO.getEndpoint());
+		verify(projectService).get(PROJECT);
+		verify(provisioningService).post(ENDPOINT_URL + "infrastructure/odahu", userInfo.getAccessToken(),
+				requestBuilder.newOdahuCreate(userInfo.getName(), odahuCreateDTO, projectDTO, endpointDTO), String.class);
+		verifyNoMoreInteractions(odahuDAO, provisioningService, endpointService, projectService);
+	}
+
+	@Test(expected = ResourceConflictException.class)
+	public void createIfClusterActive() {
+		OdahuCreateDTO odahuCreateDTO = getOdahuCreateDTO();
+		OdahuDTO failedOdahu = getOdahuDTO(UserInstanceStatus.RUNNING);
+		List<OdahuDTO> runningOdahuList = singletonList(failedOdahu);
+		when(odahuDAO.findOdahuClusters(anyString(), anyString())).thenReturn(runningOdahuList);
+
+		odahuService.create(PROJECT, odahuCreateDTO, userInfo);
+		verify(odahuDAO).findOdahuClusters(odahuCreateDTO.getProject(), odahuCreateDTO.getEndpoint());
+		verifyNoMoreInteractions(odahuDAO);
+	}
+
+	@Test(expected = DatalabException.class)
+	public void createIfDBissue() {
+		EndpointDTO endpointDTO = getEndpointDTO();
+		ProjectDTO projectDTO = getProjectDTO(UserInstanceStatus.RUNNING);
+		OdahuCreateDTO odahuCreateDTO = getOdahuCreateDTO();
+
+		when(projectService.get(anyString())).thenReturn(projectDTO);
+		when(odahuDAO.create(new OdahuDTO(odahuCreateDTO.getName(), odahuCreateDTO.getProject(),
+				odahuCreateDTO.getEndpoint(), UserInstanceStatus.CREATING, getTags()))).thenReturn(false);
+		when(endpointService.get(anyString())).thenReturn(endpointDTO);
+		when(requestId.put(userInfo.getName(), uuid)).thenReturn(uuid);
+
+		odahuService.create(PROJECT, odahuCreateDTO, userInfo);
+
+		verify(odahuDAO).findOdahuClusters(odahuCreateDTO.getProject(), odahuCreateDTO.getEndpoint());
+		verify(odahuDAO).create(new OdahuDTO(odahuCreateDTO.getName(), odahuCreateDTO.getProject(),
+				odahuCreateDTO.getEndpoint(), UserInstanceStatus.CREATING, getTags()));
+		verify(endpointService).get(odahuCreateDTO.getEndpoint());
+		verify(projectService).get(PROJECT);
+		verify(provisioningService).post(ENDPOINT_URL + "infrastructure/odahu", userInfo.getAccessToken(),
+				requestBuilder.newOdahuCreate(userInfo.getName(), odahuCreateDTO, projectDTO, endpointDTO), String.class);
+		verifyNoMoreInteractions(odahuDAO, provisioningService, endpointService, projectService);
+	}
+
+	@Test
+	public void startTest() {
+		when(endpointService.get(anyString())).thenReturn(getEndpointDTO());
+
+		odahuService.start(ODAHU_NAME, PROJECT, ENDPOINT_URL, userInfo);
+
+		verify(endpointService).get(ENDPOINT_URL);
+		verify(odahuDAO).updateStatus(ODAHU_NAME, PROJECT, ENDPOINT_URL, UserInstanceStatus.STARTING);
+		verify(odahuDAO).getFields(ODAHU_NAME, PROJECT, ENDPOINT_URL);
+		verify(provisioningService).post(ENDPOINT_URL + "infrastructure/odahu/start", userInfo.getAccessToken(),
+				requestBuilder.newOdahuAction(userInfo.getName(), ODAHU_NAME, getProjectDTO(UserInstanceStatus.STARTING), getEndpointDTO(), new OdahuFieldsDTO()), String.class);
+		verifyNoMoreInteractions(endpointService, odahuDAO);
+	}
+
+	@Test
+	public void stopTest() {
+		when(endpointService.get(anyString())).thenReturn(getEndpointDTO());
+
+		odahuService.stop(ODAHU_NAME, PROJECT, ENDPOINT_URL, userInfo);
+
+		verify(endpointService).get(ENDPOINT_URL);
+		verify(odahuDAO).updateStatus(ODAHU_NAME, PROJECT, ENDPOINT_URL, UserInstanceStatus.STOPPING);
+		verify(odahuDAO).getFields(ODAHU_NAME, PROJECT, ENDPOINT_URL);
+		verify(provisioningService).post(ENDPOINT_URL + "infrastructure/odahu/stop", userInfo.getAccessToken(),
+				requestBuilder.newOdahuAction(userInfo.getName(), ODAHU_NAME, getProjectDTO(UserInstanceStatus.STOPPING), getEndpointDTO(), new OdahuFieldsDTO()), String.class);
+		verifyNoMoreInteractions(endpointService, odahuDAO, provisioningService);
+	}
+
+	@Test
+	public void terminateTest() {
+		List<OdahuDTO> odahuDTOS = Arrays.asList(
+				getOdahuDTO(UserInstanceStatus.RUNNING),
+				getOdahuDTO(UserInstanceStatus.FAILED));
+		when(odahuDAO.findOdahuClusters(anyString(), anyString())).thenReturn(odahuDTOS);
+		when(endpointService.get(anyString())).thenReturn(getEndpointDTO());
+		when(odahuDAO.getFields(anyString(), anyString(), anyString())).thenReturn(new OdahuFieldsDTO());
+
+		odahuService.terminate(ODAHU_NAME, PROJECT, ENDPOINT_URL, userInfo);
+
+		verify(odahuDAO).findOdahuClusters(PROJECT, ENDPOINT_URL);
+		verify(endpointService).get(ENDPOINT_URL);
+		verify(odahuDAO).updateStatus(ODAHU_NAME, PROJECT, ENDPOINT_URL, UserInstanceStatus.TERMINATING);
+		verify(odahuDAO).getFields(ODAHU_NAME, PROJECT, ENDPOINT_URL);
+		verify(provisioningService).post(ENDPOINT_URL + "infrastructure/odahu/terminate", userInfo.getAccessToken(),
+				requestBuilder.newOdahuAction(userInfo.getName(), PROJECT, getProjectDTO(UserInstanceStatus.TERMINATING), getEndpointDTO(), new OdahuFieldsDTO()), String.class);
+		verifyNoMoreInteractions(odahuDAO, endpointService, provisioningService);
+	}
+
+	@Test(expected = DatalabException.class)
+	public void terminateIfIncorrectStatusTest() {
+		List<OdahuDTO> odahuDTOS = Arrays.asList(
+				getOdahuDTO(UserInstanceStatus.TERMINATING),
+				getOdahuDTO(UserInstanceStatus.FAILED));
+		when(odahuDAO.findOdahuClusters(anyString(), anyString())).thenReturn(odahuDTOS);
+
+		odahuService.terminate(ODAHU_NAME, PROJECT, ENDPOINT_URL, userInfo);
+
+		verify(odahuDAO).findOdahuClusters(PROJECT, ENDPOINT_URL);
+		verifyNoMoreInteractions(odahuDAO, endpointService, provisioningService);
+	}
+
+	private Map<String, String> getTags() {
+		Map<String, String> tags = new HashMap<>();
+		tags.put("custom_tag", getOdahuCreateDTO().getCustomTag());
+		tags.put("project_tag", getOdahuCreateDTO().getProject());
+		tags.put("endpoint_tag", getOdahuCreateDTO().getEndpoint());
+		return tags;
+	}
+
+	private EndpointDTO getEndpointDTO() {
+		return new EndpointDTO(ENDPOINT_NAME, ENDPOINT_URL, "testAccount", tag, EndpointDTO.EndpointStatus.ACTIVE, CloudProvider.GCP);
+	}
+
+	private ProjectDTO getProjectDTO(UserInstanceStatus instanceStatus) {
+		return new ProjectDTO(PROJECT,
+				Collections.emptySet(),
+				"ssh-testKey\n", tag, new BudgetDTO(200, Boolean.FALSE),
+				singletonList(new ProjectEndpointDTO(ENDPOINT_NAME, instanceStatus, new EdgeInfo())),
+				true);
+	}
+
+	private OdahuDTO getOdahuDTO(UserInstanceStatus instanceStatus) {
+		return new OdahuDTO(ODAHU_NAME, PROJECT, ENDPOINT_NAME, instanceStatus, getTags());
+	}
+
+	private OdahuCreateDTO getOdahuCreateDTO() {
+		return new OdahuCreateDTO(ODAHU_NAME, PROJECT, ENDPOINT_URL, tag);
+	}
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datalab.apache.org
For additional commands, e-mail: commits-help@datalab.apache.org


[incubator-datalab] 02/05: merge odahu (provisioning)

Posted by of...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ofuks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/incubator-datalab.git

commit 9499a58d84baa90b22295c3c7421663a6e6bcea0
Author: Oleh Fuks <ol...@gmail.com>
AuthorDate: Fri Nov 20 16:01:46 2020 +0200

    merge odahu (provisioning)
---
 .../src/main/java/com/epam/datalab/model/ResourceEnum.java             | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/services/datalab-model/src/main/java/com/epam/datalab/model/ResourceEnum.java b/services/datalab-model/src/main/java/com/epam/datalab/model/ResourceEnum.java
index 0cf7832..ce6017c 100644
--- a/services/datalab-model/src/main/java/com/epam/datalab/model/ResourceEnum.java
+++ b/services/datalab-model/src/main/java/com/epam/datalab/model/ResourceEnum.java
@@ -22,7 +22,8 @@ import com.fasterxml.jackson.annotation.JsonValue;
 
 public enum ResourceEnum {
     EDGE_NODE("edge node"),
-    NOTEBOOK("notebook");
+    NOTEBOOK("notebook"),
+    ODAHU("odahu");
 
     private String name;
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datalab.apache.org
For additional commands, e-mail: commits-help@datalab.apache.org


[incubator-datalab] 05/05: merge odahu (self-service)

Posted by of...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ofuks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/incubator-datalab.git

commit 3db6fb18de8527857de43e7b11145a64d3322e35
Author: Oleh Fuks <ol...@gmail.com>
AuthorDate: Tue Nov 24 19:50:13 2020 +0200

    merge odahu (self-service)
---
 .../epam/datalab/backendapi/dao/OdahuDAOImpl.java  |  2 +-
 .../backendapi/service/impl/OdahuServiceImpl.java  | 35 +++++++++++++---------
 .../src/main/resources/mongo/aws/mongo_roles.json  |  3 +-
 .../main/resources/mongo/azure/mongo_roles.json    |  3 +-
 .../src/main/resources/mongo/gcp/mongo_roles.json  |  3 +-
 5 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/OdahuDAOImpl.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/OdahuDAOImpl.java
index 3f57184..9c9e169 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/OdahuDAOImpl.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/OdahuDAOImpl.java
@@ -85,7 +85,7 @@ public class OdahuDAOImpl extends BaseDAO implements OdahuDAO {
         return projectDTO.map(p -> p.getOdahu().stream()
                 .filter(odahu -> project.equals(odahu.getProject()) && endpoint.equals(odahu.getEndpoint()))
                 .collect(Collectors.toList()))
-                .orElseThrow(() -> new DatalabException("Unable to find the odahu clusters in the " + project));
+                .orElse(new ArrayList<>());
     }
 
     @Override
diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/OdahuServiceImpl.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/OdahuServiceImpl.java
index d69b8ca..a581811 100644
--- a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/OdahuServiceImpl.java
+++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/OdahuServiceImpl.java
@@ -50,6 +50,15 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 
+import static com.epam.datalab.dto.UserInstanceStatus.CONFIGURING;
+import static com.epam.datalab.dto.UserInstanceStatus.CREATING;
+import static com.epam.datalab.dto.UserInstanceStatus.FAILED;
+import static com.epam.datalab.dto.UserInstanceStatus.RUNNING;
+import static com.epam.datalab.dto.UserInstanceStatus.STARTING;
+import static com.epam.datalab.dto.UserInstanceStatus.STOPPED;
+import static com.epam.datalab.dto.UserInstanceStatus.STOPPING;
+import static com.epam.datalab.dto.UserInstanceStatus.TERMINATING;
+
 @Slf4j
 public class OdahuServiceImpl implements OdahuService {
 
@@ -92,15 +101,15 @@ public class OdahuServiceImpl implements OdahuService {
 	@Override
 	public void create(@Project String project, OdahuCreateDTO odahuCreateDTO, UserInfo user) {
 		log.info("Trying to create odahu cluster for project: " + project);
-		boolean activeCluster = odahuDAO.findOdahuClusters(odahuCreateDTO.getProject(), odahuCreateDTO.getEndpoint()).stream()
-				.noneMatch(o -> !o.getStatus().equals(UserInstanceStatus.FAILED) && !o.getStatus().equals(UserInstanceStatus.TERMINATED));
-		if (!activeCluster) {
+		final boolean activeCluster = odahuDAO.findOdahuClusters(odahuCreateDTO.getProject(), odahuCreateDTO.getEndpoint()).stream()
+				.anyMatch(o -> Arrays.asList(CREATING, RUNNING, STARTING, STOPPING, STOPPED, CONFIGURING, TERMINATING).contains(o.getStatus()));
+		if (activeCluster) {
 			throw new ResourceConflictException(String.format("Odahu cluster already exist in system for project %s " +
 					"and endpoint %s", odahuCreateDTO.getProject(), odahuCreateDTO.getEndpoint()));
 		}
 		ProjectDTO projectDTO = projectService.get(project);
 		boolean isAdded = odahuDAO.create(new OdahuDTO(odahuCreateDTO.getName(), odahuCreateDTO.getProject(),
-				odahuCreateDTO.getEndpoint(), UserInstanceStatus.CREATING, getTags(odahuCreateDTO)));
+				odahuCreateDTO.getEndpoint(), CREATING, getTags(odahuCreateDTO)));
 		if (isAdded) {
 			String url = null;
 			EndpointDTO endpointDTO = endpointService.get(odahuCreateDTO.getEndpoint());
@@ -112,8 +121,7 @@ public class OdahuServiceImpl implements OdahuService {
 				requestId.put(user.getName(), uuid);
 			} catch (Exception e) {
 				log.error("Can not perform {} due to: {}, {}", url, e.getMessage(), e);
-				odahuDAO.updateStatus(odahuCreateDTO.getName(), odahuCreateDTO.getProject(), odahuCreateDTO.getEndpoint(),
-						UserInstanceStatus.FAILED);
+				odahuDAO.updateStatus(odahuCreateDTO.getName(), odahuCreateDTO.getProject(), odahuCreateDTO.getEndpoint(), FAILED);
 			}
 		} else {
 			throw new DatalabException(String.format("The odahu fields of the %s can not be updated in DB.", project));
@@ -124,14 +132,14 @@ public class OdahuServiceImpl implements OdahuService {
 	@Override
 	public void start(String name, @Project String project, String endpoint, UserInfo user) {
 		log.info("Trying to start odahu cluster for project: " + project);
-		odahuDAO.updateStatus(name, project, endpoint, UserInstanceStatus.STARTING);
+		odahuDAO.updateStatus(name, project, endpoint, STARTING);
 		actionOnCloud(user, START_ODAHU_API, name, project, endpoint);
 	}
 
 	@Override
 	public void stop(String name, String project, String endpoint, UserInfo user) {
 		log.info("Trying to stop odahu cluster for project: " + project);
-		odahuDAO.updateStatus(name, project, endpoint, UserInstanceStatus.STOPPING);
+		odahuDAO.updateStatus(name, project, endpoint, STOPPING);
 		actionOnCloud(user, STOP_ODAHU_API, name, project, endpoint);
 	}
 
@@ -140,10 +148,10 @@ public class OdahuServiceImpl implements OdahuService {
 		log.info("Trying to terminate odahu cluster for project: " + project);
 		odahuDAO.findOdahuClusters(project, endpoint).stream()
 				.filter(odahuDTO -> name.equals(odahuDTO.getName())
-						&& !odahuDTO.getStatus().equals(UserInstanceStatus.FAILED))
+						&& !odahuDTO.getStatus().equals(FAILED))
 				.forEach(odahuDTO -> {
-					if (UserInstanceStatus.RUNNING == odahuDTO.getStatus()) {
-						odahuDAO.updateStatus(name, project, endpoint, UserInstanceStatus.TERMINATING);
+					if (RUNNING == odahuDTO.getStatus()) {
+						odahuDAO.updateStatus(name, project, endpoint, TERMINATING);
 						actionOnCloud(user, TERMINATE_ODAHU_API, name, project, endpoint);
 					} else {
 						log.error("Cannot terminate odahu cluster {}", odahuDTO);
@@ -164,8 +172,7 @@ public class OdahuServiceImpl implements OdahuService {
 	@Override
 	public boolean inProgress(String project, String endpoint) {
 		return get(project, endpoint)
-				.filter(odahu -> Arrays.asList(UserInstanceStatus.CREATING, UserInstanceStatus.STARTING,
-						UserInstanceStatus.STOPPING, UserInstanceStatus.TERMINATING).contains(odahu.getStatus()))
+				.filter(odahu -> Arrays.asList(CREATING, STARTING, STOPPING, TERMINATING).contains(odahu.getStatus()))
 				.isPresent();
 	}
 
@@ -182,7 +189,7 @@ public class OdahuServiceImpl implements OdahuService {
 			requestId.put(user.getName(), uuid);
 		} catch (Exception e) {
 			log.error("Can not perform {} due to: {}, {}", url, e.getMessage(), e);
-			odahuDAO.updateStatus(name, project, project, UserInstanceStatus.FAILED);
+			odahuDAO.updateStatus(name, project, project, FAILED);
 		}
 	}
 
diff --git a/services/self-service/src/main/resources/mongo/aws/mongo_roles.json b/services/self-service/src/main/resources/mongo/aws/mongo_roles.json
index b4eddee..41737c9 100644
--- a/services/self-service/src/main/resources/mongo/aws/mongo_roles.json
+++ b/services/self-service/src/main/resources/mongo/aws/mongo_roles.json
@@ -403,7 +403,8 @@
       "/user/settings",
       "/api/project",
       "/api/project/create",
-      "/api/endpoint"
+      "/api/endpoint",
+      "/api/odahu"
     ],
     "groups": [
       "$anyuser"
diff --git a/services/self-service/src/main/resources/mongo/azure/mongo_roles.json b/services/self-service/src/main/resources/mongo/azure/mongo_roles.json
index 8b69663..bedbec8 100644
--- a/services/self-service/src/main/resources/mongo/azure/mongo_roles.json
+++ b/services/self-service/src/main/resources/mongo/azure/mongo_roles.json
@@ -343,7 +343,8 @@
       "/user/settings",
       "/api/project",
       "/api/project/create",
-      "/api/endpoint"
+      "/api/endpoint",
+      "/api/odahu"
     ],
     "groups": [
       "$anyuser"
diff --git a/services/self-service/src/main/resources/mongo/gcp/mongo_roles.json b/services/self-service/src/main/resources/mongo/gcp/mongo_roles.json
index eca5101..5de6beb 100644
--- a/services/self-service/src/main/resources/mongo/gcp/mongo_roles.json
+++ b/services/self-service/src/main/resources/mongo/gcp/mongo_roles.json
@@ -379,7 +379,8 @@
       "/user/settings",
       "/api/project",
       "/api/project/create",
-      "/api/endpoint"
+      "/api/endpoint",
+      "/api/odahu"
     ],
     "groups": [
       "$anyuser"


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datalab.apache.org
For additional commands, e-mail: commits-help@datalab.apache.org


[incubator-datalab] 01/05: merge odahu (provisioning)

Posted by of...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ofuks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/incubator-datalab.git

commit 43b7e1881e98774663243cee456f89d1630986ee
Author: Oleh Fuks <ol...@gmail.com>
AuthorDate: Fri Nov 20 13:05:42 2020 +0200

    merge odahu (provisioning)
---
 .../epam/datalab/dto/base/odahu/OdahuResult.java   |  48 +++++++++
 .../com/epam/datalab/dto/odahu/ActionOdahuDTO.java |  46 +++++++++
 .../com/epam/datalab/dto/odahu/CreateOdahuDTO.java |  38 +++++++
 .../backendapi/ProvisioningServiceApplication.java |   2 +
 .../core/commands/CommandExecutorMockAsync.java    |   2 +-
 .../response/handlers/OdahuCallbackHandler.java    |  94 +++++++++++++++++
 .../backendapi/modules/ProductionModule.java       |   3 +
 .../backendapi/modules/ProvisioningDevModule.java  |   3 +
 .../backendapi/resources/OdahuResource.java        |  70 +++++++++++++
 .../datalab/backendapi/service/OdahuService.java   |  34 ++++++
 .../backendapi/service/impl/OdahuServiceImpl.java  | 115 +++++++++++++++++++++
 11 files changed, 454 insertions(+), 1 deletion(-)

diff --git a/services/datalab-model/src/main/java/com/epam/datalab/dto/base/odahu/OdahuResult.java b/services/datalab-model/src/main/java/com/epam/datalab/dto/base/odahu/OdahuResult.java
new file mode 100644
index 0000000..0241929
--- /dev/null
+++ b/services/datalab-model/src/main/java/com/epam/datalab/dto/base/odahu/OdahuResult.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.epam.datalab.dto.base.odahu;
+
+import com.epam.datalab.dto.ResourceURL;
+import com.epam.datalab.dto.StatusBaseDTO;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class OdahuResult extends StatusBaseDTO<OdahuResult> {
+    private String name;
+    @JsonProperty("project_name")
+    private String projectName;
+    @JsonProperty("endpoint_name")
+    private String endpointName;
+    @JsonProperty("grafana_admin")
+    private String grafanaAdmin;
+    @JsonProperty("grafana_pass")
+    private String grafanaPassword;
+    @JsonProperty("oauth_cookie_secret")
+    private String oauthCookieSecret;
+    @JsonProperty("odahuflow_connection_decrypt_token")
+    private String decryptToken;
+    @JsonProperty("odahu_urls")
+    private List<ResourceURL> resourceUrls;
+}
diff --git a/services/datalab-model/src/main/java/com/epam/datalab/dto/odahu/ActionOdahuDTO.java b/services/datalab-model/src/main/java/com/epam/datalab/dto/odahu/ActionOdahuDTO.java
new file mode 100644
index 0000000..8ccdb55
--- /dev/null
+++ b/services/datalab-model/src/main/java/com/epam/datalab/dto/odahu/ActionOdahuDTO.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.epam.datalab.dto.odahu;
+
+import com.epam.datalab.dto.ResourceBaseDTO;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class ActionOdahuDTO extends ResourceBaseDTO<ActionOdahuDTO> {
+    @JsonProperty("odahu_cluster_name")
+    private final String name;
+    @JsonProperty("project_name")
+    private final String project;
+    @JsonProperty("endpoint_name")
+    private final String endpoint;
+    @JsonProperty("ssh_key")
+    private final String key;
+    @JsonProperty("grafana_admin")
+    private String grafanaAdmin;
+    @JsonProperty("grafana_pass")
+    private String grafanaPassword;
+    @JsonProperty("oauth_cookie_secret")
+    private String oauthCookieSecret;
+    @JsonProperty("odahuflow_connection_decrypt_token")
+    private String decryptToken;
+}
diff --git a/services/datalab-model/src/main/java/com/epam/datalab/dto/odahu/CreateOdahuDTO.java b/services/datalab-model/src/main/java/com/epam/datalab/dto/odahu/CreateOdahuDTO.java
new file mode 100644
index 0000000..2fa47bf
--- /dev/null
+++ b/services/datalab-model/src/main/java/com/epam/datalab/dto/odahu/CreateOdahuDTO.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.epam.datalab.dto.odahu;
+
+import com.epam.datalab.dto.ResourceBaseDTO;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class CreateOdahuDTO extends ResourceBaseDTO<CreateOdahuDTO> {
+    @JsonProperty("odahu_cluster_name")
+    private final String name;
+    @JsonProperty("project_name")
+    private final String project;
+    @JsonProperty("endpoint_name")
+    private final String endpoint;
+    @JsonProperty("ssh_key")
+    private final String key;
+}
diff --git a/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/ProvisioningServiceApplication.java b/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/ProvisioningServiceApplication.java
index fa24201..76a1cdb 100644
--- a/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/ProvisioningServiceApplication.java
+++ b/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/ProvisioningServiceApplication.java
@@ -34,6 +34,7 @@ import com.epam.datalab.backendapi.resources.GitExploratoryResource;
 import com.epam.datalab.backendapi.resources.ImageResource;
 import com.epam.datalab.backendapi.resources.InfrastructureResource;
 import com.epam.datalab.backendapi.resources.LibraryResource;
+import com.epam.datalab.backendapi.resources.OdahuResource;
 import com.epam.datalab.backendapi.resources.ProjectResource;
 import com.epam.datalab.backendapi.resources.ProvisioningHealthCheckResource;
 import com.epam.datalab.backendapi.resources.base.KeyResource;
@@ -155,6 +156,7 @@ public class ProvisioningServiceApplication extends Application<ProvisioningServ
         jersey.register(injector.getInstance(KeyResource.class));
         jersey.register(injector.getInstance(CallbackHandlerResource.class));
         jersey.register(injector.getInstance(ProjectResource.class));
+        jersey.register(injector.getInstance(OdahuResource.class));
         jersey.register(injector.getInstance(ProvisioningHealthCheckResource.class));
         environment.jersey().register(injector.getInstance(BucketResource.class));
     }
diff --git a/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/core/commands/CommandExecutorMockAsync.java b/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/core/commands/CommandExecutorMockAsync.java
index 38d6ca8..b65d891 100644
--- a/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/core/commands/CommandExecutorMockAsync.java
+++ b/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/core/commands/CommandExecutorMockAsync.java
@@ -302,7 +302,7 @@ public class CommandExecutorMockAsync implements Supplier<Boolean> {
     private void action(String user, DockerAction action) {
         String resourceType = parser.getResourceType();
 
-        String prefixFileName = (Lists.newArrayList("project", "edge", "dataengine", "dataengine-service")
+        String prefixFileName = (Lists.newArrayList("project", "edge", "odahu", "dataengine", "dataengine-service")
                 .contains(resourceType) ? resourceType : "notebook") + "_";
         String templateFileName = "mock_response/" + cloudProvider.getName() + '/' + prefixFileName +
                 action.toString() + JSON_FILE_ENDING;
diff --git a/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/core/response/handlers/OdahuCallbackHandler.java b/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/core/response/handlers/OdahuCallbackHandler.java
new file mode 100644
index 0000000..494a93b
--- /dev/null
+++ b/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/core/response/handlers/OdahuCallbackHandler.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.epam.datalab.backendapi.core.response.handlers;
+
+import com.epam.datalab.backendapi.core.commands.DockerAction;
+import com.epam.datalab.dto.ResourceURL;
+import com.epam.datalab.dto.base.odahu.OdahuResult;
+import com.epam.datalab.rest.client.RESTService;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+
+@Slf4j
+public class OdahuCallbackHandler extends ResourceCallbackHandler<OdahuResult> {
+
+    private static final String ODAHU_URLS_FIELD = "odahu_urls";
+    private static final String GRAFANA_ADMIN = "grafana_admin";
+    private static final String GRAFANA_PASSWORD = "grafana_pass";
+    private static final String OAUTH_COOKIE_SECRET = "oauth_cookie_secret";
+    private static final String DECRYPT_TOKEN = "odahuflow_connection_decrypt_token";
+    private final String callbackUri;
+    private final String name;
+    private final String projectName;
+    private final String endpointName;
+
+    public OdahuCallbackHandler(RESTService selfService, String user, String uuid, DockerAction action,
+                                String callbackUri, String name, String projectName, String endpointName) {
+        super(selfService, user, uuid, action);
+        this.callbackUri = callbackUri;
+        this.name = name;
+        this.projectName = projectName;
+        this.endpointName = endpointName;
+    }
+
+    @Override
+    protected String getCallbackURI() {
+        return callbackUri;
+    }
+
+    @Override
+    protected OdahuResult parseOutResponse(JsonNode resultNode, OdahuResult result) {
+        result.setName(name);
+        result.setProjectName(projectName);
+        result.setEndpointName(endpointName);
+
+        if (resultNode == null) {
+            return result;
+        }
+        result.setGrafanaAdmin(getTextValue(resultNode.get(GRAFANA_ADMIN)));
+        result.setGrafanaPassword(getTextValue(resultNode.get(GRAFANA_PASSWORD)));
+        result.setOauthCookieSecret(getTextValue(resultNode.get(OAUTH_COOKIE_SECRET)));
+        result.setDecryptToken(getTextValue(resultNode.get(DECRYPT_TOKEN)));
+
+        final JsonNode odahuUrls = resultNode.get(ODAHU_URLS_FIELD);
+        List<ResourceURL> urls = null;
+        if (odahuUrls != null) {
+            try {
+                urls = mapper.readValue(odahuUrls.toString(), new TypeReference<List<ResourceURL>>() {
+                });
+                result.setResourceUrls(urls);
+            } catch (IOException e) {
+                log.warn("Cannot parse field {} for UUID {} in JSON",
+                        RESPONSE_NODE + "." + RESULT_NODE + "." + ODAHU_URLS_FIELD, getUUID(), e);
+            }
+        }
+
+        if (getAction() == DockerAction.CREATE && Objects.isNull(urls)) {
+            log.warn("There are no odahu urls in response file while creating {}", result);
+        }
+
+        return result;
+    }
+}
diff --git a/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/modules/ProductionModule.java b/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/modules/ProductionModule.java
index f885338..18d38d8 100644
--- a/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/modules/ProductionModule.java
+++ b/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/modules/ProductionModule.java
@@ -29,9 +29,11 @@ import com.epam.datalab.backendapi.core.response.handlers.dao.CallbackHandlerDao
 import com.epam.datalab.backendapi.core.response.handlers.dao.FileSystemCallbackHandlerDao;
 import com.epam.datalab.backendapi.service.BucketService;
 import com.epam.datalab.backendapi.service.CheckInactivityService;
+import com.epam.datalab.backendapi.service.OdahuService;
 import com.epam.datalab.backendapi.service.ProjectService;
 import com.epam.datalab.backendapi.service.RestoreCallbackHandlerService;
 import com.epam.datalab.backendapi.service.impl.CheckInactivityServiceImpl;
+import com.epam.datalab.backendapi.service.impl.OdahuServiceImpl;
 import com.epam.datalab.backendapi.service.impl.ProjectServiceImpl;
 import com.epam.datalab.backendapi.service.impl.RestoreCallbackHandlerServiceImpl;
 import com.epam.datalab.backendapi.service.impl.aws.BucketServiceAwsImpl;
@@ -78,6 +80,7 @@ public class ProductionModule extends ModuleBase<ProvisioningServiceApplicationC
         bind(RestoreCallbackHandlerService.class).to(RestoreCallbackHandlerServiceImpl.class);
         bind(CheckInactivityService.class).to(CheckInactivityServiceImpl.class);
         bind(ProjectService.class).to(ProjectServiceImpl.class);
+        bind(OdahuService.class).to(OdahuServiceImpl.class);
         if (configuration.getCloudProvider() == CloudProvider.GCP) {
             bind(BucketService.class).to(BucketServiceGcpImpl.class);
         } else if (configuration.getCloudProvider() == CloudProvider.AWS) {
diff --git a/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/modules/ProvisioningDevModule.java b/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/modules/ProvisioningDevModule.java
index a94e706..f1b3319 100644
--- a/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/modules/ProvisioningDevModule.java
+++ b/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/modules/ProvisioningDevModule.java
@@ -32,9 +32,11 @@ import com.epam.datalab.backendapi.core.response.handlers.dao.CallbackHandlerDao
 import com.epam.datalab.backendapi.core.response.handlers.dao.FileSystemCallbackHandlerDao;
 import com.epam.datalab.backendapi.service.BucketService;
 import com.epam.datalab.backendapi.service.CheckInactivityService;
+import com.epam.datalab.backendapi.service.OdahuService;
 import com.epam.datalab.backendapi.service.ProjectService;
 import com.epam.datalab.backendapi.service.RestoreCallbackHandlerService;
 import com.epam.datalab.backendapi.service.impl.CheckInactivityServiceImpl;
+import com.epam.datalab.backendapi.service.impl.OdahuServiceImpl;
 import com.epam.datalab.backendapi.service.impl.ProjectServiceImpl;
 import com.epam.datalab.backendapi.service.impl.RestoreCallbackHandlerServiceImpl;
 import com.epam.datalab.backendapi.service.impl.aws.BucketServiceAwsImpl;
@@ -84,6 +86,7 @@ public class ProvisioningDevModule extends ModuleBase<ProvisioningServiceApplica
         bind(RestoreCallbackHandlerService.class).to(RestoreCallbackHandlerServiceImpl.class);
         bind(CheckInactivityService.class).to(CheckInactivityServiceImpl.class);
         bind(ProjectService.class).to(ProjectServiceImpl.class);
+        bind(OdahuService.class).to(OdahuServiceImpl.class);
         if (configuration.getCloudProvider() == CloudProvider.GCP) {
             bind(BucketService.class).to(BucketServiceGcpImpl.class);
         } else if (configuration.getCloudProvider() == CloudProvider.AWS) {
diff --git a/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/resources/OdahuResource.java b/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/resources/OdahuResource.java
new file mode 100644
index 0000000..69969df
--- /dev/null
+++ b/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/resources/OdahuResource.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.epam.datalab.backendapi.resources;
+
+import com.epam.datalab.auth.UserInfo;
+import com.epam.datalab.backendapi.service.OdahuService;
+import com.epam.datalab.dto.odahu.ActionOdahuDTO;
+import com.epam.datalab.dto.odahu.CreateOdahuDTO;
+import com.google.inject.Inject;
+import io.dropwizard.auth.Auth;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+@Path("infrastructure/odahu")
+@Consumes(MediaType.APPLICATION_JSON)
+@Produces(MediaType.APPLICATION_JSON)
+public class OdahuResource {
+
+    private final OdahuService odahuService;
+
+    @Inject
+    public OdahuResource(OdahuService odahuService) {
+        this.odahuService = odahuService;
+    }
+
+    @POST
+    public Response createProject(@Auth UserInfo userInfo, CreateOdahuDTO dto) {
+        return Response.ok(odahuService.create(userInfo, dto)).build();
+    }
+
+    @Path("start")
+    @POST
+    public Response startProject(@Auth UserInfo userInfo, ActionOdahuDTO dto) {
+        return Response.ok(odahuService.start(userInfo, dto)).build();
+    }
+
+    @Path("stop")
+    @POST
+    public Response stopProject(@Auth UserInfo userInfo, ActionOdahuDTO dto) {
+        return Response.ok(odahuService.stop(userInfo, dto)).build();
+    }
+
+    @Path("terminate")
+    @POST
+    public Response terminateProject(@Auth UserInfo userInfo, ActionOdahuDTO dto) {
+        return Response.ok(odahuService.terminate(userInfo, dto)).build();
+    }
+}
diff --git a/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/service/OdahuService.java b/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/service/OdahuService.java
new file mode 100644
index 0000000..46c23cf
--- /dev/null
+++ b/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/service/OdahuService.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.epam.datalab.backendapi.service;
+
+import com.epam.datalab.auth.UserInfo;
+import com.epam.datalab.dto.odahu.ActionOdahuDTO;
+import com.epam.datalab.dto.odahu.CreateOdahuDTO;
+
+public interface OdahuService {
+    String create(UserInfo userInfo, CreateOdahuDTO odahuCreateDTO);
+
+    String start(UserInfo userInfo, ActionOdahuDTO dto);
+
+    String stop(UserInfo userInfo, ActionOdahuDTO dto);
+
+    String terminate(UserInfo userInfo, ActionOdahuDTO dto);
+}
diff --git a/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/service/impl/OdahuServiceImpl.java b/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/service/impl/OdahuServiceImpl.java
new file mode 100644
index 0000000..6be8ece
--- /dev/null
+++ b/services/provisioning-service/src/main/java/com/epam/datalab/backendapi/service/impl/OdahuServiceImpl.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.epam.datalab.backendapi.service.impl;
+
+import com.epam.datalab.auth.UserInfo;
+import com.epam.datalab.backendapi.ProvisioningServiceApplicationConfiguration;
+import com.epam.datalab.backendapi.core.commands.CommandBuilder;
+import com.epam.datalab.backendapi.core.commands.DockerAction;
+import com.epam.datalab.backendapi.core.commands.DockerCommands;
+import com.epam.datalab.backendapi.core.commands.ICommandExecutor;
+import com.epam.datalab.backendapi.core.commands.RunDockerCommand;
+import com.epam.datalab.backendapi.core.response.folderlistener.FolderListenerExecutor;
+import com.epam.datalab.backendapi.core.response.handlers.OdahuCallbackHandler;
+import com.epam.datalab.backendapi.service.OdahuService;
+import com.epam.datalab.dto.ResourceBaseDTO;
+import com.epam.datalab.dto.odahu.ActionOdahuDTO;
+import com.epam.datalab.dto.odahu.CreateOdahuDTO;
+import com.epam.datalab.rest.client.RESTService;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.inject.Inject;
+
+public class OdahuServiceImpl implements OdahuService {
+
+    private static final String CALLBACK_URI = "/api/odahu/status";
+    private static final String ODAHU_RESOURCE_TYPE = "odahu";
+    private static final String ODAHU_IMAGE = "docker.datalab-odahu";
+
+    private final ProvisioningServiceApplicationConfiguration configuration;
+    private final FolderListenerExecutor folderListenerExecutor;
+    private final CommandBuilder commandBuilder;
+    private final ICommandExecutor commandExecutor;
+    private final RESTService selfService;
+
+    @Inject
+    public OdahuServiceImpl(ProvisioningServiceApplicationConfiguration configuration,
+                            FolderListenerExecutor folderListenerExecutor, CommandBuilder commandBuilder,
+                            ICommandExecutor commandExecutor, RESTService selfService) {
+        this.configuration = configuration;
+        this.folderListenerExecutor = folderListenerExecutor;
+        this.commandBuilder = commandBuilder;
+        this.commandExecutor = commandExecutor;
+        this.selfService = selfService;
+    }
+
+    @Override
+    public String create(UserInfo userInfo, CreateOdahuDTO dto) {
+        return executeDocker(userInfo, dto, DockerAction.CREATE, ODAHU_RESOURCE_TYPE, ODAHU_IMAGE, dto.getName(),
+                dto.getProject(), dto.getEndpoint());
+    }
+
+    @Override
+    public String start(UserInfo userInfo, ActionOdahuDTO dto) {
+        return executeDocker(userInfo, dto, DockerAction.START, ODAHU_RESOURCE_TYPE, ODAHU_IMAGE, dto.getName(),
+                dto.getProject(), dto.getEndpoint());
+    }
+
+    @Override
+    public String stop(UserInfo userInfo, ActionOdahuDTO dto) {
+        return executeDocker(userInfo, dto, DockerAction.STOP, ODAHU_RESOURCE_TYPE, ODAHU_IMAGE, dto.getName(),
+                dto.getProject(), dto.getEndpoint());
+    }
+
+    @Override
+    public String terminate(UserInfo userInfo, ActionOdahuDTO dto) {
+        return executeDocker(userInfo, dto, DockerAction.TERMINATE, ODAHU_RESOURCE_TYPE, ODAHU_IMAGE, dto.getName(),
+                dto.getProject(), dto.getEndpoint());
+    }
+
+
+    private String executeDocker(UserInfo userInfo, ResourceBaseDTO dto, DockerAction action, String resourceType,
+                                 String image, String name, String project, String endpoint) {
+        String uuid = DockerCommands.generateUUID();
+
+        folderListenerExecutor.start(configuration.getKeyLoaderDirectory(),
+                configuration.getKeyLoaderPollTimeout(),
+                new OdahuCallbackHandler(selfService, userInfo.getName(), uuid, action, CALLBACK_URI, name, project, endpoint));
+
+        RunDockerCommand runDockerCommand = new RunDockerCommand()
+                .withInteractive()
+                .withName(String.join("_", userInfo.getSimpleName(), name, resourceType, action.toString(),
+                        Long.toString(System.currentTimeMillis())))
+                .withVolumeForRootKeys(configuration.getKeyDirectory())
+                .withVolumeForResponse(configuration.getKeyLoaderDirectory())
+                .withVolumeForLog(configuration.getDockerLogDirectory(), resourceType)
+                .withResource(resourceType)
+                .withRequestId(uuid)
+                .withConfKeyName(configuration.getAdminKey())
+                .withImage(image)
+                .withAction(action);
+
+        try {
+            commandExecutor.executeAsync(userInfo.getName(), uuid, commandBuilder.buildCommand(runDockerCommand, dto));
+        } catch (JsonProcessingException e) {
+            e.printStackTrace();
+        }
+        return uuid;
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datalab.apache.org
For additional commands, e-mail: commits-help@datalab.apache.org