From 8be0e289aa30f9832b916f14d8821c9c9fe3b62e Mon Sep 17 00:00:00 2001 From: khemendra kumar Date: Tue, 30 Mar 2021 19:34:07 +0530 Subject: [PATCH] Added dockerfile and yaml file Signed-off-by: khemendra kumar Change-Id: I48b30f0e1a0c73f2dcb9180862502c148200382d --- example-apps/ROBO/retail_app/Dockerfile | 19 +- example-apps/ROBO/retail_app/Inventory-be-k8s.yaml | 58 ++++ example-apps/ROBO/retail_app/config.py | 17 +- example-apps/ROBO/retail_app/configs/start.sh | 61 +--- .../ROBO/retail_app/inventry/retail_app.py | 318 ++++++++++++++------- example-apps/ROBO/retail_app/requirements.txt | 7 +- 6 files changed, 295 insertions(+), 185 deletions(-) create mode 100644 example-apps/ROBO/retail_app/Inventory-be-k8s.yaml diff --git a/example-apps/ROBO/retail_app/Dockerfile b/example-apps/ROBO/retail_app/Dockerfile index 3d76af3..cad2cf5 100644 --- a/example-apps/ROBO/retail_app/Dockerfile +++ b/example-apps/ROBO/retail_app/Dockerfile @@ -16,9 +16,13 @@ # Prepare stage for multistage image build ## START OF STAGE0 ## -FROM python:3.6-slim-stretch - +# FROM python:3.6-slim-stretch +# FROM python:3.9 +# FROM python:3.9-slim-buster # # CREATE APP USER ## + +FROM python:2.7 + # Set umask RUN sed -i "s|umask 022|umask 027|g" /etc/profile @@ -27,7 +31,7 @@ RUN mkdir -p /usr/app RUN mkdir -p /usr/app/bin RUN mkdir -p /usr/app/inventry RUN mkdir -p /usr/app/test/resources -RUN mkdir -p /usr/app/images +RUN mkdir -p /usr/app/images_result # Set the home directory to our app user's home. ENV APP_HOME=/usr/app @@ -36,6 +40,7 @@ ENV GID=166 ENV USER_NAME=eguser ENV GROUP_NAME=eggroup ENV ENV="/etc/profile" +ENV PYTHONUNBUFFERED=0 # Create an app user so our program doesn't run as root. RUN apt-get -y update &&\ @@ -51,7 +56,6 @@ RUN apt-get install -y --fix-missing \ curl \ graphicsmagick \ libgraphicsmagick1-dev \ - libatlas-dev \ libavcodec-dev \ libavformat-dev \ libgtk2.0-dev \ @@ -59,10 +63,9 @@ RUN apt-get install -y --fix-missing \ liblapack-dev \ libswscale-dev \ pkg-config \ - python3-dev \ - python3-numpy \ software-properties-common \ zip \ + imagemagick \ && apt-get clean && rm -rf /tmp/* /var/tmp/* # Set the working directory. @@ -71,9 +74,9 @@ WORKDIR $APP_HOME # Copy the application & scripts COPY config.py requirements.txt run.py $APP_HOME/ COPY inventry $APP_HOME/inventry/ + COPY test $APP_HOME/test/ COPY test/resources $APP_HOME/test/resources/ -#COPY configs/*.sh $APP_HOME/bin COPY configs/start.sh $APP_HOME/bin RUN chmod 750 $APP_HOME &&\ @@ -83,7 +86,7 @@ RUN chmod 750 $APP_HOME &&\ chown -R $USER_NAME:$GROUP_NAME $APP_HOME # Exposed port -EXPOSE 9996 +EXPOSE 9995 # Change to the app user. USER $USER_NAME diff --git a/example-apps/ROBO/retail_app/Inventory-be-k8s.yaml b/example-apps/ROBO/retail_app/Inventory-be-k8s.yaml new file mode 100644 index 0000000..9c05489 --- /dev/null +++ b/example-apps/ROBO/retail_app/Inventory-be-k8s.yaml @@ -0,0 +1,58 @@ +# Copyright 2020 Huawei Technologies Co., Ltd. +# +# Licensed 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. +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: inventory-be +spec: + replicas: 1 + selector: + matchLabels: + app: inventory + template: + metadata: + labels: + app: inventory + spec: + containers: + - name: inventory-be + image: ealtedge/inventory-be + env: + - name: INFLUXDB_IP + value: "159.138.33.54" + - name: INFLUXDB_PORT + value: "30944" + - name: OBJ_DET_SER_IP + value: "159.138.33.54" + - name: OBJ_DET_SER_PORT + value: "30093" + ports: + - containerPort: 9995 + + +# backup-be serice +--- +apiVersion: v1 +kind: Service +metadata: + name: inventory-be +spec: + selector: + app: inventory + type: NodePort + ports: + - port: 9995 + name: service-port + nodePort: 30092 diff --git a/example-apps/ROBO/retail_app/config.py b/example-apps/ROBO/retail_app/config.py index 29c2ebb..896e5a0 100644 --- a/example-apps/ROBO/retail_app/config.py +++ b/example-apps/ROBO/retail_app/config.py @@ -17,8 +17,9 @@ import os # [Server Configurations] -server_port = 9999 -server_address = os.environ.get('LISTEN_IP') +server_port = 9995 +# server_address = os.environ.get('LISTEN_IP') +server_address = "0.0.0.0" # [InfluxDB config] IPADDRESS = os.environ.get('INFLUXDB_IP') @@ -39,7 +40,15 @@ ssl_server_name = os.environ.get('SERVER_NAME', "ealtedge") # [Service Configurations] api_gateway = os.environ.get("API_GATEWAY", "apigw.mep.org") -Obj_Det = os.environ.get("OBJ_DETECTION", "objdetection") +# Obj_Det_SER_IP = os.environ.get("OBJ_DET_SER_IP", "localhost") + +Obj_Det_SER_IP = os.environ.get("OBJ_DET_SER_IP") +# Obj_Det_SER_IP = os.environ.get("OBJ_DET_SER_IP") +Obj_Det_SER_PORT = os.environ.get("OBJ_DET_SER_PORT") +# Obj_Det_SER_PORT = os.environ.get("OBJ_DET_SER_PORT") +Obj_Det = os.environ.get("OBJ_DETECTION", "mep/v1/obj_detection") # [Constants] -detection_url = "http://" + api_gateway + "/" + Obj_Det +# detection_url = "http://" + api_gateway + "/" + Obj_Det +detection_url = "http://" + Obj_Det_SER_IP + ":" + Obj_Det_SER_PORT + "/" + \ + Obj_Det + "/" diff --git a/example-apps/ROBO/retail_app/configs/start.sh b/example-apps/ROBO/retail_app/configs/start.sh index fd38b71..23796c6 100644 --- a/example-apps/ROBO/retail_app/configs/start.sh +++ b/example-apps/ROBO/retail_app/configs/start.sh @@ -41,66 +41,7 @@ validate_name() { return 0 } -# validates whether file exist -validate_file_exists() { - file_path="$1" - - # checks variable is unset - if [ -z "$file_path" ]; then - echo "file path variable is not set" - return 1 - fi - - # checks if file exists - if [ ! -f "$file_path" ]; then - echo "file does not exist" - return 1 - fi - - return 0 -} - -validate_ip "$LISTEN_IP" -valid_listen_ip="$?" -if [ ! "$valid_listen_ip" -eq "0" ]; then - echo "invalid ip address for listen ip" - exit 1 -fi - -if [ ! -z "$SERVER_NAME" ]; then - validate_name "$SERVER_NAME" - valid_name="$?" - if [ ! "$valid_name" -eq "0" ]; then - echo "invalid ssl server name" - exit 1 - fi -fi - -# ssl parameters validation -validate_file_exists "/usr/app/ssl/server_tls.crt" -valid_ssl_server_cert="$?" -if [ ! "$valid_ssl_server_cert" -eq "0" ]; then - echo "invalid ssl server certificate" - exit 1 -fi - -# ssl parameters validation -validate_file_exists "/usr/app/ssl/server_tls.key" -valid_ssl_server_key="$?" -if [ ! "$valid_ssl_server_key" -eq "0" ]; then - echo "invalid ssl server key" - exit 1 -fi - -# ssl parameters validation -validate_file_exists "/usr/app/ssl/ca.crt" -valid_ssl_ca_crt="$?" -if [ ! "$valid_ssl_ca_crt" -eq "0" ]; then - echo "invalid ssl ca cert" - exit 1 -fi - echo "Running Retail App" umask 0027 cd /usr/app || exit -python run.py \ No newline at end of file +python -u run.py \ No newline at end of file diff --git a/example-apps/ROBO/retail_app/inventry/retail_app.py b/example-apps/ROBO/retail_app/inventry/retail_app.py index e3f98d3..22f7db0 100644 --- a/example-apps/ROBO/retail_app/inventry/retail_app.py +++ b/example-apps/ROBO/retail_app/inventry/retail_app.py @@ -18,21 +18,23 @@ import config from flask_sslify import SSLify from flask import Flask, request, jsonify, Response from flask_cors import CORS -# from camera_driver.capture_frame import VideoCamera, VideoFile -# from capture_frame import VideoCamera, VideoFile -# from influxdb import InfluxDBClient +from influxdb import InfluxDBClient import json -import time import requests import os import cv2 - +import os.path +from os import path +import base64 +import time +import sys app = Flask(__name__) CORS(app) sslify = SSLify(app) app.config['JSON_AS_ASCII'] = False -app.config['UPLOAD_PATH'] = '/usr/app/images/' +app.config['UPLOAD_PATH'] = '/usr/app/images_result/' +app.config['VIDEO_PATH'] = '/usr/app/test/resources/' app.config['supports_credentials'] = True app.config['CORS_SUPPORTS_CREDENTIALS'] = True app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 @@ -42,31 +44,37 @@ count = 0 listOfMsgs = [] listOfCameras = [] listOfVideos = [] -mock_func = 1 class inventory_info: """ Store the data and manage multiple input video feeds """ - def __init__(self, current_count=0, total_count=0, time=0): + def __init__(self, status="Needs Filling", time=0): self.type = "Shelf_INV1" self.labels = "Bottles" - self.current_count = current_count - self.total_count = total_count + self.status = status + self.currentCount = 1 + self.maxCount = 5 self.time = time - def setcurrentcount(self, current_count): - self.current_count = current_count + def setstatus(self, status): + self.status = status - def settotalcount(self, total_count): - self.total_count = total_count + def getstatus(self): + return self.status - def getcurrentcount(self): - return self.current_count + def setcurrentCount(self, count): + self.currentCount = count - def gettotalcount(self): - return self.total_count + def getcurrentCount(self): + return self.currentCount + + def setmaxCount(self, count): + self.maxCount = count + + def getmaxCount(self): + return self.maxCount def setlabel(self, labels): self.labels = labels @@ -83,7 +91,6 @@ class inventory_info: # temporary copied capture_frame file to this due to docker issue for module # import - class VideoCamera(object): """ opneCV to capture frame from a camera @@ -107,7 +114,7 @@ class VideoFile(object): opneCV to capture frame from a video stream """ def __init__(self, video_name): - self.video = cv2.VideoCapture("./test/resources/" + video_name) + self.video = cv2.VideoCapture(app.config['VIDEO_PATH'] + video_name) def delete(self): self.video.release() @@ -120,27 +127,6 @@ class VideoFile(object): return success, image -def store_data(inventory_info): - """ - store time series data in influx db - """ - # TODO config, schema table, DB, fill data set - create_database() - store_info_db(inventory_info) - - -def mock_table(inven_info): - current_count = 3 - labels = "Bottles" - total_count = 6 - inven_info.setcurrentcount(current_count) - inven_info.settotalcount(total_count) - inven_info.setlabel(labels) - inven_info.utime = time.time() - # store_data(inven_info) - local_store(inven_info) - - def shelf_inventory(video_capture, camera_info, true=None): """ shelf_inventory @@ -148,48 +134,68 @@ def shelf_inventory(video_capture, camera_info, true=None): global count global mock_func - labels = "bottles" + labels = "Bottles" + count_val = 'ObjCount' process_this_frame = 0 - if mock_func == 1: - inven_info = inventory_info() - mock_table(inven_info) - else: - while True: - success, frame = video_capture.get_frame() - if not success: - break - if process_this_frame == 0: - url = config.detection_url + "/v1/obj_detection/detect" - # info1 = cv2.imencode(".jpg", rgb_small_frame)[1].tobytes() - data = json.loads(requests.post - (url, data=frame, - verify=config.ssl_cacertpath).text) - inven_info = inventory_info() - current_count = data[count] - labels = data[labels] - total_count = inven_info.current_count + inven_info.total_count - inven_info.setcurrentcount(current_count) - inven_info.settotalcount(total_count) - inven_info.setlabel(labels) - inven_info.utime = time.time() - # store_data(inven_info) - local_store(inven_info) - - -def local_store(inven_info): + i = 0 + url = config.detection_url + "detect" + url_get = config.detection_url + "image" + + while True: + success, frame = video_capture.get_frame() + if not success: + print('read frame from file is failed') + break + + i = i+1 + if i < 10: + continue + + i = 0 + + if process_this_frame == 0: + imencoded = cv2.imencode(".jpg", frame)[1] + file = {'file': ( + 'image.jpg', imencoded.tostring(), 'image/jpeg', + {'Expires': '0'})} + res = requests.post(url, files=file) + data = json.loads(res.text) + + # get image + response = requests.get(url_get) + + file = open(app.config['UPLOAD_PATH'] + "sample_image.jpg", "wb") + file.write(response.content) + file.close() + + inven_info = inventory_info() + current_count = data[count_val] + if (current_count >= 3): + status = "Mostly Filled" + elif (current_count == 2): + status = "Partially Filled" + else: + status = "Needs Filling" + + inven_info.setlabel(labels) + inven_info.setstatus(status) + inven_info.setcurrentCount(current_count) + time_sec = time.time() + local_time = time.ctime(time_sec) + inven_info.time = local_time + store_info_db(inven_info) + time.sleep(0.30) + + +def db_drop_table(inven_info): """ - store "shelf" data to array + cleanup measrurment before new trigger - :param inven_info: Inventry object + :param inven_info: inven_info object :return: None """ - if len(listOfMsgs) >= 100: - listOfMsgs.pop() - newdict = {"shelfName": inven_info.type, "ObjType": inven_info.labels, - "currentCount": inven_info.current_count, - "totalCount": inven_info.total_count, - "time": time.time()} - listOfMsgs.insert(0, newdict) + global db_client + db_client.drop_measurement(inven_info.type) def store_info_db(inven_info): @@ -208,13 +214,45 @@ def store_info_db(inven_info): }, "fields": { "time": inven_info.time, - "Current Count": inven_info.current_count, - "Total Count": inven_info.total_count, + "status": inven_info.status, + "currentCount": inven_info.currentCount, + "maxCount": inven_info.maxCount, } }] db_client.write_points(json_body) +def retrive_info_db(): + """ + Send "shelf" data to InfluxDB + + :param inven_info: Inventry object + :return: None + """ + global db_client + + # get data last n data points from DB + result = db_client.query('select * from Shelf_INV1 order by desc limit ' + '1;') + + # Get points and iterate over each record + points = result.get_points(tags={"object": "bottles"}) + + # clear the msg list + # listOfMsgs.clear() + del listOfMsgs[:] + + # iterate points and fill the records and insert to list + for point in points: + print("status: %s,Time: %s" % (point['status'], point['time'])) + newdict = {"shelfName": 'Shelf_INV1', "ObjType": "bottles", + "status": point['status'], + "currentCount": point['currentCount'], + "maxCount": point['maxCount'], + "time": point['time']} + listOfMsgs.insert(0, newdict) + + def create_database(): """ Connect to InfluxDB and create the database @@ -222,10 +260,10 @@ def create_database(): :return: None """ global db_client -# proxy = {"http": "http://{}:{}".format(config.IPADDRESS, config.PORT)} -# db_client = InfluxDBClient(host=config.IPADDRESS, port=config.PORT, -# proxies=proxy, database=config.DATABASE_NAME) -# db_client.create_database(config.DATABASE_NAME) + proxy = {"http": "http://{}:{}".format(config.IPADDRESS, config.PORT)} + db_client = InfluxDBClient(host=config.IPADDRESS, port=config.PORT, + proxies=proxy, database=config.DATABASE_NAME) + db_client.create_database(config.DATABASE_NAME) @app.route('/v1/inventry/table', methods=['GET']) @@ -235,17 +273,32 @@ def inventry_table(): :return: inventry table """ - return jsonify(listOfMsgs) + retrive_info_db() + table = {"InventryData": listOfMsgs} + return jsonify(table) @app.route('/v1/inventry/image', methods=['GET']) def detected_image(): """ - return inventry table + detect images with imposed - :return: inventry table + :return: result image """ - return jsonify(listOfMsgs) + detected_image = app.config['UPLOAD_PATH'] + 'sample_image.jpg' + print('file exits:', str(path.exists(detected_image))) + status = str(path.exists(detected_image)) + if status == 'True': + # as base64 string + with open(detected_image, "rb") as img_file: + jpeg_bin = base64.b64encode(img_file.read()) + + response = {'image': jpeg_bin} + return jsonify(response) + else: + response = {'image': 'null'} + print('file not exist') + return jsonify(response) def allowed_videofile(filename): @@ -262,50 +315,96 @@ def upload_video(): app.logger.info("Received message from ClientIP [" + request.remote_addr + "] Operation [" + request.method + "]" + " Resource [" + request.url + "]") + print("videpath:" + app.config['VIDEO_PATH']) if 'file' in request.files: files = request.files.getlist("file") for file in files: if allowed_videofile(file.filename): file.save(os.path.join(app.config['VIDEO_PATH'], file.filename)) + print('file path is:', app.config['VIDEO_PATH'] + + file.filename) else: raise IOError('video format error') - return Response("success") + msg = {"responce": "failure"} + return jsonify(msg) + msg = {"responce": "success"} + return jsonify(msg) + + +def hash_func(camera_info): + hash_string = camera_info["cameraNumber"] + \ + camera_info["cameraLocation"] + \ + camera_info["rtspUrl"] + # readable_hash = hashlib.sha256(str(hash_string).encode( + # 'utf-8')).hexdigest() + readable_hash = hash(hash_string) + if readable_hash < 0: + readable_hash += sys.maxsize + print(readable_hash) + return readable_hash @app.route('/v1/monitor/cameras', methods=['POST']) def add_camera(): - camera_info = request.json + camera_detail = request.json app.logger.info("Received message from ClientIP [" + request.remote_addr + "] Operation [" + request.method + "]" + " Resource [" + request.url + "]") - camera_info = {"name": camera_info["name"], - "rtspurl": camera_info["rtspurl"], - "location": camera_info["location"]} + camera_id = hash_func(camera_detail) + camera_id = str(camera_id) + for camera_info in listOfCameras: + if camera_id == camera_info["cameraID"]: + msg = {"responce": "failure"} + return jsonify(msg) + break + camera_info = {"cameraID": camera_id, + "cameraNumber": camera_detail["cameraNumber"], + "rtspUrl": camera_detail["rtspUrl"], + "cameraLocation": camera_detail["cameraLocation"]} listOfCameras.append(camera_info) - return Response("success") + msg = {"responce": "success"} + return jsonify(msg) -@app.route('/v1/monitor/cameras///', methods=['GET']) -def get_camera(name, rtspurl, location): +@app.route('/v1/monitor/cameras/', methods=['GET']) +def get_camera(cameraID): """ register camera with location """ app.logger.info("Received message from ClientIP [" + request.remote_addr + "] Operation [" + request.method + "]" + " Resource [" + request.url + "]") - camera_info = {"name": name, "rtspurl": rtspurl, "location": location} - if "mp4" in camera_info["rtspurl"]: - video_file = VideoFile(camera_info["rtspurl"]) - video_dict = {camera_info["name"]: video_file} + valid_id = 0 + for camera_info in listOfCameras: + # cameraID = int(cameraID) + if cameraID == camera_info["cameraID"]: + valid_id = 1 + break + + if valid_id == 0: + app.logger.info("camera ID is not valid") + msg = {"responce": "failure"} + return jsonify(msg) + + if "mp4" in camera_info["rtspUrl"]: + video_file = VideoFile(camera_info["rtspUrl"]) + video_dict = {camera_info["cameraNumber"]: video_file} listOfVideos.append(video_dict) - return Response(shelf_inventory(video_file, camera_info["name"]), - mimetype='multipart/x-mixed-replace; boundary=frame') + # return Response(shelf_inventory(video_file, camera_info[ + # "cameraNumber"]), + # mimetype='multipart/x-mixed-replace; boundary=frame') + shelf_inventory(video_file, camera_info["cameraNumber"]) + app.logger.info("get_camera: Added json") + msg = {"responce": "success"} + return jsonify(msg) + else: - video_file = VideoCamera(camera_info["rtspurl"]) - video_dict = {camera_info["name"]: video_file} + video_file = VideoCamera(camera_info["rtspUrl"]) + video_dict = {camera_info["cameraNumber"]: video_file} listOfVideos.append(video_dict) - return Response(shelf_inventory(video_file, camera_info["name"]), + return Response(shelf_inventory(video_file, + camera_info["cameraNumber"]), mimetype='multipart/x-mixed-replace; boundary=frame') @@ -319,11 +418,8 @@ def delete_camera(camera_name): video_obj = video1[camera_name] video_obj.delete() for camera in listOfCameras: - if camera_name == camera["name"]: + if camera_name == camera["cameraNumber"]: listOfCameras.remove(camera) - for msg in listOfMsgs: - if camera_name in msg["msg"]: - listOfMsgs.remove(msg) return Response("success") @@ -332,7 +428,8 @@ def query_cameras(): app.logger.info("Received message from ClientIP [" + request.remote_addr + "] Operation [" + request.method + "]" + " Resource [" + request.url + "]") - return jsonify(listOfCameras) + camera_info = {"roboCamera": listOfCameras} + return jsonify(camera_info) @app.route('/', methods=['GET']) @@ -345,6 +442,7 @@ def hello_world(): def start_server(handler): app.logger.addHandler(handler) + create_database() if config.ssl_enabled: context = (config.ssl_certfilepath, config.ssl_keyfilepath) app.run(host=config.server_address, debug=True, ssl_context=context, diff --git a/example-apps/ROBO/retail_app/requirements.txt b/example-apps/ROBO/retail_app/requirements.txt index dd037ca..00afd2f 100644 --- a/example-apps/ROBO/retail_app/requirements.txt +++ b/example-apps/ROBO/retail_app/requirements.txt @@ -17,7 +17,8 @@ flask~=1.1.2 requests~=2.18.4 flask_sslify -opencv-python +opencv-python~=3.3.1.11 flask_cors - -influxdb~=5.3.1 \ No newline at end of file +numpy +influxdb~=5.3.1 +Wand \ No newline at end of file -- 2.16.6