├── requirements.txt
├── Makefile
├── documents
├── titaniadb-sentinel.jpg
├── titaniadb-sentinel.png
└── titaniadb_architecture.PNG
├── image
└── titaniadb_sentinel_fig.png
├── test
├── images
│ └── titaniadb-sentinel-test.png
├── documents
│ └── titaniadb-sentinel-test.png
├── init-mysql.sh
├── db
│ └── mysql_init
│ │ └── make_databases.sql
├── README.md
├── docker-compose.yml
└── src
│ └── test_etcd.py
├── shell
├── exec.sh
└── run.sh
├── src
├── examples
│ ├── mysql.py
│ ├── data.py
│ └── etcd.py
├── etcd.py
├── main.py
└── mysql.py
├── Dockerfile
├── k8s
├── production
│ └── deployment.yml
└── development
│ └── deployment.yml
├── docker-build.sh
├── LICENSE
└── README.md
/requirements.txt:
--------------------------------------------------------------------------------
1 | etcd3
2 | mysqlclient==1.4.6
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | docker-build:
2 | bash ./docker-build.sh
3 |
4 | docker-push:
5 | bash ./docker-build.sh push
--------------------------------------------------------------------------------
/documents/titaniadb-sentinel.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latonaio/titaniadb-sentinel/HEAD/documents/titaniadb-sentinel.jpg
--------------------------------------------------------------------------------
/documents/titaniadb-sentinel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latonaio/titaniadb-sentinel/HEAD/documents/titaniadb-sentinel.png
--------------------------------------------------------------------------------
/image/titaniadb_sentinel_fig.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latonaio/titaniadb-sentinel/HEAD/image/titaniadb_sentinel_fig.png
--------------------------------------------------------------------------------
/documents/titaniadb_architecture.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latonaio/titaniadb-sentinel/HEAD/documents/titaniadb_architecture.PNG
--------------------------------------------------------------------------------
/test/images/titaniadb-sentinel-test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latonaio/titaniadb-sentinel/HEAD/test/images/titaniadb-sentinel-test.png
--------------------------------------------------------------------------------
/test/documents/titaniadb-sentinel-test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latonaio/titaniadb-sentinel/HEAD/test/documents/titaniadb-sentinel-test.png
--------------------------------------------------------------------------------
/shell/exec.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | POD_ID=$(kubectl get po | awk '{print $1}' | grep -v NAME | grep titaniadb-sentinel)
4 | kubectl exec -it ${POD_ID} -- bash
--------------------------------------------------------------------------------
/shell/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | POD_ID=$(kubectl get po | awk '{print $1}' | grep -v NAME | grep titaniadb-sentinel)
4 | kubectl exec -it ${POD_ID} -- python3 -u main.py
--------------------------------------------------------------------------------
/test/init-mysql.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | docker-compose exec db bash -c "chmod 0775 docker-entrypoint-initdb.d/init-database.sh"
3 | docker-compose exec db bash -c "./docker-entrypoint-initdb.d/init-database.sh"
4 |
--------------------------------------------------------------------------------
/src/examples/mysql.py:
--------------------------------------------------------------------------------
1 | from src import mysql
2 | from src.examples import data
3 |
4 |
5 | def main():
6 | with mysql.ProjectTable() as client:
7 | client.create_database()
8 | client.create_table()
9 | client.upsert(data.project1)
10 | return
11 |
12 |
13 | if __name__ == "__main__":
14 | main()
15 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM l4t:latest
2 |
3 | # Definition of a Device & Service
4 | ENV POSITION=Runtime \
5 | SERVICE=titaniadb-sentinel \
6 | AION_HOME=/var/lib/aion \
7 | MY_MYSQL_HOST=mysql \
8 | MY_MYSQL_USER=MYSQL_USER_XXX \
9 | MY_ETCD_HOST=titaniadb
10 |
11 | RUN apt-get update && apt-get install -y libmysqlclient-dev
12 |
13 | # Setup Directoties
14 | RUN mkdir -p \
15 | $POSITION/$SERVICE
16 | WORKDIR ${AION_HOME}/$POSITION/$SERVICE/
17 |
18 | ADD requirements.txt .
19 |
20 | RUN pip3 install -r requirements.txt
21 |
22 | ADD src/ .
23 |
24 | CMD ["python3", "-u", "main.py"]
25 |
--------------------------------------------------------------------------------
/k8s/production/deployment.yml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: titaniadb-sentinel
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | role: titaniadb-sentinel
10 | template:
11 | metadata:
12 | labels:
13 | role: titaniadb-sentinel
14 | spec:
15 | shareProcessNamespace: true
16 | containers:
17 | - name: titaniadb-sentinel
18 | image: latonaio/titaniadb-sentinel:latest
19 | imagePullPolicy: IfNotPresent
20 | env:
21 | - name: MY_MYSQL_PASSWORD
22 | value: MYSQL_PASSWORD_XXX
23 |
--------------------------------------------------------------------------------
/test/db/mysql_init/make_databases.sql:
--------------------------------------------------------------------------------
1 | CREATE DATABASE Device;
2 | CREATE DATABASE Pod;
3 | use Device;
4 |
5 | CREATE TABLE device (
6 | deviceName VARCHAR(20),
7 | projectSymbolFk VARCHAR(20),
8 | deviceIp VARCHAR(20),
9 | connectionStatus INT NOT NULL PRIMARY KEY,
10 | os VARCHAR(20));
11 |
12 | use Pod;
13 |
14 | CREATE TABLE pod(
15 | podName VARCHAR(20)NOT NULL PRIMARY KEY,
16 | deviceNameFk VARCHAR(20),
17 | imageName VARCHAR(20),
18 | currentVersion VARCHAR(20) ,
19 | latestVersion VARCHAR(20),
20 | deployedAt TIMESTAMP,
21 | status VARCHAR(20));
--------------------------------------------------------------------------------
/docker-build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | PUSH=$1
4 | DATE="$(date "+%Y%m%d%H%M")"
5 | REPOSITORY_PREFIX="latonaio"
6 | SERVICE_NAME="titaniadb-sentinel"
7 |
8 | DOCKER_BUILDKIT=1 docker build --progress=plain -t ${SERVICE_NAME}:"${DATE}" .
9 |
10 | # tagging
11 | docker tag ${SERVICE_NAME}:"${DATE}" ${SERVICE_NAME}:latest
12 | docker tag ${SERVICE_NAME}:"${DATE}" ${REPOSITORY_PREFIX}/${SERVICE_NAME}:"${DATE}"
13 | docker tag ${REPOSITORY_PREFIX}/${SERVICE_NAME}:"${DATE}" ${REPOSITORY_PREFIX}/${SERVICE_NAME}:latest
14 |
15 | if [[ $PUSH == "push" ]]; then
16 | docker push ${REPOSITORY_PREFIX}/${SERVICE_NAME}:"${DATE}"
17 | docker push ${REPOSITORY_PREFIX}/${SERVICE_NAME}:latest
18 | fi
--------------------------------------------------------------------------------
/k8s/development/deployment.yml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: titaniadb-sentinel
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | role: titaniadb-sentinel
10 | template:
11 | metadata:
12 | labels:
13 | role: titaniadb-sentinel
14 | spec:
15 | containers:
16 | - name: titaniadb-sentinel
17 | command: ["/bin/sh", "-c", "while :; do sleep 10000; done"]
18 | image: latonaio/titaniadb-sentinel:latest
19 | imagePullPolicy: IfNotPresent
20 | env:
21 | - name: MY_MYSQL_PASSWORD
22 | value: MYSQL_PASSWORD_XXX
23 | volumeMounts:
24 | - name: src
25 | mountPath: /var/lib/aion/Runtime/titaniadb-sentinel/
26 | volumes:
27 | - name: src
28 | hostPath:
29 | path: /home/latona/vega/Runtime/titaniadb-sentinel/src
30 |
--------------------------------------------------------------------------------
/src/examples/data.py:
--------------------------------------------------------------------------------
1 | import datetime
2 |
3 |
4 | project1 = {
5 | 'projectSymbol': '1234567890abcdef',
6 | 'projectName': 'exampleProject',
7 | 'projectID': 'exampleProjectID',
8 | 'timestamp': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
9 | }
10 |
11 | device1 = {
12 | 'deviceName': 'titania',
13 | 'projectSymbolFk': '1234567890abcdef',
14 | 'deviceIp': '192.168.128.161',
15 | 'connectionStatus': 1,
16 | 'os': 'ubuntu:18.04'
17 | }
18 |
19 | pod1 = {
20 | 'podName': 'podName',
21 | 'deviceNameFk': 'titania',
22 | 'imageName': 'nginx',
23 | 'currentVersion': 'currentVersion',
24 | 'latestVersion': 'latestVersion',
25 | 'deployedAt': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
26 | 'status': 'Running'
27 | }
28 |
29 | pod2 = {
30 | 'podName': 'podName2',
31 | 'deviceNameFk': 'titania',
32 | 'currentVersion': 'currentVersion',
33 | 'latestVersion': 'latestVersion',
34 | 'deployedAt': '',
35 | 'status': 'Running'
36 | }
37 |
--------------------------------------------------------------------------------
/test/README.md:
--------------------------------------------------------------------------------
1 | ## titaniadb-sentinel-test
2 | titaniadb-sentinel-testは、titaniadb-sentinelのテスト用mockサーバーです。
3 |
4 |
5 | ## 概要
6 | titaniadb-sentinel-testは、etcd、MySQL、ubuntuの三つのコンテナによって構成されています。titaniadb-sentinel-testを使用することで、titaniadb-sentinelの動作とOutputの確認を行うことができます。
7 |
8 | ## 起動方法
9 | 以下のコマンドによりtitaniadb-sentinel-testを起動してください。
10 |
11 | ```
12 | $ cd test
13 | $ docker-compose up
14 | ```
15 |
16 | ## Input
17 | JSON形式のデータ。etcdに送られるデータの内容はtest_etcd.pyに記載されています。
18 |
19 | ## Output
20 | MySQLへのデータ入力。MySQL内に保存されたデータは以下のコマンドによって確認することができます。
21 |
22 | ```
23 | $ cd titaniadb-sentinel
24 | $ docker exec -it [コンテナ名] bash
25 | $ docker mysql -u root -p
26 | $ use [データベース名]
27 | $ select * from [テーブル名]
28 | ```
29 |
30 | ## システム構造
31 | 
32 |
33 | ## 使用したdocker image
34 | ### bitnami/etcd
35 | -https://hub.docker.com/r/bitnami/etcd/dockerfile
36 | ### ubuntu
37 | -https://hub.docker.com/_/ubuntu
38 | ### mysql
39 | -https://hub.docker.com/_/mysql
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Latona, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.5'
2 |
3 | services:
4 | etcd:
5 | image: bitnami/etcd:3.4.3
6 | environment:
7 | - ALLOW_NONE_AUTHENTICATION=yes
8 | - ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379
9 | ports:
10 | - 2379:2379
11 | - 2380:2380
12 | networks:
13 | - etcdtest
14 |
15 | db:
16 | image: mysql:5.7
17 | environment:
18 | MYSQL_ROOT_PASSWORD: "root"
19 | MYSQL_DATABASE: "mock_test_database"
20 | MYSQL_USER: "dbtest"
21 | MYSQL_PASSWORD: "pass_for_dbtest"
22 | TZ: 'Asia/Tokyo'
23 | command: mysqld --innodb-use-native-aio=0
24 | volumes:
25 | - ./db/mysql_init:/docker-entrypoint-initdb.d
26 | - db-data:/var/lib/mysql
27 | ports:
28 | - 3306:3306
29 |
30 | docker_etcd_test:
31 | image: ubuntu
32 | dns: 8.8.8.8
33 | environment:
34 | TZ: 'Asia/Tokyo'
35 | volumes:
36 | - ./src:/home/src
37 | working_dir: /home/src
38 | command: bash -c "apt-get update && apt-get install -y python3 python3-pip && apt-get update && apt-get update -y && pip3 install etcd3 && python3 -u test_etcd.py"
39 | networks:
40 | - etcdtest
41 |
42 | volumes:
43 | db-data:
44 | driver: local
45 |
46 | networks:
47 | etcdtest:
48 | name: etcdtest
49 | driver: bridge
50 |
--------------------------------------------------------------------------------
/test/src/test_etcd.py:
--------------------------------------------------------------------------------
1 | import etcd3
2 | import os
3 | import datetime
4 | import time
5 | import json
6 |
7 | pr1 = {
8 | 'projectSymbol': '1234567890abcdef',
9 | 'projectName': 'exampleProject',
10 | 'projectID': 'exampleProjectID',
11 | 'timestamp': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
12 | }
13 |
14 | d1 = {
15 | 'deviceName': 'titania', #'titania'
16 | 'projectSymbolFk': '1234567890abcdef',
17 | 'deviceIp':'192.168.128.161', #
18 | 'connectionStatus': repr(1), #'1'
19 | 'os': 'ubuntu:18.04'
20 | }
21 |
22 | p1 = {
23 | 'podName': 'podName',
24 | 'deviceNameFk': 'titania',
25 | 'imageName': 'nginx',
26 | 'currentVersion': 'currentVersion', #'currentVersion'
27 | 'latestVersion': 'latestVersion',
28 | 'deployedAt': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
29 | 'status': 'Running'
30 | }
31 |
32 | p2 = {
33 | 'podName': 'podName2',
34 | 'deviceNameFk': 'titania',
35 | 'currentVersion': 'currentVersion',
36 | 'latestVersion': 'latestVersion',
37 | 'deployedAt': '',
38 | 'status': 'Running'
39 | }
40 |
41 | #convert to json
42 | pr1_j = json.dumps(pr1)
43 | d1_j = json.dumps(d1)
44 | p1_j = json.dumps(p1)
45 | p2_j = json.dumps(p2)
46 |
47 | project1 = ["/Project/1/1", pr1_j]
48 | device1 = ["/Device/1/1", d1_j]
49 | pod1 = ["/Pod/1/1", p1_j]
50 | pod2 = ["/Pod/2/1", p2_j]
51 |
52 |
53 | client = etcd3.client(host = 'etcd', port = 2379)
54 | KV_list = [project1, device1, pod1, pod2]
55 |
56 | for KVs in KV_list :
57 | Key = KVs[0]
58 | Value = KVs[1]
59 | client.put(Key, Value)
60 | time.sleep(5)
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ***
5 |
6 | # titaniadb-sentinel
7 | titaniadb-sentinelは、エッジ上で、titaniadb(etcd) を対象として、データベースの監視、制御を行うマイクロサービスです。
8 | 
9 |
10 | ## Description
11 | titaniadb-sentinel は、etcdをラップしたエッジ上のtitaniadbと連携します。
12 | このマイクロサービスは、titaniadb-sentinel が収集したデータが蓄積されたtitaniadbにおいて、データの置換、挿入が正しく行われるよう監視、制御することができます。
13 |
14 | titaniadbは、エッジ上の IP、MACアドレス、kubernetesノードの状態、ポッドの状態など、エッジ間の安定したデータインタフェースや処理に重要なIoTメタデータをデータベースに蓄積しますが、titaniadb-sentinelは、エッジ上で、これらのデータの置換、挿入が正しく行われるよう巡回、制御します。
15 |
16 |
17 | ## 依存関係
18 |
19 | - [distributed-service-discovery](https://github.com/latonaio/distributed-service-discovery)
20 | - [gossip-propagation-d](https://github.com/latonaio/gossip-propagation-d)
21 |
22 |
23 | ## etcd
24 | etcdは、オープンソースで分散型のキーバリューストアです。etcd はkubernetes のプライマリーデータストアとして採用されており、kubernetesクラスタの情報を保存、複製しています。
25 | Github URL: https://github.com/etcd-io/etcd
26 | ## Input/Output
27 | エッジクラスター内の各エッジノード端末において、titaniadbと連携して、データの制御、監視を行います。具体的には、titaniadbのデータを監視、取得、更新、追加、削除したり、ログ、エラー等のイベントの表示をしたりします。
28 | また、MySQLに静的な結果データのアップデートを行います。
29 |
30 | ## Install
31 | ```
32 | $git clone git@bitbucket.org:latonaio/titaniadb-sentinel.git
33 |
34 | $cd titaniadb_sentinel
35 |
36 | $make docker-build
37 | ```
38 |
39 | ## File Contents
40 | * etcd.py
41 | etcdに蓄積されたデータのkey、ID、metadataの取得や、それらのデータの挿入、追加、削除、に対するresponseを定義しています。
42 | Baseクラスでは、etcdにストアされたデータの監視、制御の開始、keyの取得、挿入、削除を定義しています。
43 | Deviceクラス、Podクラスでは、Baseクラスを継承し、それぞれDevice、Podに関するデータの操作を定義します。
44 | * main.py
45 | etcd.py、mysql.py で定義された内容に従って処理を実行し、各データベースのupsertや、処理内容、エラー内容の出力を行います。
46 | upsert_at_eventメソッドでは、eventのtypeに従って処理を行います。
47 | * mysql.py
48 | MySQLへの接続、カーソルの取得、データの操作など、データベース内のデータの操作方法を定義しています。
49 | BaseTableクラスは、データベースへの接続、カーソルの取得、データの取得などを定義しています。DeviceTableクラス、PodTbaleクラスでは、BaseTableクラスを継承し、それぞれDevice、Podに関するデータの操作を定義します。
50 | * deployment.yml
51 | deploymentは、PodとReplicaSetの宣言的なアップデート機能を提供するために作成されるyamlファイルです。
52 | * Dockerfile
53 | * Makefike
54 | * docker-build.sh
55 | * requirements.txt
56 |
--------------------------------------------------------------------------------
/src/etcd.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 |
4 | import etcd3
5 |
6 |
7 | # Localhost
8 | HOST = os.environ.get('MY_ETCD_HOST', '0.0.0.0')
9 | PORT = int(os.environ.get('MY_ETCD_PORT', 2379))
10 | TYPE_POSITION_INDEX = 1
11 | STATUS_POSITION_INDEX = 3
12 | RUNNINX_STATUS_INDEX = "0"
13 | STOPPED_STATUS_INDEX = "1"
14 | DEVICE_DATA_TYPE = "Device"
15 | POD_DATA_TYPE = "Pod"
16 |
17 |
18 | class Base(etcd3.Etcd3Client):
19 | def __init__(self):
20 | super().__init__(host=HOST, port=PORT)
21 |
22 | def watch_start(self): #prefixを持つkeyをwatchする.
23 | events_iter, self.cancel = self.watch_prefix(self.BASE_KEY)
24 | for event in events_iter:
25 | yield event
26 | return
27 |
28 | #keyは、/self.BASE_KEY/ID の形でストアされている.
29 | def get_dicts(self): #prefixを持つkeyのmetadata, valueを取得し、pod_data_typeなどの状態をdictsに入れる.
30 | tuples = self.get_prefix(self.BASE_KEY, sort_order='ascend')
31 | dicts = []
32 | for b_value, b_metadata in tuples:
33 | value = b_value.decode('utf-8')
34 | _dict = json.loads(value)
35 |
36 | key = b_metadata.key.decode('utf-8')
37 | keyArr = key.split("/")
38 | if keyArr[STATUS_POSITION_INDEX] == STOPPED_STATUS_INDEX:
39 | if keyArr[TYPE_POSITION_INDEX] == DEVICE_DATA_TYPE:
40 | _dict["connectionStatus"] = 1
41 | elif keyArr[TYPE_POSITION_INDEX] == POD_DATA_TYPE:
42 | _dict["status"] = 1
43 |
44 | dicts.append(_dict)
45 | return dicts
46 |
47 | def get_dict(self, _id):
48 | key = f'{self.BASE_KEY}/{_id}'
49 | b_value, _ = self.get(key)
50 | if b_value is None:
51 | return b_value
52 |
53 | value = b_value.decode('utf-8')
54 | _dict = json.loads(value)
55 | return _dict
56 |
57 | def put_dict(self, _dict):
58 | _id = _dict[self.ID]
59 | key = f'{self.BASE_KEY}/{_id}'
60 | value = json.dumps(_dict)
61 | response = self.put(key, value)
62 | return response
63 |
64 | def delete(self, _dict):
65 | _id = _dict[self.ID]
66 | key = f'{self.BASE_KEY}/{_id}'
67 | response = super(Base, self).delete(key)
68 | return response
69 |
70 |
71 | class Device(Base):
72 | BASE_KEY = '/Device'
73 | ID = 'deviceName'
74 |
75 | def __init__(self):
76 | super().__init__()
77 |
78 |
79 | class Pod(Base):
80 | BASE_KEY = '/Pod'
81 | ID = 'podName'
82 |
83 | def __init__(self):
84 | super().__init__()
85 |
--------------------------------------------------------------------------------
/src/examples/etcd.py:
--------------------------------------------------------------------------------
1 | import copy
2 | import datetime
3 |
4 | from src import etcd
5 | from src.examples import data
6 |
7 |
8 | class Device():
9 | def __init__(self):
10 | self.etcd_client = etcd.Device()
11 |
12 | def get(self):
13 | _dict = copy.deepcopy(data.device1)
14 | new_dict = self.etcd_client.get_dict(_dict[self.etcd_client.ID])
15 | print(new_dict)
16 | return
17 |
18 | def put(self):
19 | _dict = copy.deepcopy(data.device1)
20 | _dict['deviceIp'] = datetime.datetime.now().isoformat()
21 | _dict['connectionStatus'] = datetime.datetime.now().timestamp()
22 | _dict['os'] = datetime.datetime.now().isoformat()
23 | res = self.etcd_client.put_dict(_dict)
24 | print(res)
25 | return
26 |
27 | def delete(self):
28 | _dict = copy.deepcopy(data.device1)
29 | res = self.etcd_client.delete(_dict)
30 | print(res)
31 | return
32 |
33 | def new(self):
34 | _dict = copy.deepcopy(data.device1)
35 | _dict['deviceName'] = datetime.datetime.now().isoformat()
36 | _dict['deviceIp'] = datetime.datetime.now().isoformat()
37 | _dict['connectionStatus'] = datetime.datetime.now().timestamp()
38 | _dict['os'] = datetime.datetime.now().isoformat()
39 | res = self.etcd_client.put_dict(_dict)
40 | print(res)
41 | return
42 |
43 |
44 | class Pod():
45 | def __init__(self):
46 | self.etcd_client = etcd.Pod()
47 |
48 | def get(self):
49 | _dict = copy.deepcopy(data.pod1)
50 | new_dict = self.etcd_client.get_dict(_dict[self.etcd_client.ID])
51 | print(new_dict)
52 | return
53 |
54 | def put(self):
55 | _dict = copy.deepcopy(data.pod1)
56 | _dict['currentVersion'] = datetime.datetime.now().isoformat()
57 | _dict['latestVersion'] = datetime.datetime.now().isoformat()
58 | _dict['deployedAt'] = datetime.datetime.now().isoformat()
59 | _dict['status'] = datetime.datetime.now().timestamp()
60 | res = self.etcd_client.put_dict(_dict)
61 | print(res)
62 | return
63 |
64 | def delete(self):
65 | _dict = copy.deepcopy(data.pod1)
66 | res = self.etcd_client.delete(_dict)
67 | print(res)
68 | return
69 |
70 | def new(self):
71 | _dict = copy.deepcopy(data.pod1)
72 | _dict['podName'] = datetime.datetime.now().isoformat()
73 | _dict['imageName'] = datetime.datetime.now().isoformat()
74 | _dict['currentVersion'] = datetime.datetime.now().isoformat()
75 | _dict['latestVersion'] = datetime.datetime.now().isoformat()
76 | _dict['deployedAt'] = datetime.datetime.now().isoformat()
77 | _dict['status'] = datetime.datetime.now().timestamp()
78 | res = self.etcd_client.put_dict(_dict)
79 | print(res)
80 | return
81 |
82 |
83 | if __name__ == "__main__":
84 | import argparse
85 | import sys
86 |
87 | parser = argparse.ArgumentParser()
88 | parser.add_argument('name', type=str)
89 | parser.add_argument('method', type=str)
90 | args = parser.parse_args()
91 | name = args.name
92 | method = args.method
93 | print(name, method)
94 |
95 | mapping = {
96 | 'device': Device,
97 | 'pod': Pod,
98 | }
99 | if mapping.get(name) is None:
100 | print(f'Not accept name: {name}')
101 | sys.exit(1)
102 | obj = mapping.get(name)()
103 |
104 | methods = {
105 | 'g': 'get',
106 | 'p': 'put',
107 | 'd': 'delete',
108 | 'n': 'new',
109 | }
110 | if methods.get(method) is None:
111 | print(f'Not accept method: {method}')
112 | sys.exit(1)
113 | new_method = methods.get(method)
114 |
115 | eval(f'obj.{new_method}')()
116 |
--------------------------------------------------------------------------------
/src/main.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | import queue
4 | import threading
5 | import time
6 | import pprint
7 | import traceback
8 |
9 | from aion.logger import lprint, initialize_logger
10 | from mysql import DeviceTable as deviceTable, PodTable as podTable
11 | from etcd import Device as etcdDevice, Pod as etcdPod
12 | from etcd3.events import PutEvent, DeleteEvent
13 |
14 | #環境変数の取得
15 | SERVICE_NAME = os.environ.get("SERVICE", "titaniadb-sentinel")
16 | DEVICE_INTERVAL = os.environ.get("DEVICE_INTERVAL", "30")
17 | POD_INTERVAL = os.environ.get("POD_INTERVAL", "5")
18 | initialize_logger(SERVICE_NAME)
19 |
20 |
21 | def upsert_at_event(etcd_client_class, mysql_client_class, _queue):
22 | try:
23 | lprint(f'etcd Watch {etcd_client_class.BASE_KEY} key prefix.')
24 | etcd_client = etcd_client_class()
25 | for event in etcd_client.watch_start(): #watch_start()はetcd.pyにて定義.接頭辞のあるキーの変更を取得する.
26 | key = None
27 | value = None
28 | key = event.key.decode('utf-8')
29 | value = event.value.decode('utf-8')
30 |
31 | if type(event) == PutEvent:
32 | error_count = 0
33 | while True:
34 | try:
35 | error_count += 1
36 | lprint(f'etcd get Put Event: {key}')
37 | _dict = json.loads(value)
38 | with mysql_client_class() as mysql_client:
39 | mysql_client.upsert(_dict)
40 | lprint(f'mysql upsert: {key}') #Put Eventを検知し、Valueを書き換える.
41 | break
42 | except Exception as e:
43 | if error_count < 5:
44 | lprint(f'Error was occurred at {error_count} times for {key} key. Retry it after sleep.')
45 | time.sleep(2) #Error発生(5回以内)時の表示.
46 | else:
47 | lprint('=' * 50)
48 | lprint(f'event: {event.__class__.__name__}')
49 | lprint(f'key: {key}')
50 | lprint(f'value:')
51 | lprint(value)
52 | lprint('=' * 50)
53 | raise e #5回以上Errorが発生した場合の表示.Error内容と発生箇所を表示する.
54 |
55 | elif type(event) == DeleteEvent:
56 | lprint(f'etcd get Delete Event: {key}')
57 |
58 | else:
59 | raise RuntimeError(f'etcd get unexpected event: {event}')
60 |
61 | except Exception as e:
62 | lprint(e)
63 | message = f'etcd finish to Watch {etcd_client_class.BASE_KEY} key prefix.'
64 | _queue.put(message)
65 | return
66 |
67 | return
68 |
69 |
70 | def upsert_all(etcd_client_class, mysql_client_class, interval, _queue): #すべてのデータの更新/挿入を行う.
71 | try:
72 | while True:
73 | etcd_client = etcd_client_class()
74 | dicts = etcd_client.get_dicts() #get_dicts()はetcd.pyにて定義.get_prefixで取得されたデータが取得される.
75 | with mysql_client_class() as mysql_client:
76 | for _dict in dicts:
77 | mysql_client.upsert(_dict)
78 |
79 | del etcd_client
80 |
81 | lprint(f'Success to upsert {mysql_client_class.__name__}.')
82 | pprint.pprint(dicts)
83 | time.sleep(int(interval))
84 |
85 | except Exception:
86 | lprint('=' * 50)
87 | lprint(traceback.format_exc())
88 | message = f'Faild to upsert all. mysql_client_class class is {mysql_client_class.__name__}.'
89 | _queue.put(message)
90 | return
91 |
92 |
93 | def main():
94 | _queue = queue.Queue()
95 |
96 | thread_device_event = threading.Thread(target=upsert_at_event, args=(etcdDevice, deviceTable, _queue), daemon=True)
97 | thread_pod_event = threading.Thread(target=upsert_at_event, args=(etcdPod, podTable, _queue), daemon=True)
98 | thread_device_interval = threading.Thread(target=upsert_all, args=(etcdDevice, deviceTable, DEVICE_INTERVAL, _queue), daemon=True)
99 | thread_pod_interval = threading.Thread(target=upsert_all, args=(etcdPod, podTable, POD_INTERVAL, _queue), daemon=True)
100 |
101 | thread_device_event.start()
102 | thread_pod_event.start()
103 | time.sleep(5)
104 | thread_device_interval.start()
105 | thread_pod_interval.start()
106 |
107 | message = _queue.get()
108 | lprint('=' * 50)
109 | lprint(message)
110 | lprint('Cancel all threads.')
111 | return
112 |
113 |
114 | if __name__ == "__main__":
115 | main()
116 |
--------------------------------------------------------------------------------
/src/mysql.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | import sys
4 |
5 | import MySQLdb
6 |
7 |
8 | # Localhost
9 | HOST = os.environ.get('MY_MYSQL_HOST', '127.0.0.1')
10 | PORT = int(os.environ.get('MY_MYSQL_PORT', "3306"))
11 | USER = os.environ.get('MY_MYSQL_USER')
12 | PASSWORD = os.environ.get('MY_MYSQL_PASSWORD')
13 |
14 | # re compile at first
15 | re_eof = re.compile(r'\n')
16 | re_space = re.compile(r'\s+')
17 |
18 |
19 | def format_sql(sql):
20 | sql = re_eof.sub(' ', sql)
21 | sql = re_space.sub(' ', sql)
22 | if sys.flags.debug:
23 | print(sql)
24 | return sql
25 |
26 |
27 | class BaseTable():
28 | def __enter__(self): #DBに接続し、カーソルを取得する.
29 | try:
30 | self.connection = MySQLdb.connect(
31 | host=HOST, port=PORT, user=USER, passwd=PASSWORD, charset='utf8')
32 | self.cursor = self.connection.cursor(MySQLdb.cursors.DictCursor)
33 | except MySQLdb.Error as e:
34 | print('Faild to connect MySQL Server.')
35 | raise e
36 | return self
37 |
38 | def __exit__(self, exception_type, exception_value, traceback): #カーソルを閉じる
39 | if self.cursor:
40 | self.cursor.close()
41 | self.connection.close()
42 | return
43 |
44 | def is_connect(self):
45 | return bool(self.cursor)
46 |
47 | def get_query(self, sql, args=None): #一件ずつデータを取得する
48 | if not self.cursor:
49 | return None
50 | self.cursor.execute(sql, args)
51 | return self.cursor.fetchone()
52 |
53 | def get_query_list(self, sql, size, args=None):
54 | if not self.cursor:
55 | return None
56 | self.cursor.execute(sql, args)
57 | return self.cursor.fetchmany(size)
58 |
59 | def set_query(self, sql, args=None):
60 | if not self.cursor:
61 | return False
62 | self.cursor.execute(sql, args)
63 | return True
64 |
65 | def commit_query(self):
66 | self.connection.commit()
67 | return
68 |
69 |
70 | class DeviceTable(BaseTable):
71 | DATABASE_NAME = 'Device'
72 | TABLE_NAME = 'device'
73 |
74 | def select(self, deviceName):
75 | sql = f'''
76 | SELECT *
77 | FROM {self.DATABASE_NAME}.{self.TABLE_NAME}
78 | WHERE deviceName = '{deviceName}';
79 | '''
80 | sql = format_sql(sql)
81 | _dict = self.get_query(sql)
82 | return _dict
83 |
84 | def upsert(self, _dict):
85 | sql = f"""
86 | INSERT INTO {self.DATABASE_NAME}.{self.TABLE_NAME} (
87 | deviceName, projectSymbolFk, deviceIp, connectionStatus, os
88 | ) VALUES (
89 | '{_dict['deviceName']}', '{_dict['projectSymbolFk']}',
90 | '{_dict['deviceIp']}', '{_dict['connectionStatus']}', '{_dict['os']}'
91 | )
92 | ON DUPLICATE KEY UPDATE
93 | deviceName = '{_dict['deviceName']}',
94 | projectSymbolFk = '{_dict['projectSymbolFk']}',
95 | deviceIp = '{_dict['deviceIp']}',
96 | connectionStatus = '{_dict['connectionStatus']}',
97 | os = '{_dict['os']}';
98 | """
99 | sql = format_sql(sql)
100 | self.set_query(sql)
101 | self.commit_query()
102 | return
103 |
104 | def delete(self):
105 | sql = f"""
106 | DELETE FROM {self.DATABASE_NAME}.{self.TABLE_NAME}
107 | """
108 | sql = format_sql(sql)
109 | self.set_query(sql)
110 | self.commit_query()
111 | return
112 |
113 |
114 | class PodTable(BaseTable):
115 | DATABASE_NAME = 'Pod'
116 | TABLE_NAME = 'pod'
117 |
118 | def upsert(self, _dict):
119 | # imageName
120 | imageName = _dict.get('imageName')
121 | if imageName is not None:
122 | imageName = f"'{imageName}'"
123 | else:
124 | imageName = 'NULL'
125 | # deployedAt
126 | if _dict['deployedAt']:
127 | deployedAt = f"'{_dict['deployedAt']}'"
128 | else:
129 | deployedAt = 'NULL'
130 |
131 | sql = f"""
132 | INSERT INTO {self.DATABASE_NAME}.{self.TABLE_NAME} (
133 | podName, deviceNameFk, imageName, currentVersion, latestVersion, deployedAt, status
134 | ) VALUES (
135 | '{_dict['podName']}', '{_dict['deviceNameFk']}', {imageName},
136 | '{_dict['currentVersion']}', '{_dict['latestVersion']}',
137 | {deployedAt}, '{_dict['status']}'
138 | )
139 | ON DUPLICATE KEY UPDATE
140 | podName = '{_dict['podName']}',
141 | deviceNameFk = '{_dict['deviceNameFk']}',
142 | imageName = {imageName},
143 | currentVersion = '{_dict['currentVersion']}',
144 | latestVersion = '{_dict['latestVersion']}',
145 | deployedAt = {deployedAt},
146 | status = '{_dict['status']}';
147 | """
148 | sql = format_sql(sql)
149 | self.set_query(sql)
150 | self.commit_query()
151 | return
152 |
153 | def delete(self):
154 | sql = f"""
155 | DELETE FROM {self.DATABASE_NAME}.{self.TABLE_NAME}
156 | """
157 | sql = format_sql(sql)
158 | self.set_query(sql)
159 | self.commit_query()
160 | return
161 |
--------------------------------------------------------------------------------