You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by hu...@apache.org on 2023/01/09 08:11:14 UTC

[iotdb] branch master updated: [IoTDB-5374] Implement MLNode basic service framework (#8772)

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

hui pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new d55ca83df7 [IoTDB-5374] Implement MLNode basic service framework (#8772)
d55ca83df7 is described below

commit d55ca83df7fd19c50e346a7175d3d5e0ff5fb2df
Author: Yong Liu <li...@gmail.com>
AuthorDate: Mon Jan 9 16:11:08 2023 +0800

    [IoTDB-5374] Implement MLNode basic service framework (#8772)
    
    Co-authored-by: Minghui Liu <li...@foxmail.com>
---
 mlnode/.flake8                              |  33 +++++++++
 mlnode/.gitignore                           |   5 ++
 mlnode/README.md                            |  47 ++++++++++++
 mlnode/iotdb/__init__.py                    |  17 +++++
 mlnode/iotdb/mlnode/__init__.py             |  17 +++++
 mlnode/iotdb/mlnode/client.py               |  51 +++++++++++++
 mlnode/iotdb/mlnode/config.py               | 107 ++++++++++++++++++++++++++++
 mlnode/iotdb/mlnode/constant.py             |  21 ++++++
 mlnode/iotdb/mlnode/exception.py            |  26 +++++++
 mlnode/iotdb/mlnode/handler.py              |  53 ++++++++++++++
 mlnode/iotdb/mlnode/log.py                  |  34 +++++++++
 mlnode/iotdb/mlnode/script.py               |  36 ++++++++++
 mlnode/iotdb/mlnode/service.py              |  60 ++++++++++++++++
 mlnode/iotdb/mlnode/util.py                 |  48 +++++++++++++
 mlnode/pyproject.toml                       |  54 ++++++++++++++
 mlnode/resources/conf/iotdb-mlnode.toml     |  50 +++++++++++++
 mlnode/resources/conf/logging_config.ini    |  40 +++++++++++
 thrift-mlnode/src/main/thrift/mlnode.thrift |  10 +--
 18 files changed, 704 insertions(+), 5 deletions(-)

diff --git a/mlnode/.flake8 b/mlnode/.flake8
new file mode 100644
index 0000000000..ce062a0252
--- /dev/null
+++ b/mlnode/.flake8
@@ -0,0 +1,33 @@
+# 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.
+#
+[flake8]
+ignore =
+    E203,
+    W503
+max-line-length=200
+exclude =
+    .git,
+    tests/*,
+    iotdb/thrift/**/*
+extend-exclude =
+    dist,
+    build,
+    venv
+show-source = True
+statistics = True
+format = %(path)s:%(row)d,%(col)d:%(code)s:%(text)s:https://lintlyci.github.io/Flake8Rules/rules/%(code)s.html
diff --git a/mlnode/.gitignore b/mlnode/.gitignore
index ff6532cd4e..ba68b5e54e 100644
--- a/mlnode/.gitignore
+++ b/mlnode/.gitignore
@@ -1 +1,6 @@
 /iotdb/thrift/
+
+# generated by Pypi
+/build/
+/dist/
+/*.egg-info/
diff --git a/mlnode/README.md b/mlnode/README.md
new file mode 100644
index 0000000000..d892d2273e
--- /dev/null
+++ b/mlnode/README.md
@@ -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.
+
+-->
+
+# Apache IoTDB MLNode
+
+For developers, you can start an ML Node through the following steps.
+
+- Step 1: build project
+
+```shell
+mvn clean package -DskipUTs -pl mlnode -am
+```
+
+```shell
+cd mlnode
+poetry build
+```
+
+- Step 2: install
+
+```shell
+pip install dist/apache_iotdb_mlnode-1.0.0-py3-none-any.whl --force-reinstall
+```
+
+- Step 3: start node
+
+```shell
+mlnode start
+```
\ No newline at end of file
diff --git a/mlnode/iotdb/__init__.py b/mlnode/iotdb/__init__.py
new file mode 100644
index 0000000000..2a1e720805
--- /dev/null
+++ b/mlnode/iotdb/__init__.py
@@ -0,0 +1,17 @@
+# 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.
+#
diff --git a/mlnode/iotdb/mlnode/__init__.py b/mlnode/iotdb/mlnode/__init__.py
new file mode 100644
index 0000000000..2a1e720805
--- /dev/null
+++ b/mlnode/iotdb/mlnode/__init__.py
@@ -0,0 +1,17 @@
+# 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.
+#
diff --git a/mlnode/iotdb/mlnode/client.py b/mlnode/iotdb/mlnode/client.py
new file mode 100644
index 0000000000..08a6b925c2
--- /dev/null
+++ b/mlnode/iotdb/mlnode/client.py
@@ -0,0 +1,51 @@
+# 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.
+#
+from thrift.protocol import TCompactProtocol
+from thrift.transport import TSocket, TTransport
+
+from iotdb.mlnode.log import logger
+from iotdb.thrift.mlnode import IMLNodeRPCService
+from iotdb.thrift.mlnode.ttypes import TDeleteModelReq
+
+
+class MLNodeClient(object):
+    def __init__(self, host, port):
+        self.__host = host
+        self.__port = port
+
+        transport = TTransport.TBufferedTransport(
+            TSocket.TSocket(self.__host, self.__port)
+        )
+        if not transport.isOpen():
+            try:
+                transport.open()
+            except TTransport.TTransportException as e:
+                logger.exception("TTransportException!", exc_info=e)
+
+        protocol = TCompactProtocol.TCompactProtocol(transport)
+        self.__client = IMLNodeRPCService.Client(protocol)
+
+    def delete_model(self, model_path: str):
+        req = TDeleteModelReq(model_path)
+        return self.__client.deleteModel(req)
+
+
+if __name__ == "__main__":
+    # test rpc service
+    client = MLNodeClient(host="127.0.0.1", port=10810)
+    print(client.delete_model("test_model_path"))
diff --git a/mlnode/iotdb/mlnode/config.py b/mlnode/iotdb/mlnode/config.py
new file mode 100644
index 0000000000..64155be03c
--- /dev/null
+++ b/mlnode/iotdb/mlnode/config.py
@@ -0,0 +1,107 @@
+# 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 os
+
+from dynaconf import Dynaconf
+
+from iotdb.mlnode.constant import (MLNODE_CONF_DIRECTORY_NAME,
+                                   MLNODE_CONF_FILE_NAME)
+from iotdb.mlnode.exception import BadNodeUrlError
+from iotdb.mlnode.log import logger
+from iotdb.mlnode.util import parse_endpoint_url
+from iotdb.thrift.common.ttypes import TEndPoint
+
+
+class MLNodeConfig(object):
+    def __init__(self):
+        # Used for connection of DataNode/ConfigNode clients
+        self.__mn_rpc_address: str = "127.0.0.1"
+        self.__mn_rpc_port: int = 10810
+
+        # Target ConfigNode to be connected by MLNode
+        self.__mn_target_config_node: TEndPoint = TEndPoint("127.0.0.1", 10710)
+
+        # Target DataNode to be connected by MLNode
+        self.__mn_target_data_node: TEndPoint = TEndPoint("127.0.0.1", 10730)
+
+    def get_mn_rpc_address(self) -> str:
+        return self.__mn_rpc_address
+
+    def set_mn_rpc_address(self, mn_rpc_address: str) -> None:
+        self.__mn_rpc_address = mn_rpc_address
+
+    def get_mn_rpc_port(self) -> int:
+        return self.__mn_rpc_port
+
+    def set_mn_rpc_port(self, mn_rpc_port: int) -> None:
+        self.__mn_rpc_port = mn_rpc_port
+
+    def get_mn_target_config_node(self) -> TEndPoint:
+        return self.__mn_target_config_node
+
+    def set_mn_target_config_node(self, mn_target_config_node: str) -> None:
+        self.__mn_target_config_node = parse_endpoint_url(mn_target_config_node)
+
+    def get_mn_target_data_node(self) -> TEndPoint:
+        return self.__mn_target_data_node
+
+    def set_mn_target_data_node(self, mn_target_data_node: str) -> None:
+        self.__mn_target_data_node = parse_endpoint_url(mn_target_data_node)
+
+
+class MLNodeDescriptor(object):
+    def __init__(self):
+        self.__config = MLNodeConfig()
+        self.__load_config_from_file()
+
+    def __load_config_from_file(self) -> None:
+        conf_file = os.path.join(os.getcwd(), MLNODE_CONF_DIRECTORY_NAME, MLNODE_CONF_FILE_NAME)
+        if not os.path.exists(conf_file):
+            logger.info("Cannot find MLNode config file '{}', use default configuration.".format(conf_file))
+            return
+
+        logger.info("Start to read MLNode config file '{}'...".format(conf_file))
+
+        # noinspection PyBroadException
+        try:
+            file_configs = Dynaconf(
+                envvar_prefix="DYNACONF",
+                settings_files=[conf_file],
+            )
+
+            if file_configs.mn_rpc_address is not None:
+                self.__config.set_mn_rpc_address(file_configs.mn_rpc_address)
+
+            if file_configs.mn_rpc_port is not None:
+                self.__config.set_mn_rpc_port(file_configs.mn_rpc_port)
+
+            if file_configs.mn_target_config_node is not None:
+                self.__config.set_mn_target_config_node(file_configs.mn_target_config_node)
+
+            if file_configs.mn_target_data_node is not None:
+                self.__config.set_mn_target_data_node(file_configs.mn_target_data_node)
+        except BadNodeUrlError:
+            logger.warn("Cannot load MLNode conf file, use default configuration.")
+        except Exception as e:
+            logger.warn("Cannot load MLNode conf file, use default configuration. {}".format(e))
+
+    def get_config(self) -> MLNodeConfig:
+        return self.__config
+
+
+config = MLNodeDescriptor().get_config()
diff --git a/mlnode/iotdb/mlnode/constant.py b/mlnode/iotdb/mlnode/constant.py
new file mode 100644
index 0000000000..95f25f506c
--- /dev/null
+++ b/mlnode/iotdb/mlnode/constant.py
@@ -0,0 +1,21 @@
+# 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.
+#
+
+MLNODE_CONF_DIRECTORY_NAME = "conf"
+MLNODE_CONF_FILE_NAME = "iotdb-mlnode.toml"
+MLNODE_LOG_CONF_FILE_NAME = "logging_config.ini"
diff --git a/mlnode/iotdb/mlnode/exception.py b/mlnode/iotdb/mlnode/exception.py
new file mode 100644
index 0000000000..350916a665
--- /dev/null
+++ b/mlnode/iotdb/mlnode/exception.py
@@ -0,0 +1,26 @@
+# 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.
+#
+
+class _BaseError(Exception):
+    """Base class for exceptions in this module."""
+    pass
+
+
+class BadNodeUrlError(_BaseError):
+    def __init__(self, node_url: str):
+        self.message = "Bad node url: {}".format(node_url)
diff --git a/mlnode/iotdb/mlnode/handler.py b/mlnode/iotdb/mlnode/handler.py
new file mode 100644
index 0000000000..8a36353d47
--- /dev/null
+++ b/mlnode/iotdb/mlnode/handler.py
@@ -0,0 +1,53 @@
+# 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.
+#
+from enum import Enum
+
+from iotdb.thrift.common.ttypes import TSStatus
+from iotdb.thrift.mlnode import IMLNodeRPCService
+from iotdb.thrift.mlnode.ttypes import (TCreateTrainingTaskReq,
+                                        TDeleteModelReq, TForecastReq,
+                                        TForecastResp)
+
+
+class TSStatusCode(Enum):
+    SUCCESS_STATUS = 200
+
+    def get_status_code(self) -> int:
+        return self.value
+
+
+def get_status(status_code: TSStatusCode, message: str) -> TSStatus:
+    status = TSStatus(status_code.get_status_code())
+    status.message = message
+    return status
+
+
+class MLNodeRPCServiceHandler(IMLNodeRPCService.Iface):
+    def __init__(self):
+        pass
+
+    def deleteModel(self, req: TDeleteModelReq):
+        return get_status(TSStatusCode.SUCCESS_STATUS, "")
+
+    def createTrainingTask(self, req: TCreateTrainingTaskReq):
+        return get_status(TSStatusCode.SUCCESS_STATUS, "")
+
+    def forecast(self, req: TForecastReq):
+        status = get_status(TSStatusCode.SUCCESS_STATUS, "")
+        forecast_result = b'forecast result'
+        return TForecastResp(status, forecast_result)
diff --git a/mlnode/iotdb/mlnode/log.py b/mlnode/iotdb/mlnode/log.py
new file mode 100644
index 0000000000..c63e4adcc5
--- /dev/null
+++ b/mlnode/iotdb/mlnode/log.py
@@ -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.
+#
+import logging
+import os
+from logging.config import fileConfig
+
+from iotdb.mlnode.constant import (MLNODE_CONF_DIRECTORY_NAME,
+                                   MLNODE_LOG_CONF_FILE_NAME)
+
+log_conf_file = os.path.join(os.getcwd(), MLNODE_CONF_DIRECTORY_NAME, MLNODE_LOG_CONF_FILE_NAME)
+if os.path.exists(log_conf_file):
+    fileConfig(log_conf_file)
+else:
+    logging.basicConfig(
+        level=logging.DEBUG,
+        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+    )
+
+logger = logging.getLogger()
diff --git a/mlnode/iotdb/mlnode/script.py b/mlnode/iotdb/mlnode/script.py
new file mode 100644
index 0000000000..b73dfb78c5
--- /dev/null
+++ b/mlnode/iotdb/mlnode/script.py
@@ -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.
+#
+import sys
+
+from iotdb.mlnode.log import logger
+from iotdb.mlnode.service import MLNode
+
+
+def main():
+    arguments = sys.argv
+    if len(arguments) == 1:
+        logger.info("Command line argument must be specified.")
+        return
+
+    argument = sys.argv[1]
+    # TODO(lmh): support more commands
+    if argument == 'start':
+        server = MLNode()
+        server.start()
+    else:
+        logger.info("Unknown argument: {}.".format(argument))
diff --git a/mlnode/iotdb/mlnode/service.py b/mlnode/iotdb/mlnode/service.py
new file mode 100644
index 0000000000..8314dc363e
--- /dev/null
+++ b/mlnode/iotdb/mlnode/service.py
@@ -0,0 +1,60 @@
+# 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 threading
+import time
+
+from thrift.protocol import TCompactProtocol
+from thrift.server import TServer
+from thrift.transport import TSocket, TTransport
+
+from iotdb.mlnode.config import config
+from iotdb.mlnode.handler import MLNodeRPCServiceHandler
+from iotdb.mlnode.log import logger
+from iotdb.thrift.mlnode import IMLNodeRPCService
+
+
+class RPCService(threading.Thread):
+    def __init__(self):
+        super().__init__()
+        processor = IMLNodeRPCService.Processor(handler=MLNodeRPCServiceHandler())
+        transport = TSocket.TServerSocket(host=config.get_mn_rpc_address(), port=config.get_mn_rpc_port())
+        transport_factory = TTransport.TBufferedTransportFactory()
+        protocol_factory = TCompactProtocol.TCompactProtocolFactory()
+
+        self.__pool_server = TServer.TThreadPoolServer(processor, transport, transport_factory, protocol_factory)
+
+    def run(self) -> None:
+        logger.info("The RPC service thread begin to run...")
+        self.__pool_server.serve()
+
+
+class MLNode(object):
+    def __init__(self):
+        self.__rpc_service = RPCService()
+
+    def start(self) -> None:
+        self.__rpc_service.start()
+
+        # sleep 100ms for waiting the rpc server start.
+        time.sleep(0.1)
+        logger.info('IoTDB-MLNode has successfully started.')
+
+
+if __name__ == "__main__":
+    server = MLNode()
+    server.start()
diff --git a/mlnode/iotdb/mlnode/util.py b/mlnode/iotdb/mlnode/util.py
new file mode 100644
index 0000000000..c15ec6b89f
--- /dev/null
+++ b/mlnode/iotdb/mlnode/util.py
@@ -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.
+#
+from iotdb.mlnode.exception import BadNodeUrlError
+from iotdb.mlnode.log import logger
+
+from iotdb.thrift.common.ttypes import TEndPoint
+
+
+def parse_endpoint_url(endpoint_url: str) -> TEndPoint:
+    """ Parse TEndPoint from a given endpoint url.
+
+    Args:
+        endpoint_url: an endpoint url, format: ip:port
+
+    Returns:
+        TEndPoint
+
+    Raises:
+        BadNodeUrlError
+    """
+    split = endpoint_url.split(":")
+    if len(split) != 2:
+        logger.warning("Illegal endpoint url format: {}".format(endpoint_url))
+        raise BadNodeUrlError(endpoint_url)
+
+    ip = split[0]
+    try:
+        port = int(split[1])
+        result = TEndPoint(ip, port)
+        return result
+    except ValueError as e:
+        logger.warning("Illegal endpoint url format: {} ({})".format(endpoint_url, e))
+        raise BadNodeUrlError(endpoint_url)
diff --git a/mlnode/pyproject.toml b/mlnode/pyproject.toml
new file mode 100644
index 0000000000..3944e2910d
--- /dev/null
+++ b/mlnode/pyproject.toml
@@ -0,0 +1,54 @@
+#  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.
+#
+[build-system]
+requires = ["poetry-core>=1.0.0"]
+build-backend = "poetry.core.masonry.api"
+
+[tool.poetry]
+name = "apache-iotdb-mlnode"
+version = "1.0.0"
+description = "Apache IoTDB MLNode"
+readme = "README.md"
+authors = ["Apache Software Foundation <de...@iotdb.apache.org>"]
+license = "Apache License, Version 2.0"
+classifiers = [
+    "Programming Language :: Python :: 3",
+    "License :: OSI Approved :: Apache Software License",
+    "Operating System :: OS Independent",
+    "Topic :: Software Development :: Libraries",
+    "Topic :: Software Development :: Libraries :: Python Modules",
+]
+include = [
+    "iotdb/thrift/*",
+    "iotdb/thrift/common/*",
+    "iotdb/thrift/confignode/*",
+    "iotdb/thrift/datanode/*",
+    "iotdb/thrift/mlnode/*",
+    "resources/conf/*"
+]
+packages = [
+    { include = "iotdb" }
+]
+
+[tool.poetry.dependencies]
+python = "^3.7"
+thrift = "^0.13.0"
+dynaconf = "^3.1.11"
+
+[tool.poetry.scripts]
+mlnode = "iotdb.mlnode.script:main"
\ No newline at end of file
diff --git a/mlnode/resources/conf/iotdb-mlnode.toml b/mlnode/resources/conf/iotdb-mlnode.toml
new file mode 100644
index 0000000000..0c82425ece
--- /dev/null
+++ b/mlnode/resources/conf/iotdb-mlnode.toml
@@ -0,0 +1,50 @@
+#
+# 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.
+#
+
+####################
+### Ml Node RPC Configuration
+####################
+
+# Used for connection of DataNode/ConfigNode clients
+# Could set 127.0.0.1(for local test) or ipv4 address
+# Datatype: String
+mn_rpc_address = "127.0.0.1"
+
+# Used for connection of DataNode/ConfigNode clients
+# Bind with MN_RPC_ADDRESS
+# Datatype: int
+mn_rpc_port = 10810
+
+####################
+### Target Config Node
+####################
+
+# Target ConfigNode to be connected by MLNode
+# Format: ip:port
+# Datatype: String
+mn_target_config_node = "127.0.0.1:10710"
+
+####################
+### Target Data Node
+####################
+
+# Target DataNode to be connected by MLNode
+# Format: ip:port
+# Datatype: String
+mn_target_data_node = "127.0.0.1:10730"
\ No newline at end of file
diff --git a/mlnode/resources/conf/logging_config.ini b/mlnode/resources/conf/logging_config.ini
new file mode 100644
index 0000000000..9b38c39abd
--- /dev/null
+++ b/mlnode/resources/conf/logging_config.ini
@@ -0,0 +1,40 @@
+#
+# 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.
+#
+
+[loggers]
+keys = root
+
+[handlers]
+keys = consoleHandler
+
+[formatters]
+keys = simpleFormatter
+
+[logger_root]
+level = DEBUG
+handlers = consoleHandler
+
+[handler_consoleHandler]
+class = StreamHandler
+level = DEBUG
+formatter = simpleFormatter
+args = (sys.stdout,)
+
+[formatter_simpleFormatter]
+format = %(asctime)s - %(name)s - %(levelname)s - %(message)s
\ No newline at end of file
diff --git a/thrift-mlnode/src/main/thrift/mlnode.thrift b/thrift-mlnode/src/main/thrift/mlnode.thrift
index fb0e479fe7..a5f7943aa5 100644
--- a/thrift-mlnode/src/main/thrift/mlnode.thrift
+++ b/thrift-mlnode/src/main/thrift/mlnode.thrift
@@ -23,10 +23,10 @@ namespace py iotdb.thrift.mlnode
 
 struct TCreateTrainingTaskReq {
   1: required string modelId
-  3: required bool isAuto
-  4: required map<string, string> modelConfigs
-  5: required list<string> queryExpressions
-  6: optional string queryFilter
+  2: required bool isAuto
+  3: required map<string, string> modelConfigs
+  4: required list<string> queryExpressions
+  5: optional string queryFilter
 }
 
 struct TDeleteModelReq {
@@ -35,7 +35,7 @@ struct TDeleteModelReq {
 
 struct TForecastReq {
   1: required string modelPath
-  2: required list<binary> dataset
+  2: required binary dataset
 }
 
 struct TForecastResp {