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 {