Skip to content
3 changes: 3 additions & 0 deletions deploy/kubernetes/dolphinscheduler/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,9 @@ api:
# jackson:
# time-zone: UTC
# date-format: "yyyy-MM-dd HH:mm:ss"
# api:
# # Whether to test datasource connectivity before creating or updating a datasource.
# datasource-connection-enable: false
# -- Periodic probe of container liveness. Container will be restarted if the probe fails.
# More info: [container-probes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes)
livenessProbe:
Expand Down
1 change: 1 addition & 0 deletions docs/docs/en/architecture/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ Location: `api-server/conf/application.yaml`
| casdoor.organization-name | | organization name in Casdoor |
| casdoor.application-name | | application name in Casdoor |
| casdoor.redirect-url | | doplhinscheduler login url |
| api.datasource-connection-enable | false | Whether to test datasource connectivity before creating or updating a datasource |
| api.traffic.control.global.switch | false | traffic control global switch |
| api.traffic.control.max-global-qps-rate | 300 | global max request number per second |
| api.traffic.control.tenant-switch | false | traffic control tenant switch |
Expand Down
1 change: 1 addition & 0 deletions docs/docs/zh/architecture/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ common.properties配置文件目前主要是配置hadoop/s3/yarn/applicationId
| casdoor.organization-name | | Casdoor中的组织名称 |
| casdoor.application-name | | Casdoor中的应用名称 |
| casdoor.redirect-url | | dolphinscheduler登录URL |
| api.datasource-connection-enable | false | 在创建或更新数据源时是否需要强制测试其连接性 |
| api.traffic.control.global.switch | false | 流量控制全局开关 |
| api.traffic.control.max-global-qps-rate | 300 | 全局最大请求数/秒 |
| api.traffic.control.tenant-switch | false | 流量控制租户开关 |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* 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 org.apache.dolphinscheduler.api.test.cases;

import org.apache.dolphinscheduler.api.test.core.DolphinScheduler;
import org.apache.dolphinscheduler.api.test.entity.HttpResponse;
import org.apache.dolphinscheduler.api.test.entity.LoginResponseData;
import org.apache.dolphinscheduler.api.test.pages.LoginPage;
import org.apache.dolphinscheduler.api.test.pages.datasource.DataSourcePage;
import org.apache.dolphinscheduler.api.test.utils.JSONUtils;

import java.util.LinkedHashMap;

import lombok.extern.slf4j.Slf4j;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.DisableIfTestFails;

@DolphinScheduler(composeFiles = "docker/datasource-connection-check-disabled/docker-compose.yaml")
@Slf4j
@DisableIfTestFails
public class DataSourceConnectionCheckDisabledAPITest {

private static final String username = "admin";

private static final String password = "dolphinscheduler123";

private static String sessionId;

private static DataSourcePage dataSourcePage;

private static int createdDataSourceId;

@BeforeAll
public static void setup() {
LoginPage loginPage = new LoginPage();
HttpResponse loginResponse = loginPage.login(username, password);
sessionId = JSONUtils.convertValue(loginResponse.getBody().getData(), LoginResponseData.class).getSessionId();
dataSourcePage = new DataSourcePage(sessionId);
}

@AfterAll
public static void cleanup() {
if (createdDataSourceId > 0) {
dataSourcePage.deleteDataSource(createdDataSourceId);
}
}

@Test
@Order(10)
void testCreateDataSourceWithInvalidHostShouldSuccess() {
HttpResponse response = dataSourcePage.createDataSource("mysql_test_invalid", "MYSQL",
"invalid-host-12345", 3306, "root", "password", "test_db");

Assertions.assertTrue(response.getBody().getSuccess(),
"Creating datasource with invalid host should succeed when connection check is disabled");

LinkedHashMap<String, Object> data = (LinkedHashMap<String, Object>) response.getBody().getData();
createdDataSourceId = ((Number) data.get("id")).intValue();
Assertions.assertNotNull(data.get("id"), "Datasource id should not be null");
}

@Test
@Order(20)
void testUpdateDataSourceWithInvalidHostShouldSuccess() {
HttpResponse updateResponse = dataSourcePage.updateDataSource(createdDataSourceId,
"mysql_test_invalid_updated", "MYSQL", "another-invalid-host", 3307, "root", "new_password",
"test_db_updated");

Assertions.assertTrue(updateResponse.getBody().getSuccess(),
"Updating datasource with invalid host should succeed when connection check is disabled");
}

@Test
@Order(30)
void testQueryDataSource() {
HttpResponse response = dataSourcePage.queryDataSource(createdDataSourceId);

Assertions.assertTrue(response.getBody().getSuccess(), "Querying datasource should succeed");
}

@Test
@Order(40)
void testDeleteDataSource() {
HttpResponse response = dataSourcePage.deleteDataSource(createdDataSourceId);

Assertions.assertTrue(response.getBody().getSuccess(), "Deleting datasource should succeed");
createdDataSourceId = 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* 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 org.apache.dolphinscheduler.api.test.cases;

import org.apache.dolphinscheduler.api.test.core.DolphinScheduler;
import org.apache.dolphinscheduler.api.test.entity.HttpResponse;
import org.apache.dolphinscheduler.api.test.entity.LoginResponseData;
import org.apache.dolphinscheduler.api.test.pages.LoginPage;
import org.apache.dolphinscheduler.api.test.pages.datasource.DataSourcePage;
import org.apache.dolphinscheduler.api.test.utils.JSONUtils;

import lombok.extern.slf4j.Slf4j;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.DisableIfTestFails;

@DolphinScheduler(composeFiles = "docker/datasource-connection-check-enabled/docker-compose.yaml")
@Slf4j
@DisableIfTestFails
public class DataSourceConnectionCheckEnabledAPITest {

private static final String username = "admin";

private static final String password = "dolphinscheduler123";

private static String sessionId;

private static DataSourcePage dataSourcePage;

@BeforeAll
public static void setup() {
LoginPage loginPage = new LoginPage();
HttpResponse loginResponse = loginPage.login(username, password);
sessionId = JSONUtils.convertValue(loginResponse.getBody().getData(), LoginResponseData.class).getSessionId();
dataSourcePage = new DataSourcePage(sessionId);
}

@AfterAll
public static void cleanup() {
}

@Test
@Order(10)
void testCreateDataSourceWithInvalidHostShouldFail() {
HttpResponse response = dataSourcePage.createDataSource("mysql_test_invalid", "MYSQL",
"invalid-host-12345", 3306, "root", "password", "test_db");

Assertions.assertFalse(response.getBody().getSuccess(),
"Creating datasource with invalid host should fail when connection check is enabled");
}

@Test
@Order(20)
void testUpdateDataSourceWithInvalidHostShouldFail() {
HttpResponse createResponse = dataSourcePage.createDataSource("mysql_test_update", "MYSQL",
"invalid-host-12345", 3306, "root", "password", "test_db");

Assertions.assertFalse(createResponse.getBody().getSuccess(),
"Creating datasource with invalid host should fail when connection check is enabled");
}
}
Original file line number Diff line number Diff line change
@@ -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 org.apache.dolphinscheduler.api.test.pages.datasource;

import org.apache.dolphinscheduler.api.test.core.Constants;
import org.apache.dolphinscheduler.api.test.entity.HttpResponse;
import org.apache.dolphinscheduler.api.test.utils.JSONUtils;
import org.apache.dolphinscheduler.api.test.utils.RequestClient;

import java.util.HashMap;
import java.util.Map;

import lombok.AllArgsConstructor;

@AllArgsConstructor
public final class DataSourcePage {

private String sessionId;

public HttpResponse createDataSource(String name, String type, String host, int port,
String userName, String password, String database) {
Map<String, Object> params = new HashMap<>();
params.put("name", name);
params.put("type", type);
params.put("host", host);
params.put("port", port);
params.put("userName", userName);
params.put("password", password);
params.put("database", database);
params.put("other", new HashMap<>());

Map<String, String> headers = new HashMap<>();
headers.put(Constants.SESSION_ID_KEY, sessionId);

RequestClient requestClient = new RequestClient();
return requestClient.postJson("/datasources", headers, JSONUtils.toJsonString(params));
}

public HttpResponse updateDataSource(int id, String name, String type, String host, int port,
String userName, String password, String database) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("name", name);
params.put("type", type);
params.put("host", host);
params.put("port", port);
params.put("userName", userName);
params.put("password", password);
params.put("database", database);
params.put("other", new HashMap<>());

Map<String, String> headers = new HashMap<>();
headers.put(Constants.SESSION_ID_KEY, sessionId);

RequestClient requestClient = new RequestClient();
String url = String.format("/datasources/%d", id);
return requestClient.putJson(url, headers, JSONUtils.toJsonString(params));
}

public HttpResponse queryDataSource(int id) {
Map<String, Object> params = new HashMap<>();
Map<String, String> headers = new HashMap<>();
headers.put(Constants.SESSION_ID_KEY, sessionId);

RequestClient requestClient = new RequestClient();
String url = String.format("/datasources/%d", id);
return requestClient.get(url, headers, params);
}

public HttpResponse deleteDataSource(int id) {
Map<String, Object> params = new HashMap<>();
Map<String, String> headers = new HashMap<>();
headers.put(Constants.SESSION_ID_KEY, sessionId);

RequestClient requestClient = new RequestClient();
String url = String.format("/datasources/%d", id);
return requestClient.delete(url, headers, params);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,82 @@ public HttpResponse post(String url, Map<String, String> headers, Map<String, Ob
return httpResponse;
}

@SneakyThrows
public HttpResponse postJson(String url, Map<String, String> headers, String jsonBody) {
if (headers == null) {
headers = new HashMap<>();
}

String requestUrl = String.format("%s%s", Constants.DOLPHINSCHEDULER_API_URL, url);
headers.put("Content-Type", "application/json");
Headers headersBuilder = Headers.of(headers);
RequestBody requestBody = RequestBody.create(jsonBody, MediaType.parse("application/json"));
log.info("POST JSON request to {}, Headers: {}", requestUrl, headersBuilder);
Request request = new Request.Builder()
.headers(headersBuilder)
.url(requestUrl)
.post(requestBody)
.build();
Response response = this.httpClient.newCall(request).execute();
int responseCode = response.code();
HttpResponseBody responseData = null;
Map<String, String> responseHeaders = new HashMap<>();

Headers responseHeadersObj = response.headers();
for (String name : responseHeadersObj.names()) {
responseHeaders.put(name, responseHeadersObj.get(name));
}

if (response.body() != null) {
responseData = JSONUtils.parseObject(response.body().string(), HttpResponseBody.class);
}
response.close();

HttpResponse httpResponse = new HttpResponse(responseCode, responseData, responseHeaders);

log.info("POST JSON response: {}", httpResponse);

return httpResponse;
}

@SneakyThrows
public HttpResponse putJson(String url, Map<String, String> headers, String jsonBody) {
if (headers == null) {
headers = new HashMap<>();
}

String requestUrl = String.format("%s%s", Constants.DOLPHINSCHEDULER_API_URL, url);
headers.put("Content-Type", "application/json");
Headers headersBuilder = Headers.of(headers);
RequestBody requestBody = RequestBody.create(jsonBody, MediaType.parse("application/json"));
log.info("PUT JSON request to {}, Headers: {}", requestUrl, headersBuilder);
Request request = new Request.Builder()
.headers(headersBuilder)
.url(requestUrl)
.put(requestBody)
.build();
Response response = this.httpClient.newCall(request).execute();
int responseCode = response.code();
HttpResponseBody responseData = null;
Map<String, String> responseHeaders = new HashMap<>();

Headers responseHeadersObj = response.headers();
for (String name : responseHeadersObj.names()) {
responseHeaders.put(name, responseHeadersObj.get(name));
}

if (response.body() != null) {
responseData = JSONUtils.parseObject(response.body().string(), HttpResponseBody.class);
}
response.close();

HttpResponse httpResponse = new HttpResponse(responseCode, responseData, responseHeaders);

log.info("PUT JSON response: {}", httpResponse);

return httpResponse;
}

@SneakyThrows
public HttpResponse put(String url, Map<String, String> headers, Map<String, Object> params) {
if (headers == null) {
Expand Down
Loading
Loading