You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by ma...@apache.org on 2021/04/25 05:51:29 UTC

[apisix-dashboard] branch master updated: fix: can not configure upstream with no nodes (#1812)

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

majunjie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git


The following commit(s) were added to refs/heads/master by this push:
     new 1dce703  fix: can not configure upstream with no nodes (#1812)
1dce703 is described below

commit 1dce703dba0c0f773cb4deb8545e0b671a39e8db
Author: liuxiran <li...@apache.org>
AuthorDate: Sun Apr 25 13:51:22 2021 +0800

    fix: can not configure upstream with no nodes (#1812)
---
 api/internal/core/entity/format.go                 |  2 +-
 api/internal/core/entity/format_test.go            | 76 ++++++++++++++++++++++
 web/cypress/fixtures/data.json                     |  1 +
 web/cypress/fixtures/selector.json                 |  1 +
 .../create_and_edit_upstream_with_no_nodes.spec.js | 68 +++++++++++++++++++
 web/src/components/Upstream/components/Nodes.tsx   |  2 +-
 web/src/components/Upstream/service.ts             | 15 ++---
 web/src/pages/Route/typing.d.ts                    |  1 -
 web/src/pages/Upstream/locales/en-US.ts            |  2 +-
 web/src/pages/Upstream/typing.d.ts                 |  1 -
 10 files changed, 154 insertions(+), 15 deletions(-)

diff --git a/api/internal/core/entity/format.go b/api/internal/core/entity/format.go
index d92bb40..1043e81 100644
--- a/api/internal/core/entity/format.go
+++ b/api/internal/core/entity/format.go
@@ -46,7 +46,7 @@ func mapKV2Node(key string, val float64) (*Node, error) {
 }
 
 func NodesFormat(obj interface{}) interface{} {
-	var nodes []*Node
+	nodes := make([]*Node, 0)
 	switch objType := obj.(type) {
 	case map[string]float64:
 		log.Infof("nodes type: %v", objType)
diff --git a/api/internal/core/entity/format_test.go b/api/internal/core/entity/format_test.go
index 7ca9d4c..23ff9f1 100644
--- a/api/internal/core/entity/format_test.go
+++ b/api/internal/core/entity/format_test.go
@@ -101,3 +101,79 @@ func TestNodesFormat_Map(t *testing.T) {
 	assert.Contains(t, jsonStr, `"port":8080`)
 	assert.Contains(t, jsonStr, `"host":"127.0.0.1"`)
 }
+
+func TestNodesFormat_empty_struct(t *testing.T) {
+	// route data saved in ETCD
+	routeStr := `{
+		"uris": ["/*"],
+		"upstream": {
+			"type": "roundrobin",
+			"nodes": []
+		}
+	}`
+
+	// bind struct
+	var route Route
+	err := json.Unmarshal([]byte(routeStr), &route)
+	assert.Nil(t, err)
+
+	// nodes format
+	nodes := NodesFormat(route.Upstream.Nodes)
+
+	// json encode for client
+	res, err := json.Marshal(nodes)
+	assert.Nil(t, err)
+	jsonStr := string(res)
+	assert.Contains(t, jsonStr, `[]`)
+}
+
+func TestNodesFormat_empty_map(t *testing.T) {
+	// route data saved in ETCD
+	routeStr := `{
+		"uris": ["/*"],
+		"upstream": {
+			"type": "roundrobin",
+			"nodes": {}
+		}
+	}`
+
+	// bind struct
+	var route Route
+	err := json.Unmarshal([]byte(routeStr), &route)
+	assert.Nil(t, err)
+
+	// nodes format
+	nodes := NodesFormat(route.Upstream.Nodes)
+
+	// json encode for client
+	res, err := json.Marshal(nodes)
+	assert.Nil(t, err)
+	jsonStr := string(res)
+	assert.Contains(t, jsonStr, `[]`)
+}
+
+func TestNodesFormat_no_nodes(t *testing.T) {
+	// route data saved in ETCD
+	routeStr := `{
+		"uris": ["/*"],
+		"upstream": {
+			"type": "roundrobin",
+			"service_name": "USER-SERVICE",
+			"discovery_type": "eureka"
+		}
+	}`
+
+	// bind struct
+	var route Route
+	err := json.Unmarshal([]byte(routeStr), &route)
+	assert.Nil(t, err)
+
+	// nodes format
+	nodes := NodesFormat(route.Upstream.Nodes)
+
+	// json encode for client
+	res, err := json.Marshal(nodes)
+	assert.Nil(t, err)
+	jsonStr := string(res)
+	assert.Contains(t, jsonStr, `null`)
+}
diff --git a/web/cypress/fixtures/data.json b/web/cypress/fixtures/data.json
index 9c14e98..6412060 100644
--- a/web/cypress/fixtures/data.json
+++ b/web/cypress/fixtures/data.json
@@ -1,6 +1,7 @@
 {
   "deletePluginSuccess": "Delete Plugin Successfully",
   "createUpstreamSuccess": "Create Upstream Successfully",
+  "configureUpstreamSuccess": "Configure Upstream Successfully",
   "createServiceSuccess": "Create Service Successfully",
   "editServiceSuccess": "Configure Service Successfully",
   "deleteServiceSuccess": "Delete Service Successfully",
diff --git a/web/cypress/fixtures/selector.json b/web/cypress/fixtures/selector.json
index f075bc0..d6691b9 100644
--- a/web/cypress/fixtures/selector.json
+++ b/web/cypress/fixtures/selector.json
@@ -30,6 +30,7 @@
   "nameSearch": "[title=Name]",
   "description": "#desc",
   "upstreamSelector": "[data-cy=upstream_selector]",
+  "upstreamNodeMinus0": "[data-cy=upstream-node-minus-0]",
   "addHost": "[data-cy=addHost]",
   "hosts_1": "#hosts_1",
   "remoteHost": "#remote_addrs_0",
diff --git a/web/cypress/integration/upstream/create_and_edit_upstream_with_no_nodes.spec.js b/web/cypress/integration/upstream/create_and_edit_upstream_with_no_nodes.spec.js
new file mode 100644
index 0000000..95cc820
--- /dev/null
+++ b/web/cypress/integration/upstream/create_and_edit_upstream_with_no_nodes.spec.js
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+/* eslint-disable no-undef */
+
+context('Create and Delete Upstream', () => {
+    beforeEach(() => {
+      cy.login();
+
+      cy.fixture('selector.json').as('domSelector');
+      cy.fixture('data.json').as('data');
+    });
+
+    it('should create upstream with no nodes', function () {
+      cy.visit('/');
+      cy.contains('Upstream').click();
+      cy.contains('Create').click();
+
+      cy.get(this.domSelector.name).type(this.data.upstreamName);
+      cy.get(this.domSelector.description).type(this.data.description);
+
+      // delete all nodes
+      cy.get(this.domSelector.upstreamNodeMinus0).click();
+
+      cy.contains('Next').click();
+      cy.contains('Submit').click();
+      cy.get(this.domSelector.notification).should('contain', this.data.createUpstreamSuccess);
+      cy.url().should('contains', 'upstream/list');
+    });
+
+    it('should configure the upstream with no nodes', function () {
+      cy.visit('/');
+      cy.contains('Upstream').click();
+
+      cy.get(this.domSelector.nameSelector).type(this.data.upstreamName);
+      cy.contains('Search').click();
+      cy.contains(this.data.upstreamName).siblings().contains('Configure').click();
+
+      cy.get(this.domSelector.upstreamNodeMinus0).should('not.exist');
+      cy.contains('Next').click();
+      cy.contains('Submit').click();
+
+      cy.get(this.domSelector.notification).should('contain', this.data.configureUpstreamSuccess);
+      cy.url().should('contains', 'upstream/list');
+
+    });
+
+    it('should delete the upstream', function () {
+      cy.visit('/');
+      cy.contains('Upstream').click();
+      cy.contains(this.data.upstreamName).siblings().contains('Delete').click();
+      cy.contains('button', 'Confirm').click();
+      cy.get(this.domSelector.notification).should('contain', this.data.deleteUpstreamSuccess);
+    });
+  });
diff --git a/web/src/components/Upstream/components/Nodes.tsx b/web/src/components/Upstream/components/Nodes.tsx
index 92bfcda..92bfc94 100644
--- a/web/src/components/Upstream/components/Nodes.tsx
+++ b/web/src/components/Upstream/components/Nodes.tsx
@@ -105,7 +105,7 @@ const Component: React.FC<Props> = ({ readonly }) => {
                 </Col>
                 <Col style={{ ...removeBtnStyle, marginLeft: -50 }}>
                   {!readonly && (
-                    <MinusCircleOutlined onClick={() => remove(field.name)} />
+                    <MinusCircleOutlined data-cy={`upstream-node-minus-${index}`} onClick={() => remove(field.name)} />
                   )}
                 </Col>
               </Row>
diff --git a/web/src/components/Upstream/service.ts b/web/src/components/Upstream/service.ts
index 098ed22..168df8b 100644
--- a/web/src/components/Upstream/service.ts
+++ b/web/src/components/Upstream/service.ts
@@ -68,7 +68,6 @@ export const convertToRequestData = (
     type,
     hash_on,
     key,
-    k8s_deployment_info,
     nodes,
     pass_host,
     upstream_host,
@@ -82,14 +81,6 @@ export const convertToRequestData = (
 
   data = omit(data, "upstream_id") as any
 
-  if (nodes && k8s_deployment_info) {
-    return undefined;
-  }
-
-  if (!nodes && !k8s_deployment_info) {
-    return undefined;
-  }
-
   if (type === 'chash') {
     if (!hash_on) {
       return undefined;
@@ -112,9 +103,13 @@ export const convertToRequestData = (
     return undefined
   }
 
+  /**
+   * nodes will be [] or node list
+   * when upstream_id === none, None === undefined
+   */
   if (nodes) {
     // NOTE: https://github.com/ant-design/ant-design/issues/27396
-    data.nodes = data.nodes?.map((item) => {
+    data.nodes = nodes?.map((item) => {
       return pick(item, ['host', 'port', 'weight']);
     });
     return data;
diff --git a/web/src/pages/Route/typing.d.ts b/web/src/pages/Route/typing.d.ts
index 7a8a3e6..3245c7a 100644
--- a/web/src/pages/Route/typing.d.ts
+++ b/web/src/pages/Route/typing.d.ts
@@ -190,7 +190,6 @@ declare namespace RouteModule {
     upstream: {
       checks: UpstreamModule.HealthCheck;
       create_time: number;
-      k8s_deployment_info: UpstreamModule.K8SDeploymentInfo;
       id: string;
       nodes: {
         port: number;
diff --git a/web/src/pages/Upstream/locales/en-US.ts b/web/src/pages/Upstream/locales/en-US.ts
index 702104e..23741e5 100644
--- a/web/src/pages/Upstream/locales/en-US.ts
+++ b/web/src/pages/Upstream/locales/en-US.ts
@@ -67,7 +67,7 @@ export default {
   'page.upstream.create': 'Create Upstream',
   'page.upstream.configure': 'Configure Upstream',
   'page.upstream.create.upstream.successfully': 'Create Upstream Successfully',
-  'page.upstream.edit.upstream.successfully': 'Update Upstream Successfully',
+  'page.upstream.edit.upstream.successfully': 'Configure Upstream Successfully',
   'page.upstream.create.basic.info': 'Basic Information',
   'page.upstream.create.preview': 'Preview',
 
diff --git a/web/src/pages/Upstream/typing.d.ts b/web/src/pages/Upstream/typing.d.ts
index a262ba1..7adf150 100644
--- a/web/src/pages/Upstream/typing.d.ts
+++ b/web/src/pages/Upstream/typing.d.ts
@@ -68,7 +68,6 @@ declare namespace UpstreamModule {
     upstream_id: string;
     type: Type;
     nodes?: Node[];
-    k8s_deployment_info?: K8SDeploymentInfo;
     hash_on?: 'vars' | 'header' | 'cookie' | 'consumer';
     key?: string;
     checks?: HealthCheck;