docs(fhs): split CentOS 6 build and deploy documentation
This commit is contained in:
+351
@@ -0,0 +1,351 @@
|
||||
# FHS CentOS 6 배포와 DAV 검증
|
||||
|
||||
이 문서는 빌드된 FHS 산출물을 CentOS 6 Apache 2.2.15에 배포하고,
|
||||
PostgreSQL 기반 `sh-opendav`의 DAV 기능을 검증하는 절차만 다룬다.
|
||||
빌드 컨테이너와 컴파일 의존성은 [`build.6.md`](build.6.md)를 먼저 수행한다.
|
||||
|
||||
## 1. 배포 대상
|
||||
|
||||
`build.6.md`에서 만든 컨테이너와 산출물을 사용한다.
|
||||
|
||||
```bash
|
||||
FHS_CONTAINER=interactive-fhs-centos6-<RUN_ID>
|
||||
DB_CONTAINER=interactive-dav-postgres
|
||||
```
|
||||
|
||||
Apache는 FHS 컨테이너 안에서 실행하고 PostgreSQL은 같은 network namespace를
|
||||
사용한다. 따라서 Apache와 FHS 데몬은 DB를 `127.0.0.1:5432`로 접근한다.
|
||||
|
||||
## 2. PostgreSQL 런타임 준비
|
||||
|
||||
실제 서비스에서는 운영 PostgreSQL과 RCDB를 사용한다. 로컬 검증은 PostgreSQL
|
||||
컨테이너로 대체한다.
|
||||
|
||||
```bash
|
||||
podman run -d \
|
||||
--name "$DB_CONTAINER" \
|
||||
--network "container:${FHS_CONTAINER}" \
|
||||
-e POSTGRES_USER=syshost \
|
||||
-e POSTGRES_PASSWORD=test \
|
||||
-e POSTGRES_DB=localdb \
|
||||
docker.io/library/postgres:latest
|
||||
|
||||
until podman exec "$DB_CONTAINER" pg_isready -U syshost -d localdb; do
|
||||
sleep 1
|
||||
done
|
||||
```
|
||||
|
||||
RCDB 스키마를 넣고 필요한 테이블을 확인한다. 레거시 스키마 파일의 중복
|
||||
정의는 허용하되, 필요한 테이블이 실제로 만들어졌는지 확인한다.
|
||||
|
||||
```bash
|
||||
podman exec -i "$DB_CONTAINER" psql -U syshost -d localdb \
|
||||
-v ON_ERROR_STOP=0 -f - < RCDB/DB/scripts/rcdb_schema.sql
|
||||
|
||||
podman exec "$DB_CONTAINER" psql -U syshost -d localdb -c \
|
||||
'select to_regclass('"'"'public.t_dav_lock'"'"'), to_regclass('"'"'public.t_dav_locknull'"'"');'
|
||||
```
|
||||
|
||||
DAV 인증과 service cache에 필요한 최소 데이터:
|
||||
|
||||
```bash
|
||||
podman exec -i "$DB_CONTAINER" psql -U syshost -d localdb <<'SQL'
|
||||
INSERT INTO t_sms_sp_user
|
||||
(sp_user_seq, sp_user_id, sp_user_password, reg_datetime, update_datetime)
|
||||
VALUES
|
||||
(1, 'davuser', 'test', now(), now())
|
||||
ON CONFLICT (sp_user_seq) DO UPDATE SET
|
||||
sp_user_id = EXCLUDED.sp_user_id,
|
||||
sp_user_password = EXCLUDED.sp_user_password,
|
||||
update_datetime = now();
|
||||
|
||||
INSERT INTO t_sms_sp_svc_product
|
||||
(sp_svc_tran_id, sp_user_seq, sp_svc_id, sp_svc_begin_date,
|
||||
sp_svc_end_date, initial_vector, private_key, reg_datetime,
|
||||
update_datetime, validate_sp_session_id_yn, bandwidth_ctrl_yn,
|
||||
referer_chk_yn, replication_count, cal_used_depth, uri_ignore_case,
|
||||
del_file_keep_time)
|
||||
VALUES
|
||||
(100, 1, 'svc1', current_date - 1, current_date + 365,
|
||||
'123456789012345678901234', 'abcdefghijklmnopqrstuvwx', now(), now(),
|
||||
'N', 'N', 'N', 1, 0, 'N', 3)
|
||||
ON CONFLICT (sp_svc_tran_id) DO NOTHING;
|
||||
|
||||
INSERT INTO t_sms_sp_svc_product_val
|
||||
(sp_svc_tran_id, sp_user_seq, mode, auth_server, auth_path, auth_port)
|
||||
VALUES
|
||||
(100, 1, '1000', '', '', '0')
|
||||
ON CONFLICT (sp_svc_tran_id) DO NOTHING;
|
||||
|
||||
INSERT INTO t_sms_sp_svc_product_band
|
||||
(sp_svc_tran_id, sp_user_seq, mode)
|
||||
VALUES
|
||||
(100, 1, '10')
|
||||
ON CONFLICT (sp_svc_tran_id) DO NOTHING;
|
||||
SQL
|
||||
```
|
||||
|
||||
서비스 insert trigger가 만든 `t_meta_100`에 루트와 service collection을
|
||||
추가한다. 모듈의 `/dav/svc1/` 요청은 이 service collection을 사용한다.
|
||||
|
||||
```bash
|
||||
podman exec -i "$DB_CONTAINER" psql -U syshost -d localdb <<'SQL'
|
||||
INSERT INTO t_meta_100
|
||||
(sp_user_seq, sp_svc_tran_id, host_name, resource_type, uri, depth,
|
||||
creation_date, get_lastmodified, deleted_yn, is_cache)
|
||||
SELECT * FROM (VALUES
|
||||
(1, 100, 'localhost', 1, '/', 0,
|
||||
extract(epoch from now())::bigint,
|
||||
extract(epoch from now())::bigint, 'N', 'N'),
|
||||
(1, 100, 'localhost', 1, '/100/svc1', 1,
|
||||
extract(epoch from now())::bigint,
|
||||
extract(epoch from now())::bigint, 'N', 'N')
|
||||
) AS seed(sp_user_seq, sp_svc_tran_id, host_name, resource_type, uri, depth,
|
||||
creation_date, get_lastmodified, deleted_yn, is_cache)
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM t_meta_100 existing
|
||||
WHERE existing.sp_svc_tran_id = seed.sp_svc_tran_id
|
||||
AND existing.uri = seed.uri
|
||||
);
|
||||
SQL
|
||||
```
|
||||
|
||||
## 3. FHS 데몬과 저장소
|
||||
|
||||
`fimngd`는 계정 cache와 FHS shared memory를 Apache 모듈에 제공한다. 운영
|
||||
배포에서는 실제 FHS/RCTS 설정을 사용하고, 아래 설정은 로컬 검증용으로만
|
||||
사용한다.
|
||||
|
||||
```bash
|
||||
podman exec "$FHS_CONTAINER" mkdir -p \
|
||||
/stg /var/lib/fhs-dav/100 /user/service/etc
|
||||
podman exec "$FHS_CONTAINER" bash -lc 'cat > /tmp/fhs-dav-test.conf <<"EOF"
|
||||
[COMMON]
|
||||
DEFAULT_LOG_DIR = /user/service/logs
|
||||
LOG_LEVEL = 7
|
||||
RCID = test
|
||||
RCTS_SERVER = 127.0.0.1
|
||||
FHS_PORT_MNGCHECK = 14000
|
||||
FILE_STORAGE_ROOT = /stg
|
||||
INTERNAL_NIC_NAME = lo
|
||||
RCDB_IP = 127.0.0.1
|
||||
RCDB_PORT = 5432
|
||||
RCDB_DB_NAME = localdb
|
||||
RCDB_ACCT = syshost
|
||||
RCDB_ACCT_PW = test
|
||||
[fimngd]
|
||||
HOT_CONTENT_REQUEST_TCP_PORT = 14001
|
||||
TRANSFER_STAT_SEND_PORT = 14002
|
||||
FHS_INFO_REQUEST_PORT = 14003
|
||||
MAX_FHS = 1
|
||||
MAX_ACCOUNT = 20
|
||||
MAX_ANONYMOUS = 1
|
||||
IPC_QUEUE_COUNT = 1
|
||||
IPC_QUEUE_MAX_SIZE = 500
|
||||
EOF
|
||||
if ! ps axo stat,comm | \
|
||||
awk '$2 == "fimngd" && $1 !~ /^Z/ { found = 1 } END { exit !found }'; then
|
||||
/workspace/fhs/fimngd/src/fimngd \
|
||||
-D -c /tmp/fhs-dav-test.conf >/tmp/fimngd-final.log 2>&1 &
|
||||
fi
|
||||
'
|
||||
```
|
||||
|
||||
실제 FHS가 연결된 환경에서는 RCTS가 FHS 정보를 shared memory에 넣어야 한다.
|
||||
이 문서는 RCTS/FHS 자체를 배포하지 않으므로, DAV 검증 전 해당 runtime이
|
||||
실행 중이고 FHS count가 1 이상인지 확인한다. FHS 정보가 없으면 Apache는
|
||||
정상이어도 storage DAV 검증을 완료할 수 없다.
|
||||
|
||||
```bash
|
||||
podman exec "$FHS_CONTAINER" ps axo pid,stat,comm | \
|
||||
awk '$3 == "fimngd" && $2 !~ /^Z/'
|
||||
podman exec "$DB_CONTAINER" psql -U syshost -d localdb -c \
|
||||
'select sp_user_id, sp_svc_id, sp_svc_tran_id
|
||||
from t_sms_sp_user join t_sms_sp_svc_product using (sp_user_seq);'
|
||||
podman exec "$FHS_CONTAINER" sh -lc \
|
||||
'grep -hE "FHS stat updated \\[[^]]*fhs count: [1-9][0-9]*\\]" \
|
||||
/user/service/logs/fimngd/fimngd_*.log | tail -n 1'
|
||||
```
|
||||
|
||||
## 4. Apache와 sh-opendav 배포
|
||||
|
||||
CentOS 6 yum Apache 2.2.15와 실행 모듈을 준비한다. 컴파일러와
|
||||
`httpd-devel`은 [빌드 문서](build.6.md)에서만 다룬다.
|
||||
|
||||
```bash
|
||||
podman exec "$FHS_CONTAINER" yum install -y httpd
|
||||
podman exec "$FHS_CONTAINER" cp \
|
||||
/workspace/fhs/sh-opendav/.libs/libmod_dav_repos.so \
|
||||
/etc/httpd/modules/mod_dav_repos.so
|
||||
podman exec "$FHS_CONTAINER" sed -i \
|
||||
's|^#LoadModule dbd_module|LoadModule dbd_module|' \
|
||||
/etc/httpd/conf/httpd.conf
|
||||
```
|
||||
|
||||
DBD provider와 FHS DAV 설정을 추가한다.
|
||||
|
||||
```bash
|
||||
podman exec "$FHS_CONTAINER" bash -lc '
|
||||
cat > /etc/httpd/conf.d/fhs-opendav-test.conf <<"EOF"
|
||||
DBDriver pgsql
|
||||
DBDPersist On
|
||||
DBDParams "hostaddr=127.0.0.1 dbname=localdb user=syshost password=test"
|
||||
DBDMin 3
|
||||
DBDMax 5
|
||||
DBDKeep 4
|
||||
DBDExptime 900
|
||||
|
||||
DavProviderStorageDir /var/lib/fhs-dav
|
||||
DavProviderBaseURI /dav
|
||||
DavProviderHostName localhost
|
||||
DavProviderMaxFHS 1
|
||||
DavProviderMaxAnonymous 1
|
||||
DavProviderMaxAccount 20
|
||||
DavProviderMaxProgess 1
|
||||
DavProviderDBCharset UHC
|
||||
DavProviderCloseInsert off
|
||||
|
||||
<Location /dav>
|
||||
Dav repos
|
||||
DavDepthInfinity on
|
||||
Options Indexes FollowSymLinks
|
||||
AuthType Basic
|
||||
AuthName "-- OpenDisk Login --"
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
<Limit GET PUT POST DELETE PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK>
|
||||
Require valid-user
|
||||
</Limit>
|
||||
</Location>
|
||||
EOF
|
||||
grep -q '^LoadModule dav_repos_module modules/mod_dav_repos.so' \
|
||||
/etc/httpd/conf/httpd.conf || \
|
||||
printf "%s\n" "LoadModule dav_repos_module modules/mod_dav_repos.so" \
|
||||
>> /etc/httpd/conf/httpd.conf
|
||||
chown -R apache:apache /var/lib/fhs-dav
|
||||
httpd -t
|
||||
'
|
||||
```
|
||||
|
||||
모듈과 Apache가 로드되는지 확인하고 실행한다.
|
||||
|
||||
```bash
|
||||
podman exec "$FHS_CONTAINER" httpd -M 2>&1 | \
|
||||
grep -E 'dbd_module|dav_repos_module|dav_module|dav_fs_module'
|
||||
if ! podman exec "$FHS_CONTAINER" ps axo stat,comm | \
|
||||
awk '$2 == "httpd" && $1 !~ /^Z/ { found = 1 } END { exit !found }'; then
|
||||
podman exec -d "$FHS_CONTAINER" /usr/sbin/httpd -DFOREGROUND
|
||||
fi
|
||||
```
|
||||
|
||||
예상 모듈 목록:
|
||||
|
||||
```text
|
||||
dav_module (shared)
|
||||
dav_fs_module (shared)
|
||||
dbd_module (shared)
|
||||
dav_repos_module (shared)
|
||||
```
|
||||
|
||||
## 5. DAV 검증
|
||||
|
||||
검증은 FHS 컨테이너 내부에서 실행한다. 외부 호스트의 `18068`은 별도 Apache
|
||||
컨테이너 포트일 수 있으므로 사용하지 않는다.
|
||||
|
||||
```bash
|
||||
podman exec -it "$FHS_CONTAINER" bash
|
||||
```
|
||||
|
||||
이 셸 안에서 다음을 실행한다. 검증 URL은 `/dav/svc1/`이며 인증 계정은
|
||||
`svc1@davuser:test`다.
|
||||
|
||||
```bash
|
||||
BASE="http://127.0.0.1/dav/svc1"
|
||||
printf 'dav-payload\n' >/tmp/dav-payload
|
||||
|
||||
curl -sS -o /dev/null -w 'OPTIONS %{http_code}\n' \
|
||||
-X OPTIONS "$BASE/" -u 'svc1@davuser:test'
|
||||
curl -sS -o /dev/null -w 'MKCOL %{http_code}\n' \
|
||||
-X MKCOL "$BASE/check/" -u 'svc1@davuser:test'
|
||||
curl -sS -o /dev/null -w 'PUT %{http_code}\n' \
|
||||
-X PUT "$BASE/check/a.txt" --data-binary @/tmp/dav-payload \
|
||||
-u 'svc1@davuser:test'
|
||||
curl -sS -o /tmp/dav-get -w 'GET %{http_code}\n' \
|
||||
"$BASE/check/a.txt" -u 'svc1@davuser:test'
|
||||
curl -sS -o /dev/null -w 'PROPFIND %{http_code}\n' \
|
||||
-X PROPFIND "$BASE/check/" -H 'Depth: 1' -u 'svc1@davuser:test'
|
||||
cat >/tmp/dav-proppatch.xml <<'EOF'
|
||||
<?xml version="1.0"?>
|
||||
<propertyupdate xmlns="DAV:">
|
||||
<set><prop><displayname>check</displayname></prop></set>
|
||||
</propertyupdate>
|
||||
EOF
|
||||
curl -sS -o /dev/null -w 'PROPPATCH %{http_code}\n' \
|
||||
-X PROPPATCH "$BASE/check/a.txt" \
|
||||
-H 'Content-Type: application/xml' \
|
||||
--data-binary @/tmp/dav-proppatch.xml \
|
||||
-u 'svc1@davuser:test'
|
||||
curl -sS -o /dev/null -w 'COPY %{http_code}\n' \
|
||||
-X COPY "$BASE/check/a.txt" \
|
||||
-H "Destination: $BASE/check/copied.txt" -u 'svc1@davuser:test'
|
||||
curl -sS -o /dev/null -w 'MOVE %{http_code}\n' \
|
||||
-X MOVE "$BASE/check/copied.txt" \
|
||||
-H "Destination: $BASE/check/moved.txt" -u 'svc1@davuser:test'
|
||||
```
|
||||
|
||||
LOCK/UNLOCK은 lock token을 받아서 같은 token으로 해제한다.
|
||||
|
||||
```bash
|
||||
cat >/tmp/dav-lock.xml <<'EOF'
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<lockinfo xmlns="DAV:">
|
||||
<lockscope><exclusive/></lockscope>
|
||||
<locktype><write/></locktype>
|
||||
<owner><href>deploy-check</href></owner>
|
||||
</lockinfo>
|
||||
EOF
|
||||
|
||||
curl -sS -D /tmp/dav-lock.headers -o /dev/null \
|
||||
-w 'LOCK %{http_code}\n' -X LOCK "$BASE/check/a.txt" \
|
||||
-H 'Depth: 0' -H 'Timeout: Second-3600' \
|
||||
-H 'Content-Type: application/xml' --data-binary @/tmp/dav-lock.xml \
|
||||
-u 'svc1@davuser:test'
|
||||
LOCK_TOKEN="$(
|
||||
awk '
|
||||
BEGIN { IGNORECASE = 1 }
|
||||
/^Lock-Token:/ {
|
||||
sub(/^[^:]*:[[:space:]]*/, "")
|
||||
gsub(/[[:space:]]/, "")
|
||||
print
|
||||
}
|
||||
' /tmp/dav-lock.headers
|
||||
)"
|
||||
curl -sS -o /dev/null -w 'UNLOCK %{http_code}\n' \
|
||||
-X UNLOCK "$BASE/check/a.txt" \
|
||||
-H "Lock-Token: $LOCK_TOKEN" -u 'svc1@davuser:test'
|
||||
```
|
||||
|
||||
마지막으로 자원을 삭제하고 본문을 비교한다.
|
||||
|
||||
```bash
|
||||
curl -sS -o /dev/null -w 'DELETE moved %{http_code}\n' \
|
||||
-X DELETE "$BASE/check/moved.txt" -u 'svc1@davuser:test'
|
||||
curl -sS -o /dev/null -w 'DELETE original %{http_code}\n' \
|
||||
-X DELETE "$BASE/check/a.txt" -u 'svc1@davuser:test'
|
||||
curl -sS -o /dev/null -w 'DELETE dir %{http_code}\n' \
|
||||
-X DELETE "$BASE/check/" -u 'svc1@davuser:test'
|
||||
cmp /tmp/dav-payload /tmp/dav-get
|
||||
```
|
||||
|
||||
실제 검증 결과:
|
||||
|
||||
```text
|
||||
OPTIONS 200, PROPFIND 207, PROPPATCH 207
|
||||
MKCOL 201, PUT 201, GET 200
|
||||
COPY 201, MOVE 201, LOCK 200, UNLOCK 204
|
||||
DELETE 204
|
||||
GET 본문: PUT 내용과 일치
|
||||
```
|
||||
|
||||
`PROPPATCH`의 `displayname`은 모듈에서 읽기 전용 live property로 처리하므로
|
||||
전체 응답은 `207`이고 해당 속성의 내부 상태가 `409`인 것은 정상이다.
|
||||
Reference in New Issue
Block a user