diff --git a/contrib/kubespray/quick-start/services-configuration.yaml.template b/contrib/kubespray/quick-start/services-configuration.yaml.template
index 5ba81b6cde..05844fd7be 100644
--- a/contrib/kubespray/quick-start/services-configuration.yaml.template
+++ b/contrib/kubespray/quick-start/services-configuration.yaml.template
@@ -236,6 +236,10 @@ authentication:
# uncomment following section if you want to customize the port of log-manager
# log-manager:
# port: 9103
+# admin_name: "admin"
+# admin_password: "admin"
+# jwt_secret: "jwt_secret"
+# token_expired_second: 120
# uncomment following section if you want to customize the port of storage-manager
diff --git a/deployment/quick-start/services-configuration.yaml.template b/deployment/quick-start/services-configuration.yaml.template
index 795441a793..3d34cedeee 100644
--- a/deployment/quick-start/services-configuration.yaml.template
+++ b/deployment/quick-start/services-configuration.yaml.template
@@ -107,6 +107,10 @@ rest-server:
# uncomment following section if you want to customize the port of log-manager
# log-manager:
# port: 9103
+# admin_name: "admin"
+# admin_password: "admin"
+# jwt_secret: "jwt_secret"
+# token_expired_second: 120
# uncomment following section if you want to customize the port of storage-manager
# storage-manager:
diff --git a/src/log-manager/build/log-manager-cleaner.k8s.dockerfile b/src/log-manager/build/log-manager-cleaner.k8s.dockerfile
new file mode 100644
index 0000000000..245649d0a8
--- /dev/null
+++ b/src/log-manager/build/log-manager-cleaner.k8s.dockerfile
@@ -0,0 +1,24 @@
+# Copyright (c) Microsoft Corporation
+# All rights reserved.
+#
+# MIT License
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+# to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+FROM alpine:3.10
+
+# install dev tools
+RUN apk update && apk add --no-cache tini bash findutils
+COPY src/cleaner/ /usr/bin/cleaner/
+ENTRYPOINT ["/sbin/tini","--","/usr/bin/cleaner/entrypoint.sh"]
+
diff --git a/src/log-manager/build/log-manager-logrotate.k8s.dockerfile b/src/log-manager/build/log-manager-logrotate.k8s.dockerfile
deleted file mode 100644
index 598274c29b..0000000000
--- a/src/log-manager/build/log-manager-logrotate.k8s.dockerfile
+++ /dev/null
@@ -1,55 +0,0 @@
-# Original work Copyright (c) 2015 Steffen Bleul
-# Modified work Copyright (c) Microsoft Corporation
-# All rights reserved.
-#
-# MIT License
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
-# documentation files (the "Software"), to deal in the Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
-# to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
-# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-FROM alpine:3.10
-
-# logrotate version (e.g. 3.9.1-r0)
-ARG LOGROTATE_VERSION=latest
-# permissions
-ARG CONTAINER_UID=1000
-ARG CONTAINER_GID=1000
-
-# install dev tools
-RUN export CONTAINER_USER=logrotate && \
- export CONTAINER_GROUP=logrotate && \
- addgroup -g $CONTAINER_GID logrotate && \
- adduser -u $CONTAINER_UID -G logrotate -h /usr/bin/logrotate.d -s /bin/bash -S logrotate && \
- apk add --update \
- tini \
- bash \
- tar \
- gzip \
- wget \
- tzdata && \
- if [ "${LOGROTATE_VERSION}" = "latest" ]; \
- then apk add logrotate ; \
- else apk add "logrotate=${LOGROTATE_VERSION}" ; \
- fi && \
- mkdir -p /usr/bin/logrotate.d && \
- wget --no-check-certificate -O /tmp/go-cron.tar.gz https://github.com/michaloo/go-cron/releases/download/v0.0.2/go-cron.tar.gz && \
- tar xvf /tmp/go-cron.tar.gz -C /usr/bin && \
- apk del \
- wget && \
- rm -rf /var/cache/apk/* && rm -rf /tmp/*
-
-COPY src/logrotate/ /usr/bin/logrotate.d/
-RUN chmod +x /usr/bin/logrotate.d/docker-entrypoint.sh
-
-ENTRYPOINT ["/sbin/tini","--","/usr/bin/logrotate.d/docker-entrypoint.sh"]
-VOLUME ["/logrotate-status"]
-CMD ["cron"]
diff --git a/src/log-manager/build/log-manager-nginx.k8s.dockerfile b/src/log-manager/build/log-manager-nginx.k8s.dockerfile
index d1d4fb59c7..a5896ac5cb 100644
--- a/src/log-manager/build/log-manager-nginx.k8s.dockerfile
+++ b/src/log-manager/build/log-manager-nginx.k8s.dockerfile
@@ -15,5 +15,11 @@
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-FROM openresty/openresty:1.15.8.2-alpine
-COPY src/nginx/nginx.conf /etc/nginx/conf.d/default.conf
\ No newline at end of file
+FROM openresty/openresty:1.15.8.3-2-alpine-fat
+
+RUN luarocks install lua-cjson && luarocks install lua-resty-jwt && \
+ luarocks install luafilesystem
+
+COPY src/nginx/nginx.conf.default /etc/nginx/conf.d/default.conf
+COPY src/nginx/nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
+COPY src/nginx/*.lua /etc/nginx/lua/
diff --git a/src/log-manager/config/log-manager.md b/src/log-manager/config/log-manager.md
index afbf148d0c..194a7f45d5 100644
--- a/src/log-manager/config/log-manager.md
+++ b/src/log-manager/config/log-manager.md
@@ -1,46 +1,43 @@
## Log-manager section parser
-- [Default Configuration](#D_Config)
-- [How to Configure](#HT_Config)
-- [Generated Configuration](#G_Config)
-- [Data Table](#T_config)
+- [Default configuration](#default-configuration)
+- [How to configure cluster section in service-configuration.yaml](#how-to-configure-cluster-section-in-service-configurationyaml)
+- [Generated Configuration](#generated-configuration)
+- [Table](#table)
-#### Default configuration
+#### Default configuration
[log-manager default configuration](log-manager.yaml)
-#### How to configure cluster section in service-configuration.yaml
+#### How to configure cluster section in service-configuration.yaml
All configurations in this section is optional. If you want to customized these value, you can configure it in service-configuration.yaml.
For example, if you want to use different port than the default 9103, add following to your service-configuration.yaml as following:
```yaml
log-manager:
- port: new-value
+ port: new-value
```
-#### Generated Configuration
+#### Generated Configuration
Generated configuration means the object model after parsing. The parsed data will be presented by a yaml format.
```yaml
log-manager:
- port: 9103
+ port: 9103
+ admin_name: admin
+ admin_password: admin
+ jwt_secret: "jwt_secret"
+ token_expired_second: 120
```
-#### Table
-
-
-
- Data in Configuration File |
- Data in Cluster Object Model |
- Data in Jinja2 Template |
- Data type |
-
-
- log-manager.port |
- com["log-manager"]["port"] |
- cluster_cfg["log-manager"]["port"] |
- Int |
-
-
+#### Table
+
+| Data in Configuration File | Data in Cluster Object Model | Data in Jinja2 Template | Data type |
+|-----------------------------------|---------------------------------------------|----------------------------------------------------|-----------|
+| log-manager.port | com["log-manager"]["port"] | cluster_cfg["log-manager"]["port"] | Int |
+| log-manager.admin_name | com["log-manager"]["admin_name"] | cluster_cfg["log-manager"]["admin_name"] | String |
+| log-manager.admin_password | com["log-manager"]["admin_password"] | cluster_cfg["log-manager"]["admin_password"] | String |
+| log-manager.jwt_secret | com["log-manager"]["jwt_secret"] | cluster_cfg["log-manager"]["jwt_secret"] | String |
+| log-manager.token_expired_second | com["log-manager"]["token_expired_second"] | cluster_cfg["log-manager"]["token_expired_second"] | Int |
diff --git a/src/log-manager/config/log-manager.yaml b/src/log-manager/config/log-manager.yaml
index 1fd2af76a5..302b5bcb51 100644
--- a/src/log-manager/config/log-manager.yaml
+++ b/src/log-manager/config/log-manager.yaml
@@ -18,3 +18,7 @@
service_type: "k8s"
port: 9103
+admin_name: "admin"
+admin_password: "admin"
+jwt_secret: "jwt_secret"
+token_expired_second: 120
diff --git a/src/log-manager/deploy/log-manager.yaml.template b/src/log-manager/deploy/log-manager.yaml.template
index dfedc2ef5e..c20767863c 100644
--- a/src/log-manager/deploy/log-manager.yaml.template
+++ b/src/log-manager/deploy/log-manager.yaml.template
@@ -34,12 +34,9 @@ spec:
priorityClassName: pai-daemon-priority
hostNetwork: false
containers:
- - name: log-manager-logrotate
- image: {{ cluster_cfg["cluster"]["docker-registry"]["prefix"] }}log-manager-logrotate:{{ cluster_cfg["cluster"]["docker-registry"]["tag"] }}
+ - name: log-cleaner
+ image: {{ cluster_cfg["cluster"]["docker-registry"]["prefix"] }}log-manager-cleaner:{{ cluster_cfg["cluster"]["docker-registry"]["tag"] }}
imagePullPolicy: Always
- env:
- - name: LOGROTATE_CRONSCHEDULE
- value: "*/10 * * * *"
volumeMounts:
- name: pai-log
mountPath: /usr/local/pai/logs
@@ -77,6 +74,19 @@ spec:
cpu: 0
memory: "128Mi"
{%- endif %}
+ env:
+ - name: ADMIN_NAME
+ value: {{ cluster_cfg["log-manager"]["admin_name"] }}
+ - name: ADMIN_PASSWORD
+ value: {{ cluster_cfg["log-manager"]["admin_password"] }}
+ - name: JWT_SECRET
+ value: {{ cluster_cfg["log-manager"]["jwt_secret"] }}
+ - name: TOKEN_EXPIRED_SECOND
+ value: '{{ cluster_cfg["log-manager"]["token_expired_second"] }}'
+ - name: NODE_NAME
+ valueFrom:
+ fieldRef:
+ fieldPath: spec.nodeName
volumes:
- name: pai-log
hostPath:
diff --git a/src/log-manager/src/cleaner/entrypoint.sh b/src/log-manager/src/cleaner/entrypoint.sh
new file mode 100755
index 0000000000..41adf9708b
--- /dev/null
+++ b/src/log-manager/src/cleaner/entrypoint.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+
+# Copyright (c) Microsoft Corporation
+# All rights reserved.
+#
+# MIT License
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+# to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+set -o errexit
+set -o pipefail
+
+log_exist_time=30 # 30 day
+if [ -n "${LOG_EXIST_TIME}" ]; then
+ log_exist_time=${LOG_EXIST_TIME}
+fi
+
+cat > /etc/periodic/daily/remove_logs << EOF
+#!/bin/bash
+/usr/bin/pgrep -f ^find 2>&1 > /dev/null || find /usr/local/pai/logs/* -mtime +${log_exist_time} -type f -exec rm -fv {} \;
+EOF
+
+cat > /etc/periodic/weekly/remove_log_dir << EOF
+#!/bin/bash
+"/usr/bin/pgrep -f ^find 2>&1 > /dev/null || find /usr/local/pai/logs/* -mtime +${log_exist_time} -type d -empty -exec rmdir -v {} \;"
+EOF
+
+chmod a+x /etc/periodic/daily/remove_logs /etc/periodic/weekly/remove_log_dir
+
+echo "cron job added"
+
+crond -f -l 0
+
diff --git a/src/log-manager/src/logrotate/docker-entrypoint.sh b/src/log-manager/src/logrotate/docker-entrypoint.sh
deleted file mode 100755
index 1b03c1fd6c..0000000000
--- a/src/log-manager/src/logrotate/docker-entrypoint.sh
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/bin/bash
-
-# Original work Copyright (c) 2015 Steffen Bleul
-# Modified work Copyright (c) Microsoft Corporation
-# All rights reserved.
-#
-# MIT License
-
-
-# A helper script for ENTRYPOINT.
-
-set -e
-
-[[ ${DEBUG} == true ]] && set -x
-
-if [ -n "${DELAYED_START}" ]; then
- sleep ${DELAYED_START}
-fi
-
-# Logrotate status file handling
-readonly logrotate_logstatus=${LOGROTATE_STATUSFILE:-"/logrotate-status/logrotate.status"}
-
-# ----- Crontab Generation ------
-
-logrotate_parameters=""
-
-if [ -n "${LOGROTATE_PARAMETERS}" ]; then
- logrotate_parameters="-"${LOGROTATE_PARAMETERS}
-fi
-
-syslogger_tag=""
-
-if [ -n "${SYSLOGGER_TAG}" ]; then
-syslogger_tag=" -t "${SYSLOGGER_TAG}
-fi
-
-syslogger_command=""
-
-if [ -n "${SYSLOGGER}" ]; then
- syslogger_command="logger "${syslogger_tag}
-fi
-
-logrotate_cronlog=""
-
-if [ -n "${LOGROTATE_LOGFILE}" ] && [ -z "${SYSLOGGER}"]; then
- logrotate_cronlog=" 2>&1 | tee -a "${LOGROTATE_LOGFILE}
-else
- if [ -n "${SYSLOGGER}" ]; then
- logrotate_cronlog=" 2>&1 | "${syslogger_command}
- fi
-fi
-
-logrotate_croninterval="1 0 0 * * *"
-
-if [ -n "${LOGROTATE_INTERVAL}" ]; then
- case "$LOGROTATE_INTERVAL" in
- hourly)
- logrotate_croninterval='@hourly'
- ;;
- daily)
- logrotate_croninterval='@daily'
- ;;
- weekly)
- logrotate_croninterval='@weekly'
- ;;
- monthly)
- logrotate_croninterval='@monthly'
- ;;
- yearly)
- logrotate_croninterval='@yearly'
- ;;
- *)
- logrotate_croninterval="1 0 0 * * *"
- ;;
- esac
-fi
-
-if [ -n "${LOGROTATE_CRONSCHEDULE}" ]; then
- logrotate_croninterval=${LOGROTATE_CRONSCHEDULE}
-fi
-
-logrotate_cron_timetable="/usr/bin/pgrep -f ^/usr/sbin/logrotate 2>&1 > /dev/null || /usr/sbin/logrotate ${logrotate_parameters} --state=${logrotate_logstatus} /usr/bin/logrotate.d/logrotate.conf ${logrotate_cronlog}"
-
-log_exist_time=30 # 30 day
-if [ -n "${LOG_EXIST_TIME}" ]; then
- log_exist_time=${LOG_EXIST_TIME}
-fi
-
-# ----- Cron Start ------
-exec /usr/bin/go-cron '@daily' /bin/bash -c "/usr/bin/pgrep -f ^find 2>&1 > /dev/null || find /usr/local/pai/logs/* -mtime +${log_exist_time} -type f -exec rm -fv {} \;"&
-if [ "$1" = 'cron' ]; then
- exec /usr/bin/go-cron "${logrotate_croninterval}" /bin/bash -c "${logrotate_cron_timetable}"
-fi
-
-#-----------------------
-
-exec "$@"
diff --git a/src/log-manager/src/logrotate/logrotate.conf b/src/log-manager/src/logrotate/logrotate.conf
deleted file mode 100644
index fc6e36183d..0000000000
--- a/src/log-manager/src/logrotate/logrotate.conf
+++ /dev/null
@@ -1,14 +0,0 @@
-/usr/local/pai/logs/*/*/*/*/*.log
-/usr/local/pai/logs/*/*/*/*/*.stdout
-/usr/local/pai/logs/*/*/*/*/*.stderr
-/usr/local/pai/logs/*/*/*/*/*.all
-{
- nomail
- rotate 1
- nocompress
- missingok
- notifempty
- copytruncate
- size 256M
- maxage 30
-}
diff --git a/src/log-manager/src/nginx/get_log_content.lua b/src/log-manager/src/nginx/get_log_content.lua
new file mode 100644
index 0000000000..1399f547c7
--- /dev/null
+++ b/src/log-manager/src/nginx/get_log_content.lua
@@ -0,0 +1,84 @@
+-- Copyright (c) Microsoft Corporation
+-- All rights reserved.
+-- MIT License
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+-- documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+-- the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+-- to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+-- THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+-- BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+-- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+local lfs = require "lfs"
+
+local function get_rotated_log(log_path)
+ for file in lfs.dir(log_path) do
+ local rotated_log_name = string.match(file, "^@.*%.s")
+ if rotated_log_name then
+ return rotated_log_name
+ end
+ end
+end
+
+
+local args = ngx.req.get_uri_args()
+local username = args["username"]
+local framework_name = args["framework-name"]
+local taskrole = args["taskrole"]
+local pod_uid = args["pod-uid"]
+local token = args["token"]
+local tail_mode = args["tail-mode"]
+
+if not token or not username or not taskrole or not framework_name or not pod_uid then
+ ngx.log(ngx.ERR, "some query parameters is nil")
+ ngx.status = ngx.HTTP_BAD_REQUEST
+ return ngx.exit(ngx.HTTP_OK)
+end
+
+local path_prefix = "/usr/local/pai/logs/"..username.."/".. framework_name.."/".. taskrole.."/"..pod_uid.."/"
+local log_name = ngx.var[1]
+
+local log_path = path_prefix..log_name
+ngx.log(ngx.INFO, "get log name "..log_name)
+if string.match(log_name, "^user%-.*$") then
+ -- we only keep one rotated log in log manager
+ if string.match(log_name, "%.1$") then
+ local parent_path = path_prefix..string.sub(log_name, 1, string.len(log_name) - 2)
+ local rotated_log_name = get_rotated_log(parent_path)
+ if not rotated_log_name then
+ ngx.status = ngx.HTTP_NOT_FOUND
+ return ngx.exit(ngx.HTTP_OK)
+ else
+ log_path = parent_path.."/"..rotated_log_name
+ end
+ else
+ log_path = path_prefix..log_name.."/current"
+ end
+end
+
+ngx.log(ngx.INFO, "get log from path"..log_path)
+
+if lfs.attributes(log_path, "mode") ~= "file" then
+ ngx.log(ngx.ERR, log_path.." not exists")
+ ngx.status = ngx.HTTP_NOT_FOUND
+ return ngx.exit(ngx.HTTP_OK)
+end
+
+local logs
+if (tail_mode == "true") then
+ logs = io.popen("tail -c 16k "..log_path)
+else
+ logs = io.popen("cat "..log_path)
+end
+
+-- buffer size (8K)
+local size = 2^13
+while true do
+ local block = logs:read(size)
+ if not block then break end
+ ngx.say(block)
+end
diff --git a/src/log-manager/src/nginx/guard.lua b/src/log-manager/src/nginx/guard.lua
new file mode 100644
index 0000000000..fed14bb5ae
--- /dev/null
+++ b/src/log-manager/src/nginx/guard.lua
@@ -0,0 +1,38 @@
+-- Copyright (c) Microsoft Corporation
+-- All rights reserved.
+-- MIT License
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+-- documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+-- the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+-- to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+-- THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+-- BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+-- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+local jwt = require "resty.jwt"
+local validators = require "resty.jwt-validators"
+
+local args = ngx.req.get_uri_args()
+local jwt_token = args["token"]
+if not jwt_token then
+ ngx.status = ngx.HTTP_FORBIDDEN
+ return ngx.exit(ngx.HTTP_OK)
+end
+
+local jwt_secret = os.getenv("JWT_SECRET")
+local node_name = os.getenv("NODE_NAME")
+
+local claim_spec = {
+ sub = validators.equals("log-manager-"..node_name),
+ exp = validators.is_not_expired()
+}
+local jwt_obj = jwt:verify(jwt_secret, jwt_token, claim_spec)
+
+if not jwt_obj["verified"] then
+ ngx.status = ngx.HTTP_FORBIDDEN
+ ngx.header["Access-Control-Allow-Origin"] = "*";
+ return ngx.exit(ngx.HTTP_OK)
+end
diff --git a/src/log-manager/src/nginx/list_logs.lua b/src/log-manager/src/nginx/list_logs.lua
new file mode 100644
index 0000000000..8acf9edf1f
--- /dev/null
+++ b/src/log-manager/src/nginx/list_logs.lua
@@ -0,0 +1,74 @@
+-- Copyright (c) Microsoft Corporation
+-- All rights reserved.
+-- MIT License
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+-- documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+-- the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+-- to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+-- THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+-- BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+-- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+local cjson = require "cjson"
+local lfs = require "lfs"
+
+local function has_file_with_pattern(path, pattern)
+ for file in lfs.dir(path) do
+ if string.match(file, pattern) then
+ return true
+ end
+ end
+ return false
+end
+
+local function is_dir(path)
+ return lfs.attributes(path, "mode") == "directory"
+end
+
+local args = ngx.req.get_uri_args()
+local username = args["username"]
+local framework_name = args["framework-name"]
+local taskrole = args["taskrole"]
+local pod_uid = args["pod-uid"]
+local token = args["token"]
+
+if not token or not username or not taskrole or not framework_name or not pod_uid then
+ ngx.log(ngx.ERR, "some query parameters is nil")
+ ngx.status = ngx.HTTP_BAD_REQUEST
+ return ngx.exit(ngx.HTTP_OK)
+end
+
+local log_query_param = "?username="..username.."&framework-name="..framework_name..
+ "&pod-uid="..pod_uid.."&taskrole="..taskrole.."&token="..token
+local path = "/usr/local/pai/logs/"..username.."/".. framework_name.."/".. taskrole.."/"..pod_uid.."/"
+local path_prefix = "/api/v1/logs/"
+
+local ret = {}
+
+if not is_dir(path) then
+ ngx.log(ngx.ERR, "log folder not exists")
+ ngx.status = ngx.HTTP_NOT_FOUND
+ return ngx.exit(ngx.HTTP_OK)
+end
+
+for file in lfs.dir(path) do
+ if not is_dir(path..file) then
+ if string.match(file, "^user%.pai%..*$") then
+ local sub_str = string.sub(file, string.len("user.pai.") + 1)
+ ret[sub_str] = path_prefix..file..log_query_param
+ else
+ ret[file] = path_prefix..file..log_query_param
+ end
+ elseif string.match(file, "^user-.*$") then
+ local sub_str = string.sub(file, string.len("user-") + 1)
+ ret[sub_str] = path_prefix..file..log_query_param
+ if has_file_with_pattern(path..file, "^@.*%.s") then
+ ret[sub_str..".1"] = path_prefix..file..".1"..log_query_param
+ end
+ end
+end
+
+ngx.say(cjson.encode(ret))
diff --git a/src/log-manager/src/nginx/nginx.conf b/src/log-manager/src/nginx/nginx.conf
index 29d6c11b87..5bda1eb6c9 100644
--- a/src/log-manager/src/nginx/nginx.conf
+++ b/src/log-manager/src/nginx/nginx.conf
@@ -15,70 +15,74 @@
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-server {
- listen 80;
- server_name localhost;
- client_max_body_size 0; # Disable checking of client request body size.
- client_body_buffer_size 256M;
- proxy_connect_timeout 60m;
- proxy_send_timeout 60m;
- proxy_read_timeout 60m;
- send_timeout 60m;
-
- #
- # Health check
- #
- location = /healthz {
- default_type text/plain;
- return 200 "Log manager ready.";
- }
- #
- # Get all logs
- #
- location /log-manager {
- add_header Access-Control-Allow-Origin *;
- add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
- default_type text/plain;
- alias /usr/local/pai/logs;
- autoindex on;
- autoindex_exact_size off;
- autoindex_localtime on;
- }
+# nginx.conf -- docker-openresty
+#
+# This file is installed to:
+# `/usr/local/openresty/nginx/conf/nginx.conf`
+# and is the file loaded by nginx at startup,
+# unless the user specifies otherwise.
+#
+# It tracks the upstream OpenResty's `nginx.conf`, but removes the `server`
+# section and adds this directive:
+# `include /etc/nginx/conf.d/*.conf;`
+#
+# The `docker-openresty` file `nginx.vh.default.conf` is copied to
+# `/etc/nginx/conf.d/default.conf`. It contains the `server section
+# of the upstream `nginx.conf`.
+#
+# See https://github.com/openresty/docker-openresty/blob/master/README.md#nginx-config-files
+#
- #
- # Get full/tailed log
- #
- location ~ ^/log-manager/(full|tail)/(.*)$ {
- add_header Access-Control-Allow-Origin *;
- add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
- default_type text/plain;
- content_by_lua_block{
- logpath = "/usr/local/pai/logs/"..ngx.var[2]
- errcheck = " || echo No such file!"
- if (ngx.var[1] == "tail")
- then
- logs = io.popen("tail -c 16k "..logpath..errcheck)
- elseif (ngx.var[1] == "full")
- then
- logs = io.popen("cat "..logpath..errcheck)
- end
- for line in logs:lines() do
- ngx.say(line)
- end
- }
- }
+#user nobody;
+worker_processes 1;
+
+#error_log logs/error.log;
+#error_log logs/error.log notice;
+error_log logs/error.log info;
+
+#pid logs/nginx.pid;
+
+
+events {
+ worker_connections 1024;
+}
+
+
+http {
+ include mime.types;
+ default_type application/octet-stream;
+
+ #log_format main '$remote_addr - $remote_user [$time_local] "$request" '
+ # '$status $body_bytes_sent "$http_referer" '
+ # '"$http_user_agent" "$http_x_forwarded_for"';
+
+ #access_log logs/access.log main;
+
+ # See Move default writable paths to a dedicated directory (#119)
+ # https://github.com/openresty/docker-openresty/issues/119
+ client_body_temp_path /var/run/openresty/nginx-client-body;
+ proxy_temp_path /var/run/openresty/nginx-proxy;
+ fastcgi_temp_path /var/run/openresty/nginx-fastcgi;
+ uwsgi_temp_path /var/run/openresty/nginx-uwsgi;
+ scgi_temp_path /var/run/openresty/nginx-scgi;
+
+ sendfile on;
+ #tcp_nopush on;
+
+ #keepalive_timeout 0;
+ keepalive_timeout 65;
+
+ #gzip on;
- #
- # Get compressed logs for old job
- #
- location /log-backup {
- add_header Access-Control-Allow-Origin *;
- add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
- default_type text/plain;
- alias /usr/local/pai/logs-backup;
- autoindex on;
- autoindex_exact_size off;
- autoindex_localtime on;
+ init_by_lua_block {
+ local errlog = require "ngx.errlog"
+ errlog.set_filter_level(ngx.INFO)
}
+ include /etc/nginx/conf.d/*.conf;
}
+env ADMIN_NAME;
+env ADMIN_PASSWORD;
+env JWT_SECRET;
+env TOKEN_EXPIRED_SECOND;
+env NODE_NAME;
diff --git a/src/log-manager/src/nginx/nginx.conf.default b/src/log-manager/src/nginx/nginx.conf.default
new file mode 100644
index 0000000000..2adea886bc
--- /dev/null
+++ b/src/log-manager/src/nginx/nginx.conf.default
@@ -0,0 +1,77 @@
+# Copyright (c) Microsoft Corporation
+# All rights reserved.
+#
+# MIT License
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+# to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+server {
+ listen 80;
+ server_name localhost;
+ client_max_body_size 0; # Disable checking of client request body size.
+ client_body_buffer_size 256M;
+ proxy_connect_timeout 60m;
+ proxy_send_timeout 60m;
+ proxy_read_timeout 60m;
+ send_timeout 60m;
+
+ #
+ # Health check
+ #
+ location = /healthz {
+ default_type text/plain;
+ return 200 "Log manager ready.";
+ }
+
+ #
+ # Get log list
+ #
+ location /api/v1/logs {
+ add_header Access-Control-Allow-Origin *;
+ add_header Access-Control-Allow-Methods 'GET';
+ limit_except GET {
+ deny all;
+ }
+ default_type application/json;
+ access_by_lua_file /etc/nginx/lua/guard.lua;
+ content_by_lua_file /etc/nginx/lua/list_logs.lua;
+ }
+
+ #
+ # Get the token
+ #
+ location /api/v1/tokens {
+ add_header Access-Control-Allow-Origin *;
+ add_header Access-Control-Allow-Methods 'POST';
+ limit_except POST {
+ deny all;
+ }
+ default_type application/json;
+ content_by_lua_file /etc/nginx/lua/token.lua;
+ }
+
+ #
+ # Get full/tail log
+ #
+ location ~ ^/api/v1/logs/(.*)$ {
+ add_header Access-Control-Allow-Origin *;
+ add_header Access-Control-Allow-Methods 'GET';
+ limit_except GET {
+ deny all;
+ }
+ default_type text/plain;
+ access_by_lua_file /etc/nginx/lua/guard.lua;
+ content_by_lua_file /etc/nginx/lua/get_log_content.lua;
+ }
+}
diff --git a/src/log-manager/src/nginx/token.lua b/src/log-manager/src/nginx/token.lua
new file mode 100644
index 0000000000..bb92adc16c
--- /dev/null
+++ b/src/log-manager/src/nginx/token.lua
@@ -0,0 +1,45 @@
+-- Copyright (c) Microsoft Corporation
+-- All rights reserved.
+-- MIT License
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+-- documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+-- the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+-- to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+-- THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+-- BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+-- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+local cjson = require "cjson"
+local jwt = require "resty.jwt"
+
+-- check username & password
+local admin_name = os.getenv("ADMIN_NAME")
+local admin_password = os.getenv("ADMIN_PASSWORD")
+local token_expired_second = os.getenv("TOKEN_EXPIRED_SECOND")
+ngx.req.read_body()
+local ok, body = pcall(cjson.decode, ngx.req.get_body_data())
+
+if not ok then
+ ngx.status = ngx.HTTP_BAD_REQUEST
+ return ngx.exit(ngx.HTTP_OK)
+end
+
+if body["username"] ~= admin_name or body["password"] ~= admin_password then
+ ngx.status = ngx.HTTP_UNAUTHORIZED
+ return ngx.exit(ngx.HTTP_OK)
+end
+
+-- sign jwt token
+local jwt_secret = os.getenv("JWT_SECRET")
+local node_name = os.getenv("NODE_NAME")
+local jwt_token = jwt:sign(
+ jwt_secret,
+ {
+ header={typ="JWT", alg="HS256"},
+ payload={sub="log-manager-"..node_name, iat=os.time(), exp=os.time() + tonumber(token_expired_second)}
+ }
+)
+ngx.say(cjson.encode({token=jwt_token}))