You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by su...@apache.org on 2021/04/12 12:46:08 UTC

[apisix-dashboard] branch master updated: feat: add limit-conn plugin form (#1728)

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

sunyi 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 c68e2d2  feat: add limit-conn plugin form (#1728)
c68e2d2 is described below

commit c68e2d2b3d4cfc2602e6667f91c77aaff69b6038
Author: litesun <su...@apache.org>
AuthorDate: Mon Apr 12 20:46:01 2021 +0800

    feat: add limit-conn plugin form (#1728)
    
    Co-authored-by: 琚致远 <ju...@apache.org>
---
 .../consumer/create-with-limit-conn-form.spec.js   | 109 +++++++++++++++++++++
 web/cypress/support/commands.js                    |   8 --
 web/src/components/Plugin/UI/limit-conn.tsx        |  93 ++++++++++++++++++
 web/src/components/Plugin/UI/plugin.tsx            |   7 +-
 web/src/components/Plugin/locales/en-US.ts         |   8 ++
 web/src/components/Plugin/locales/zh-CN.ts         |   8 ++
 6 files changed, 223 insertions(+), 10 deletions(-)

diff --git a/web/cypress/integration/consumer/create-with-limit-conn-form.spec.js b/web/cypress/integration/consumer/create-with-limit-conn-form.spec.js
new file mode 100644
index 0000000..3cbec3d
--- /dev/null
+++ b/web/cypress/integration/consumer/create-with-limit-conn-form.spec.js
@@ -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.
+ */
+/* eslint-disable no-undef */
+
+context('Create and delete consumer with limit-conn plugin form', () => {
+  beforeEach(() => {
+    cy.login();
+
+    cy.fixture('selector.json').as('domSelector');
+    cy.fixture('data.json').as('data');
+  });
+
+  const selector = {
+    conn: '#conn',
+    burst: '#burst',
+    default_conn_delay: '#default_conn_delay',
+    key: '#key',
+    rejected_code: '#rejected_code',
+    title: '[title="remote_addr"]'
+  }
+
+  const data = {
+    conn: 1,
+    burst: 0,
+    default_conn_delay: 1,
+    key: 'remote_addr',
+  }
+
+  it('creates consumer with limit-conn form', function () {
+    cy.visit('/');
+    cy.contains('Consumer').click();
+    cy.get(this.domSelector.empty).should('be.visible');
+    cy.contains('Create').click();
+    // basic information
+    cy.get(this.domSelector.username).type(this.data.consumerName);
+    cy.get(this.domSelector.description).type(this.data.description);
+    cy.contains('Next').click();
+
+    // config auth plugin
+    cy.contains(this.domSelector.pluginCard, 'key-auth').within(() => {
+      cy.contains('Enable').click({
+        force: true,
+      });
+    });
+    cy.focused(this.domSelector.drawer).should('exist');
+    cy.get(this.domSelector.disabledSwitcher).click();
+    // edit codemirror
+    cy.get(this.domSelector.codeMirror)
+      .first()
+      .then((editor) => {
+        editor[0].CodeMirror.setValue(
+          JSON.stringify({
+            key: 'test',
+          }),
+        );
+        cy.contains('button', 'Submit').click();
+      });
+
+    cy.contains(this.domSelector.pluginCard, 'limit-conn').within(() => {
+      cy.contains('Enable').click({
+        force: true,
+      });
+    });
+
+    cy.focused(this.domSelector.drawer).should('exist');
+
+    // config limit-conn form
+    cy.get(selector.conn).type(data.conn);
+    cy.get(selector.burst).type(data.burst);
+    cy.get(selector.default_conn_delay).type(data.default_conn_delay);
+    cy.get(selector.key).click();
+    cy.get(this.domSelector.selectDropdown).should('be.visible');
+    cy.get(selector.title).click({
+      timeout: 5000,
+    });
+    cy.get(this.domSelector.disabledSwitcher).click();
+    cy.get(this.domSelector.drawer).within(() => {
+      cy.contains('Submit').click({
+        force: true,
+      });
+    });
+    cy.get(this.domSelector.drawer).should('not.exist');
+
+    cy.contains('button', 'Next').click();
+    cy.contains('button', 'Submit').click();
+    cy.get(this.domSelector.notification).should('contain', this.data.createConsumerSuccess);
+  });
+
+  it('delete the consumer', function () {
+    cy.visit('/consumer/list');
+    cy.contains(this.data.consumerName).should('be.visible').siblings().contains('Delete').click();
+    cy.contains('button', 'Confirm').click();
+    cy.get(this.domSelector.notification).should('contain', this.data.deleteConsumerSuccess);
+  });
+});
diff --git a/web/cypress/support/commands.js b/web/cypress/support/commands.js
index 2f28b64..a55a8d0 100644
--- a/web/cypress/support/commands.js
+++ b/web/cypress/support/commands.js
@@ -89,14 +89,6 @@ Cypress.Commands.add('configurePlugins', (cases) => {
           }
           cy.get(domSelector.drawer).should('exist');
 
-          cy.get(domSelector.codeMirrorMode).invoke('text').then(text => {
-            if (text === 'Form') {
-              cy.get(domSelector.codeMirrorMode).click();
-              cy.get(domSelector.selectDropdown).should('be.visible');
-              cy.get(domSelector.selectJSON).click();
-            }
-          });
-
           cy.get(domSelector.drawer, { timeout }).within(() => {
             cy.contains('Submit').click({
               force: true,
diff --git a/web/src/components/Plugin/UI/limit-conn.tsx b/web/src/components/Plugin/UI/limit-conn.tsx
new file mode 100644
index 0000000..ec535fc
--- /dev/null
+++ b/web/src/components/Plugin/UI/limit-conn.tsx
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+import React from 'react';
+import type { FormInstance } from 'antd/es/form';
+import { Form, InputNumber, Select } from 'antd';
+import { useIntl } from 'umi';
+
+type Props = {
+  form: FormInstance;
+  ref?: any;
+};
+
+const FORM_ITEM_LAYOUT = {
+  labelCol: {
+    span: 6,
+  },
+  wrapperCol: {
+    span: 8
+  },
+};
+
+const LimitConn: React.FC<Props> = ({ form }) => {
+  const { formatMessage } = useIntl();
+  return (
+    <Form
+      form={form}
+      {...FORM_ITEM_LAYOUT}
+    >
+      <Form.Item
+        label="conn"
+        required
+        name="conn"
+        tooltip={formatMessage({ id: 'component.pluginForm.limit-conn.conn.tooltip' })}
+      >
+        <InputNumber min={1} required />
+      </Form.Item>
+      <Form.Item
+        label="burst"
+        required
+        name="burst"
+        tooltip={formatMessage({ id: 'component.pluginForm.limit-conn.burst.tooltip' })}
+      >
+        <InputNumber min={0} required />
+      </Form.Item>
+      <Form.Item
+        label="default_conn_delay"
+        required
+        name="default_conn_delay"
+        tooltip={formatMessage({ id: 'component.pluginForm.limit-conn.default_conn_delay.tooltip' })}
+      >
+        <InputNumber step={0.001} min={0.001} required />
+      </Form.Item>
+
+      <Form.Item
+        label="key"
+        required
+        name="key"
+        tooltip={formatMessage({ id: 'component.pluginForm.limit-conn.key.tooltip' })}
+      >
+        <Select>
+          {["remote_addr", "server_addr", "http_x_real_ip", "http_x_forwarded_for", "consumer_name"].map(item => {
+            return <Select.Option value={item} key={item}>{item}</Select.Option>
+          })}
+        </Select>
+      </Form.Item>
+
+      <Form.Item
+        label="rejected_code"
+        name="rejected_code"
+        initialValue={503}
+        tooltip={formatMessage({ id: 'component.pluginForm.limit-conn.rejected_code.tooltip' })}
+      >
+        <InputNumber min={200} max={599} required />
+      </Form.Item>
+    </Form>
+  );
+}
+
+export default LimitConn;
diff --git a/web/src/components/Plugin/UI/plugin.tsx b/web/src/components/Plugin/UI/plugin.tsx
index eb2e7c2..79a2df5 100644
--- a/web/src/components/Plugin/UI/plugin.tsx
+++ b/web/src/components/Plugin/UI/plugin.tsx
@@ -19,7 +19,8 @@ import type { FormInstance } from 'antd/es/form';
 import { Empty } from 'antd';
 import { useIntl } from 'umi';
 
-import BasicAuth from './basic-auth'
+import BasicAuth from './basic-auth';
+import LimitConn from './limit-conn';
 
 type Props = {
   name: string,
@@ -27,7 +28,7 @@ type Props = {
   renderForm: boolean
 }
 
-export const PLUGIN_UI_LIST = ['basic-auth',];
+export const PLUGIN_UI_LIST = ['basic-auth', 'limit-conn'];
 
 export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
 
@@ -38,6 +39,8 @@ export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
   switch (name) {
     case 'basic-auth':
       return <BasicAuth form={form} />
+    case 'limit-conn':
+      return <LimitConn form={form} />
     default:
       return null;
   }
diff --git a/web/src/components/Plugin/locales/en-US.ts b/web/src/components/Plugin/locales/en-US.ts
index c7c1c04..2b63c63 100644
--- a/web/src/components/Plugin/locales/en-US.ts
+++ b/web/src/components/Plugin/locales/en-US.ts
@@ -21,6 +21,14 @@ export default {
   'component.step.select.pluginTemplate.select.option': 'Custom',
   'component.plugin.pluginTemplate.tip1': '1. When a route already have plugins field configured, the plugins in the plugin template will be merged into it.',
   'component.plugin.pluginTemplate.tip2': '2. The same plugin in the plugin template will override one in the plugins',
+
+  // limit-conn
+  'component.pluginForm.limit-conn.conn.tooltip': 'the maximum number of concurrent requests allowed. Requests exceeding this ratio (and below conn + burst) will get delayed(the latency seconds is configured by default_conn_delay) to conform to this threshold.',
+  'component.pluginForm.limit-conn.burst.tooltip': 'the number of excessive concurrent requests (or connections) allowed to be delayed.',
+  'component.pluginForm.limit-conn.default_conn_delay.tooltip': 'the latency seconds of request when concurrent requests exceeding conn but below (conn + burst).',
+  'component.pluginForm.limit-conn.key.tooltip': 'to limit the concurrency level.For example, one can use the host name (or server zone) as the key so that we limit concurrency per host name. Otherwise, we can also use the client address as the key so that we can avoid a single client from flooding our service with too many parallel connections or requests.Now accept those as key: "remote_addr"(client\'s IP), "server_addr"(server\'s IP), "X-Forwarded-For/X-Real-IP" in request header, "co [...]
+  'component.pluginForm.limit-conn.rejected_code.tooltip': 'returned when the request exceeds conn + burst will be rejected.',
+
   'component.plugin.form': 'Form',
   'component.plugin.format-codes.disable': 'Format JSON or YAML data',
   'component.plugin.editor': 'Plugin Editor',
diff --git a/web/src/components/Plugin/locales/zh-CN.ts b/web/src/components/Plugin/locales/zh-CN.ts
index d54dba1..c106b2e 100644
--- a/web/src/components/Plugin/locales/zh-CN.ts
+++ b/web/src/components/Plugin/locales/zh-CN.ts
@@ -21,6 +21,14 @@ export default {
   'component.step.select.pluginTemplate.select.option': '手动配置',
   'component.plugin.pluginTemplate.tip1': '1. 若路由已配置插件,则插件模板数据将与已配置的插件数据合并。',
   'component.plugin.pluginTemplate.tip2': '2. 插件模板相同的插件会覆盖掉原有的插件。',
+
+  // limit-conn
+  'component.pluginForm.limit-conn.conn.tooltip': '允许的最大并发请求数。超过 conn 的限制、但是低于 conn + burst 的请求,将被延迟处理。',
+  'component.pluginForm.limit-conn.burst.tooltip': '允许被延迟处理的并发请求数。',
+  'component.pluginForm.limit-conn.default_conn_delay.tooltip': '默认的典型连接(或请求)的处理延迟时间。',
+  'component.pluginForm.limit-conn.key.tooltip': '用户指定的限制并发级别的关键字,可以是客户端 IP 或服务端 IP。例如,可以使用主机名(或服务器区域)作为关键字,以便限制每个主机名的并发性。 否则,我们也可以使用客户端地址作为关键字,这样我们就可以避免单个客户端用太多的并行连接或请求淹没我们的服务。当前接受的 key 有:"remote_addr"(客户端 IP 地址), "server_addr"(服务端 IP 地址), 请求头中的"X-Forwarded-For" 或 "X-Real-IP", "consumer_name"(consumer 的 username)。',
+  'component.pluginForm.limit-conn.rejected_code.tooltip': '当请求超过 conn + burst 这个阈值时,返回的 HTTP 状态码。',
+
   'component.plugin.form': '表单',
   'component.plugin.format-codes.disable': '用于格式化 JSON 或 YAML 内容',
   'component.plugin.editor': '插件配置',