diff --git a/.github/workflows/backend.yaml b/.github/workflows/backend.yaml index dcd0f4f2bd..fc9ae65001 100644 --- a/.github/workflows/backend.yaml +++ b/.github/workflows/backend.yaml @@ -244,16 +244,58 @@ jobs: - name: Init k3s uses: nolar/setup-k3d-k3s@v1 with: - version: v1.27.16+k3s1 + version: v1.35.2+k3s1 k3d-args: -s 1 --network dinky_net --api-port 172.28.0.1:6550 k3d-tag: v5.7.5 - - name: Get k3s kube config - run: k3d kubeconfig get --all && mkdir ./kube && k3d kubeconfig get --all > ./kube/k3s.yaml && sed -i 's/0.0.0.0/172.28.0.1/g' ./kube/k3s.yaml - name: Init k8s RBAC and namespace run: | kubectl create namespace dinky kubectl create serviceaccount dinky -n dinky kubectl create clusterrolebinding flink-role-binding-dinky --clusterrole=edit --serviceaccount=dinky:dinky + cat < ./kube/k3s.yaml + apiVersion: v1 + kind: Config + clusters: + - name: k3d-default + cluster: + server: https://172.28.0.1:6550 + insecure-skip-tls-verify: true + users: + - name: dinky + user: + token: $(kubectl create token dinky -n dinky) + contexts: + - name: dinky + context: + cluster: k3d-default + namespace: dinky + user: dinky + current-context: dinky + EOF - name: Init k3s main images run: | docker exec k3d-k3s-default-server-0 crictl pull library/busybox:latest diff --git a/dinky-admin/src/main/resources/mapper/DocumentMapper.xml b/dinky-admin/src/main/resources/mapper/DocumentMapper.xml index 604c8fe14e..a3a3cd95dd 100644 --- a/dinky-admin/src/main/resources/mapper/DocumentMapper.xml +++ b/dinky-admin/src/main/resources/mapper/DocumentMapper.xml @@ -46,6 +46,13 @@ and a.subtype = #{param.subtype} + + and a.enabled = + + 1 + 0 + + and a.version = #{param.version} diff --git a/dinky-gateway/src/main/java/org/dinky/gateway/kubernetes/KubernetesGateway.java b/dinky-gateway/src/main/java/org/dinky/gateway/kubernetes/KubernetesGateway.java index ae9bd7236d..803036096a 100644 --- a/dinky-gateway/src/main/java/org/dinky/gateway/kubernetes/KubernetesGateway.java +++ b/dinky-gateway/src/main/java/org/dinky/gateway/kubernetes/KubernetesGateway.java @@ -41,11 +41,9 @@ import org.apache.flink.kubernetes.KubernetesClusterClientFactory; import org.apache.flink.kubernetes.KubernetesClusterDescriptor; import org.apache.flink.kubernetes.configuration.KubernetesConfigOptions; -import org.apache.flink.kubernetes.kubeclient.Fabric8FlinkKubeClient; -import org.apache.flink.kubernetes.kubeclient.FlinkKubeClient; import org.apache.flink.python.PythonOptions; -import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; import java.util.Collections; import java.util.Map; import java.util.UUID; @@ -55,7 +53,6 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.text.StrFormatter; -import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; import io.fabric8.kubernetes.api.model.Pod; import lombok.Data; @@ -224,26 +221,37 @@ public TestResult test() { // Test mode no jobName, use uuid . addConfigParas(KubernetesConfigOptions.CLUSTER_ID, UUID.randomUUID().toString()); initConfig(); - FlinkKubeClient client = k8sClientHelper.getClient(); - if (client instanceof Fabric8FlinkKubeClient) { - Object internalClient = ReflectUtil.getFieldValue(client, "internalClient"); - Method method = ReflectUtil.getMethod(internalClient.getClass(), "getVersion"); - Object versionInfo = method.invoke(internalClient); - logger.info( - "k8s cluster link successful ; k8s version: {} ; platform: {}", - ReflectUtil.getFieldValue(versionInfo, "gitVersion"), - ReflectUtil.getFieldValue(versionInfo, "platform")); - } + String namespace = configuration.get(KubernetesConfigOptions.NAMESPACE); + k8sClientHelper.getKubernetesClient().pods().inNamespace(namespace).list(); + logger.info("k8s cluster link successful ; namespace: {}", namespace); return TestResult.success(); } catch (Exception e) { logger.error(Status.GATEWAY_KUBERNETES_TEST_FAILED.getMessage(), e); + String errorDetail = extractTestErrorDetail(e); return TestResult.fail( - StrFormatter.format("{}:{}", Status.GATEWAY_KUBERNETES_TEST_FAILED.getMessage(), e.getMessage())); + StrFormatter.format("{} {}", Status.GATEWAY_KUBERNETES_TEST_FAILED.getMessage(), errorDetail)); } finally { close(); } } + static String extractTestErrorDetail(Throwable throwable) { + Throwable rootCause = throwable; + while (rootCause instanceof InvocationTargetException + && ((InvocationTargetException) rootCause).getTargetException() != null) { + rootCause = ((InvocationTargetException) rootCause).getTargetException(); + } + while (rootCause.getCause() != null && rootCause.getCause() != rootCause) { + rootCause = rootCause.getCause(); + } + + String message = rootCause.getMessage(); + if (StringUtils.isBlank(message)) { + return rootCause.getClass().getName(); + } + return StrFormatter.format("{}: {}", rootCause.getClass().getName(), message); + } + @Override public void killCluster() { log.info("Start kill cluster: " + config.getFlinkConfig().getJobName()); diff --git a/dinky-gateway/src/test/java/org/dinky/gateway/kubernetes/KubernetesGatewayTest.java b/dinky-gateway/src/test/java/org/dinky/gateway/kubernetes/KubernetesGatewayTest.java new file mode 100644 index 0000000000..a8f3e88dcb --- /dev/null +++ b/dinky-gateway/src/test/java/org/dinky/gateway/kubernetes/KubernetesGatewayTest.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 org.dinky.gateway.kubernetes; + +import static org.junit.Assert.assertEquals; + +import java.lang.reflect.InvocationTargetException; + +import org.junit.Test; + +public class KubernetesGatewayTest { + + @Test + public void testExtractTestErrorDetailUnwrapsInvocationTargetException() { + IllegalStateException rootCause = new IllegalStateException("connection refused"); + InvocationTargetException invocationTargetException = new InvocationTargetException(rootCause); + + String errorDetail = KubernetesGateway.extractTestErrorDetail(invocationTargetException); + + assertEquals("java.lang.IllegalStateException: connection refused", errorDetail); + } + + @Test + public void testExtractTestErrorDetailFallsBackToClassName() { + NullPointerException rootCause = new NullPointerException(); + + String errorDetail = KubernetesGateway.extractTestErrorDetail(rootCause); + + assertEquals("java.lang.NullPointerException", errorDetail); + } +} diff --git a/dinky-web/pom.xml b/dinky-web/pom.xml index e81a054878..6635b17191 100644 --- a/dinky-web/pom.xml +++ b/dinky-web/pom.xml @@ -51,8 +51,8 @@ 10.5.0 9.15.4 - - + + @@ -61,24 +61,24 @@ install-node-and-pnpm - - install - - pnpm - - - install --registry ${npm-registry-repo} - - - - build - - pnpm - - - run build - - + + install + + pnpm + + + install --registry ${npm-registry-repo} + + + + build + + pnpm + + + run build + + diff --git a/dinky-web/src/pages/RegCenter/Document/components/DocumentProTable/index.tsx b/dinky-web/src/pages/RegCenter/Document/components/DocumentProTable/index.tsx index a5ebecbbad..ec0491d3e8 100644 --- a/dinky-web/src/pages/RegCenter/Document/components/DocumentProTable/index.tsx +++ b/dinky-web/src/pages/RegCenter/Document/components/DocumentProTable/index.tsx @@ -117,16 +117,12 @@ const DocumentTableList: React.FC = () => { title: l('rc.doc.functionType'), sorter: true, dataIndex: 'type', - filterMultiple: true, - filters: true, valueEnum: DOCUMENT_TYPE_ENUMS }, { title: l('rc.doc.subFunctionType'), sorter: true, dataIndex: 'subtype', - filters: true, - filterMultiple: true, renderFormItem: (item, { type }, form) => { const currentType = form.getFieldValue('type'); let options = currentType === DOCUMENT_TYPE_ENUMS.FUN_UDF.value ? FUNCTION_TYPES : JOB_TYPE; @@ -139,8 +135,6 @@ const DocumentTableList: React.FC = () => { title: l('rc.doc.category'), sorter: true, dataIndex: 'category', - filterMultiple: true, - filters: true, valueEnum: DOCUMENT_CATEGORY_ENUMS }, { @@ -172,11 +166,11 @@ const DocumentTableList: React.FC = () => { { title: l('global.table.isEnable'), dataIndex: 'enabled', - hideInSearch: true, - filters: STATUS_MAPPING(), - filterMultiple: false, hideInDescriptions: true, - valueEnum: STATUS_ENUM(), + valueType: 'select', + valueEnum: Object.fromEntries( + STATUS_MAPPING().map(item => [item.value, { text: item.text, status: item.value === 1 ? 'Success' : 'Error' }]) + ), render: (_, record) => { return ( int: def addApplicationCluster(session: Session, params: dict) -> Optional[int]: name = params['name'] - test_connection_yarn_resp = session.post(url("api/clusterConfiguration/testConnect"), json=params) - assertRespOk(test_connection_yarn_resp, "Test yarn connectivity") - test_connection_yarn_resp = session.put(url("api/clusterConfiguration/saveOrUpdate"), json=params) - assertRespOk(test_connection_yarn_resp, "Add Yarn Application Cluster") + cluster_type = params["type"] + test_connection_resp = session.post(url("api/clusterConfiguration/testConnect"), json=params) + assertRespOk(test_connection_resp, f"Test {cluster_type} connectivity") + save_cluster_resp = session.put(url("api/clusterConfiguration/saveOrUpdate"), json=params) + assertRespOk(save_cluster_resp, f"Add {cluster_type} cluster") get_app_list = session.get(url(f"api/clusterConfiguration/list?keyword={name}"), json=params) - assertRespOk(get_app_list, "Get Yarn Application Cluster") + assertRespOk(get_app_list, f"Get {cluster_type} cluster") for data in get_app_list.json()["data"]: if data["name"] == name: return data['id']