Merge pull request #24 from democratic-csi/next

version auto-detection, delete race conditions, sudo, apiKey, multipath
This commit is contained in:
Travis Glenn Hansen 2020-12-03 16:45:31 -07:00 committed by GitHub
commit 148c8d9ac0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 2040 additions and 915 deletions

View File

@ -1,4 +1,7 @@
chart chart
dev dev
examples examples
contrib
node_modules node_modules
Dockerfile*
TODO.md

View File

@ -13,6 +13,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.6.0
with:
access_token: ${{ github.token }}
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: docker build - name: docker build
run: | run: |

View File

@ -1,16 +1,22 @@
FROM debian:10-slim FROM debian:10-slim AS build
#FROM --platform=$BUILDPLATFORM debian:10-slim AS build
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
ARG TARGETPLATFORM ARG TARGETPLATFORM
ARG BUILDPLATFORM ARG BUILDPLATFORM
RUN echo "I am running build on $BUILDPLATFORM, building for $TARGETPLATFORM"
RUN apt-get update && apt-get install -y locales && rm -rf /var/lib/apt/lists/* \ RUN apt-get update && apt-get install -y locales && rm -rf /var/lib/apt/lists/* \
&& localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 && localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8
ENV LANG=en_US.utf8 NODE_VERSION=v12.15.0 ENV LANG=en_US.utf8
ENV NODE_VERSION=v12.20.0
#ENV NODE_VERSION=v14.15.1
RUN echo "I am running on $BUILDPLATFORM, building for $TARGETPLATFORM" # install build deps
RUN apt-get update && apt-get install -y python make gcc g++
# install node # install node
RUN apt-get update && apt-get install -y wget xz-utils RUN apt-get update && apt-get install -y wget xz-utils
@ -18,26 +24,6 @@ ADD docker/node-installer.sh /usr/local/sbin
RUN chmod +x /usr/local/sbin/node-installer.sh && node-installer.sh RUN chmod +x /usr/local/sbin/node-installer.sh && node-installer.sh
ENV PATH=/usr/local/lib/nodejs/bin:$PATH ENV PATH=/usr/local/lib/nodejs/bin:$PATH
# node service requirements
RUN apt-get update && \
apt-get install -y xfsprogs fatresize dosfstools open-iscsi lsscsi sg3-utils multipath-tools scsitools nfs-common cifs-utils sudo && \
rm -rf /var/lib/apt/lists/*
# controller requirements
RUN apt-get update && \
apt-get install -y ansible && \
rm -rf /var/lib/apt/lists/*
# npm requirements
# gcc and g++ required by grpc-usd until proper upstream support
RUN apt-get update && \
apt-get install -y python make gcc g++ && \
rm -rf /var/lib/apt/lists/*
# install wrappers
ADD docker/iscsiadm /usr/local/sbin
RUN chmod +x /usr/local/sbin/iscsiadm
# Run as a non-root user # Run as a non-root user
RUN useradd --create-home csi \ RUN useradd --create-home csi \
&& mkdir /home/csi/app \ && mkdir /home/csi/app \
@ -47,15 +33,65 @@ USER csi
COPY package*.json ./ COPY package*.json ./
RUN npm install RUN npm install
COPY --chown=csi:csi . . COPY --chown=csi:csi . .
RUN rm -rf docker
USER root
# remove build deps ######################
# actual image
######################
FROM debian:10-slim
ENV DEBIAN_FRONTEND=noninteractive
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN echo "I am running on final $BUILDPLATFORM, building for $TARGETPLATFORM"
RUN apt-get update && apt-get install -y locales && rm -rf /var/lib/apt/lists/* \
&& localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8
ENV LANG=en_US.utf8
# install node
ENV PATH=/usr/local/lib/nodejs/bin:$PATH
COPY --from=build /usr/local/lib/nodejs /usr/local/lib/nodejs
# node service requirements
# netbase is required by rpcbind/rpcinfo to work properly
# /etc/{services,rpc} are required
RUN apt-get update && \
apt-get install -y netbase socat e2fsprogs xfsprogs fatresize dosfstools nfs-common cifs-utils sudo && \
rm -rf /var/lib/apt/lists/*
# controller requirements
#RUN apt-get update && \ #RUN apt-get update && \
# apt-get purge -y python make gcc g++ && \ # apt-get install -y ansible && \
# rm -rf /var/lib/apt/lists/* # rm -rf /var/lib/apt/lists/*
# install wrappers
ADD docker/iscsiadm /usr/local/sbin
RUN chmod +x /usr/local/sbin/iscsiadm
ADD docker/multipath /usr/local/sbin
RUN chmod +x /usr/local/sbin/multipath
## USE_HOST_MOUNT_TOOLS=1
ADD docker/mount /usr/local/bin/mount
RUN chmod +x /usr/local/bin/mount
## USE_HOST_MOUNT_TOOLS=1
ADD docker/umount /usr/local/bin/umount
RUN chmod +x /usr/local/bin/umount
# Run as a non-root user
RUN useradd --create-home csi \
&& chown -R csi: /home/csi
COPY --from=build --chown=csi:csi /home/csi/app /home/csi/app
WORKDIR /home/csi/app
EXPOSE 50051 EXPOSE 50051
ENTRYPOINT [ "bin/democratic-csi" ] ENTRYPOINT [ "bin/democratic-csi" ]

59
Dockerfile.unified Normal file
View File

@ -0,0 +1,59 @@
FROM debian:10-slim
ENV DEBIAN_FRONTEND=noninteractive
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN apt-get update && apt-get install -y locales && rm -rf /var/lib/apt/lists/* \
&& localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8
ENV LANG=en_US.utf8 NODE_VERSION=v12.20.0
RUN echo "I am running on $BUILDPLATFORM, building for $TARGETPLATFORM"
# install node
RUN apt-get update && apt-get install -y wget xz-utils
ADD docker/node-installer.sh /usr/local/sbin
RUN chmod +x /usr/local/sbin/node-installer.sh && node-installer.sh
ENV PATH=/usr/local/lib/nodejs/bin:$PATH
# node service requirements
RUN apt-get update && \
apt-get install -y e2fsprogs xfsprogs fatresize dosfstools nfs-common cifs-utils sudo && \
rm -rf /var/lib/apt/lists/*
# controller requirements
RUN apt-get update && \
apt-get install -y ansible && \
rm -rf /var/lib/apt/lists/*
# npm requirements
# gcc and g++ required by grpc-usd until proper upstream support
RUN apt-get update && \
apt-get install -y python make gcc g++ && \
rm -rf /var/lib/apt/lists/*
# install wrappers
ADD docker/iscsiadm /usr/local/sbin
RUN chmod +x /usr/local/sbin/iscsiadm
ADD docker/multipath /usr/local/sbin
RUN chmod +x /usr/local/sbin/multipath
# Run as a non-root user
RUN useradd --create-home csi \
&& mkdir /home/csi/app \
&& chown -R csi: /home/csi
WORKDIR /home/csi/app
USER csi
COPY package*.json ./
RUN npm install
COPY --chown=csi:csi . .
USER root
EXPOSE 50051
ENTRYPOINT [ "bin/democratic-csi" ]

View File

@ -40,6 +40,9 @@ You should install/configure the requirements for both nfs and iscsi.
Follow the instructions here: https://netapp-trident.readthedocs.io/en/stable-v20.04/kubernetes/operations/tasks/worker.html Follow the instructions here: https://netapp-trident.readthedocs.io/en/stable-v20.04/kubernetes/operations/tasks/worker.html
Note that `multipath` is supported for the `iscsi`-based drivers. Simply setup
multipath to your liking and set multiple portals in the config as appropriate.
If you are running Kubernetes with rancher/rke please see the following: If you are running Kubernetes with rancher/rke please see the following:
- https://github.com/rancher/rke/issues/1846 - https://github.com/rancher/rke/issues/1846
@ -71,14 +74,43 @@ Server preparation depends slightly on which `driver` you are using.
### FreeNAS (freenas-nfs, freenas-iscsi, freenas-smb) ### FreeNAS (freenas-nfs, freenas-iscsi, freenas-smb)
The recommended version of FreeNAS is 11.3+, however the driver should work
with much older versions as well.
Ensure the following services are configurged and running: Ensure the following services are configurged and running:
- ssh (if you use a password for authentication make sure it is allowed) - ssh (if you use a password for authentication make sure it is allowed)
- ensure `zsh`, `bash`, or `sh` is set as the root shell, `csh` gives false errors due to quoting - ensure `zsh`, `bash`, or `sh` is set as the root shell, `csh` gives false errors due to quoting
- nfs - nfs
- iscsi - iscsi
- when using the FreeNAS API concurrently the `/etc/ctl.conf` file on the
server can become invalid, some sample scripts are provided in the
`contrib` directory to clean things up
ie: copy the script to the server and directly and run - `./ctld-config-watchdog-db.sh | logger -t ctld-config-watchdog-db.sh &`
please read the scripts and set the variables as appropriate for your server.
- ensure you have pre-emptively created portal, group, auth
- smb - smb
In addition, if you want to use a non-root user for the ssh operations you may
create a `csi` user and then run `visudo` directly from the console. Make sure
the line for the `csi` user has `NOPASSWD` added (note this can get reset by
FreeNAS if you alter the user via the GUI later):
```
csi ALL=(ALL) NOPASSWD:ALL
```
Starting with TrueNAS CORE 12 it is also possible to use an `apiKey` instead of
the `root` password for the http connection.
Issues to review:
- https://jira.ixsystems.com/browse/NAS-108519
- https://jira.ixsystems.com/browse/NAS-108520
- https://jira.ixsystems.com/browse/NAS-108521
- https://jira.ixsystems.com/browse/NAS-108522
- https://jira.ixsystems.com/browse/NAS-107219
### ZoL (zfs-generic-nfs, zfs-generic-iscsi) ### ZoL (zfs-generic-nfs, zfs-generic-iscsi)
Ensure ssh and zfs is installed on the server and that you have installed Ensure ssh and zfs is installed on the server and that you have installed
@ -115,9 +147,10 @@ helm upgrade \
--namespace democratic-csi \ --namespace democratic-csi \
zfs-nfs democratic-csi/democratic-csi zfs-nfs democratic-csi/democratic-csi
``` ```
### A note on non standard kubelet paths ### A note on non standard kubelet paths
Some distrobutions, such as `minikube` and `microk8s` uses a non-standard kubelet path. Some distrobutions, such as `minikube` and `microk8s` uses a non-standard kubelet path.
In such cases it is necessary to provide a new kubelet host path, microk8s example below: In such cases it is necessary to provide a new kubelet host path, microk8s example below:
```bash ```bash

View File

@ -0,0 +1,54 @@
#!/bin/bash
WAIT_TIME_SECS=30
USERNAME="root"
PASSWORD="secret"
BASE_URL="http://localhost"
LIMIT=1000
while [ 1 ]; do
sleep "${WAIT_TIME_SECS}"
# ctl extents
CTL_EXTENT_COUNT=$(ctladm devlist | tail -n +2 | wc -l | sed 's/^[ \t]*//;s/[ \t]*$//')
echo "ctl extent count: ${CTL_EXTENT_COUNT}"
# ctl luns
CTL_LUN_COUNT=$(ctladm lunlist | wc -l | sed 's/^[ \t]*//;s/[ \t]*$//')
echo "ctl lun count: ${CTL_LUN_COUNT}"
# db targets
DB_TARGET_COUNT=$(curl --user "${USERNAME}:${PASSWORD}" "${BASE_URL}/api/v2.0/iscsi/target?limit=${LIMIT}" 2>/dev/null | jq length)
echo "DB target count: ${DB_TARGET_COUNT}"
# db extents
DB_EXTENT_COUNT=$(curl --user "${USERNAME}:${PASSWORD}" "${BASE_URL}/api/v2.0/iscsi/extent?limit=${LIMIT}" 2>/dev/null | jq length)
echo "DB extent count: ${DB_EXTENT_COUNT}"
# db luns
DB_LUN_COUNT=$(curl --user "${USERNAME}:${PASSWORD}" "${BASE_URL}/api/v2.0/iscsi/targetextent?limit=${LIMIT}" 2>/dev/null | jq length)
echo "DB lun count: ${DB_LUN_COUNT}"
REGEN=0
if [[ ${CTL_TARGET_COUNT} -ne ${DB_TARGET_COUNT} ]]; then
REGEN=1
fi
if [[ ${CTL_EXTENT_COUNT} -ne ${DB_EXTENT_COUNT} ]]; then
REGEN=1
fi
if [[ ${CTL_LUN_COUNT} -ne ${DB_LUN_COUNT} ]]; then
REGEN=1
fi
if [[ ${REGEN} -eq 1 ]]; then
echo "regen ctld config"
midclt call etc.generate ctld &>/dev/null
echo "reload ctld service"
/etc/rc.d/ctld reload &>/dev/null
fi
done

View File

@ -0,0 +1,19 @@
#!/bin/bash
# under certain circumstances high concurrency requests to the FreeNAS/TrueNAS
# API can result in an invalid /etc/ctl.conf written to disk
# this script attempts to mitigate those failures by forcing a rebuild of the
# file using info strictly from the sqlite DB
# can test with this
# logger -t ctld "error in configuration file"
while [ 1 ]; do
egrep -m 1 "ctld.*error in configuration file" <(tail -n 0 -F /var/log/messages) &>/dev/null
echo "regen ctld config"
midclt call etc.generate ctld &>/dev/null
echo "reload ctld service"
/etc/rc.d/ctld reload &>/dev/null
done

View File

@ -0,0 +1,16 @@
#!/bin/bash
# watch the ctld pid file and ensure the service is actually running
while [ 1 ]; do
sleep 5
ps -p $(cat /var/run/ctld.pid) | grep ctld &>/dev/null || {
echo "ctld not running, restarting"
echo "regen ctld config"
midclt call etc.generate ctld &>/dev/null
echo "restart ctld service"
/etc/rc.d/ctld restart &>/dev/null
}
done

7
docker/mount Normal file
View File

@ -0,0 +1,7 @@
#!/bin/bash
if [[ ${USE_HOST_MOUNT_TOOLS} -eq 1 ]];then
chroot /host /usr/bin/env -i PATH="/sbin:/bin:/usr/bin:/usr/sbin" mount "${@:1}"
else
/usr/bin/env -i PATH="/sbin:/bin:/usr/bin:/usr/sbin" mount "${@:1}"
fi

3
docker/multipath Normal file
View File

@ -0,0 +1,3 @@
#!/bin/bash
chroot /host /usr/bin/env -i PATH="/sbin:/bin:/usr/sbin:/usr/bin" multipath "${@:1}"

7
docker/umount Normal file
View File

@ -0,0 +1,7 @@
#!/bin/bash
if [[ ${USE_HOST_MOUNT_TOOLS} -eq 1 ]];then
chroot /host /usr/bin/env -i PATH="/sbin:/bin:/usr/bin:/usr/sbin" umount "${@:1}"
else
/usr/bin/env -i PATH="/sbin:/bin:/usr/bin:/usr/sbin" umount "${@:1}"
fi

View File

@ -4,9 +4,16 @@ httpConnection:
protocol: http protocol: http
host: server address host: server address
port: 80 port: 80
# use only 1 of apiKey or username/password
# if both are present, apiKey is preferred
# apiKey is only available starting in TrueNAS-12
#apiKey:
username: root username: root
password: password:
allowInsecure: true allowInsecure: true
# use apiVersion 2 for TrueNAS-12 and up (will work on 11.x in some scenarios as well)
# leave unset for auto-detection
#apiVersion: 2
sshConnection: sshConnection:
host: server address host: server address
port: 22 port: 22
@ -21,6 +28,9 @@ zfs:
# can be used to override defaults if necessary # can be used to override defaults if necessary
# the example below is useful for TrueNAS 12 # the example below is useful for TrueNAS 12
#cli: #cli:
# sudoEnabled: true
#
# leave paths unset for auto-detection
# paths: # paths:
# zfs: /usr/local/sbin/zfs # zfs: /usr/local/sbin/zfs
# zpool: /usr/local/sbin/zpool # zpool: /usr/local/sbin/zpool

View File

@ -4,9 +4,16 @@ httpConnection:
protocol: http protocol: http
host: server address host: server address
port: 80 port: 80
# use only 1 of apiKey or username/password
# if both are present, apiKey is preferred
# apiKey is only available starting in TrueNAS-12
#apiKey:
username: root username: root
password: password:
allowInsecure: true allowInsecure: true
# use apiVersion 2 for TrueNAS-12 and up (will work on 11.x in some scenarios as well)
# leave unset for auto-detection
#apiVersion: 2
sshConnection: sshConnection:
host: server address host: server address
port: 22 port: 22
@ -21,6 +28,9 @@ zfs:
# can be used to override defaults if necessary # can be used to override defaults if necessary
# the example below is useful for TrueNAS 12 # the example below is useful for TrueNAS 12
#cli: #cli:
# sudoEnabled: true
#
# leave paths unset for auto-detection
# paths: # paths:
# zfs: /usr/local/sbin/zfs # zfs: /usr/local/sbin/zfs
# zpool: /usr/local/sbin/zpool # zpool: /usr/local/sbin/zpool

View File

@ -4,9 +4,16 @@ httpConnection:
protocol: http protocol: http
host: server address host: server address
port: 80 port: 80
# use only 1 of apiKey or username/password
# if both are present, apiKey is preferred
# apiKey is only available starting in TrueNAS-12
#apiKey:
username: root username: root
password: password:
allowInsecure: true allowInsecure: true
# use apiVersion 2 for TrueNAS-12 and up (will work on 11.x in some scenarios as well)
# leave unset for auto-detection
#apiVersion: 2
sshConnection: sshConnection:
host: server address host: server address
port: 22 port: 22
@ -21,6 +28,9 @@ zfs:
# can be used to override defaults if necessary # can be used to override defaults if necessary
# the example below is useful for TrueNAS 12 # the example below is useful for TrueNAS 12
#cli: #cli:
# sudoEnabled: true
#
# leave paths unset for auto-detection
# paths: # paths:
# zfs: /usr/local/sbin/zfs # zfs: /usr/local/sbin/zfs
# zpool: /usr/local/sbin/zpool # zpool: /usr/local/sbin/zpool

View File

@ -17,6 +17,7 @@ zfs:
# can be used to override defaults if necessary # can be used to override defaults if necessary
# the example below is useful for TrueNAS 12 # the example below is useful for TrueNAS 12
#cli: #cli:
# sudoEnabled: true
# paths: # paths:
# zfs: /usr/local/sbin/zfs # zfs: /usr/local/sbin/zfs
# zpool: /usr/local/sbin/zpool # zpool: /usr/local/sbin/zpool
@ -50,7 +51,8 @@ iscsi:
# http://www.linux-iscsi.org/wiki/ISCSI # http://www.linux-iscsi.org/wiki/ISCSI
# https://bugzilla.redhat.com/show_bug.cgi?id=1659195 # https://bugzilla.redhat.com/show_bug.cgi?id=1659195
# http://atodorov.org/blog/2015/04/07/how-to-configure-iscsi-target-on-red-hat-enterprise-linux-7/ # http://atodorov.org/blog/2015/04/07/how-to-configure-iscsi-target-on-red-hat-enterprise-linux-7/
shareStragetyTargetCli: shareStrategyTargetCli:
#sudoEnabled: true
basename: "iqn.2003-01.org.linux-iscsi.ubuntu-19.x8664" basename: "iqn.2003-01.org.linux-iscsi.ubuntu-19.x8664"
tpg: tpg:
attributes: attributes:

View File

@ -17,6 +17,7 @@ zfs:
# can be used to override defaults if necessary # can be used to override defaults if necessary
# the example below is useful for TrueNAS 12 # the example below is useful for TrueNAS 12
#cli: #cli:
# sudoEnabled: true
# paths: # paths:
# zfs: /usr/local/sbin/zfs # zfs: /usr/local/sbin/zfs
# zpool: /usr/local/sbin/zpool # zpool: /usr/local/sbin/zpool

227
package-lock.json generated
View File

@ -50,9 +50,9 @@
} }
}, },
"@eslint/eslintrc": { "@eslint/eslintrc": {
"version": "0.1.3", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.1.tgz",
"integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==", "integrity": "sha512-XRUeBZ5zBWLYgSANMpThFddrZZkEbGHgUdt5UJjZfnlN9BGCiUBrf+nvbRupSjMvqzwnQN0qwCmOxITt1cfywA==",
"requires": { "requires": {
"ajv": "^6.12.4", "ajv": "^6.12.4",
"debug": "^4.1.1", "debug": "^4.1.1",
@ -67,9 +67,9 @@
}, },
"dependencies": { "dependencies": {
"ajv": { "ajv": {
"version": "6.12.4", "version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"requires": { "requires": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
@ -169,11 +169,6 @@
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
}, },
"@types/color-name": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
},
"@types/long": { "@types/long": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
@ -185,14 +180,14 @@
"integrity": "sha512-dJ9vXxJ8MEwzNn4GkoAGauejhXoKuJyYKegsA6Af25ZpEDXomeVXt5HUWUNVHk5UN7+U0f6ghC6otwt+7PdSDg==" "integrity": "sha512-dJ9vXxJ8MEwzNn4GkoAGauejhXoKuJyYKegsA6Af25ZpEDXomeVXt5HUWUNVHk5UN7+U0f6ghC6otwt+7PdSDg=="
}, },
"acorn": { "acorn": {
"version": "7.4.0", "version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==" "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="
}, },
"acorn-jsx": { "acorn-jsx": {
"version": "5.2.0", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
"integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==" "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng=="
}, },
"ajv": { "ajv": {
"version": "6.12.3", "version": "6.12.3",
@ -324,11 +319,6 @@
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
}, },
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
"caseless": { "caseless": {
"version": "0.12.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
@ -344,11 +334,10 @@
}, },
"dependencies": { "dependencies": {
"ansi-styles": { "ansi-styles": {
"version": "4.2.1", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": { "requires": {
"@types/color-name": "^1.1.1",
"color-convert": "^2.0.1" "color-convert": "^2.0.1"
} }
}, },
@ -381,13 +370,13 @@
} }
}, },
"cliui": { "cliui": {
"version": "6.0.0", "version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
"requires": { "requires": {
"string-width": "^4.2.0", "string-width": "^4.2.0",
"strip-ansi": "^6.0.0", "strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0" "wrap-ansi": "^7.0.0"
}, },
"dependencies": { "dependencies": {
"emoji-regex": { "emoji-regex": {
@ -504,11 +493,11 @@
} }
}, },
"debug": { "debug": {
"version": "4.1.1", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": { "requires": {
"ms": "^2.1.1" "ms": "2.1.2"
} }
}, },
"decamelize": { "decamelize": {
@ -570,27 +559,32 @@
"ansi-colors": "^4.1.1" "ansi-colors": "^4.1.1"
} }
}, },
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
},
"escape-string-regexp": { "escape-string-regexp": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
}, },
"eslint": { "eslint": {
"version": "7.8.1", "version": "7.14.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.8.1.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.14.0.tgz",
"integrity": "sha512-/2rX2pfhyUG0y+A123d0ccXtMm7DV7sH1m3lk9nk2DZ2LReq39FXHueR9xZwshE5MdfSf0xunSaMWRqyIA6M1w==", "integrity": "sha512-5YubdnPXrlrYAFCKybPuHIAH++PINe1pmKNc5wQRB9HSbqIK1ywAnntE3Wwua4giKu0bjligf1gLF6qxMGOYRA==",
"requires": { "requires": {
"@babel/code-frame": "^7.0.0", "@babel/code-frame": "^7.0.0",
"@eslint/eslintrc": "^0.1.3", "@eslint/eslintrc": "^0.2.1",
"ajv": "^6.10.0", "ajv": "^6.10.0",
"chalk": "^4.0.0", "chalk": "^4.0.0",
"cross-spawn": "^7.0.2", "cross-spawn": "^7.0.2",
"debug": "^4.0.1", "debug": "^4.0.1",
"doctrine": "^3.0.0", "doctrine": "^3.0.0",
"enquirer": "^2.3.5", "enquirer": "^2.3.5",
"eslint-scope": "^5.1.0", "eslint-scope": "^5.1.1",
"eslint-utils": "^2.1.0", "eslint-utils": "^2.1.0",
"eslint-visitor-keys": "^1.3.0", "eslint-visitor-keys": "^2.0.0",
"espree": "^7.3.0", "espree": "^7.3.0",
"esquery": "^1.2.0", "esquery": "^1.2.0",
"esutils": "^2.0.2", "esutils": "^2.0.2",
@ -620,11 +614,11 @@
} }
}, },
"eslint-scope": { "eslint-scope": {
"version": "5.1.0", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
"integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
"requires": { "requires": {
"esrecurse": "^4.1.0", "esrecurse": "^4.3.0",
"estraverse": "^4.1.1" "estraverse": "^4.1.1"
} }
}, },
@ -634,12 +628,19 @@
"integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
"requires": { "requires": {
"eslint-visitor-keys": "^1.1.0" "eslint-visitor-keys": "^1.1.0"
},
"dependencies": {
"eslint-visitor-keys": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ=="
}
} }
}, },
"eslint-visitor-keys": { "eslint-visitor-keys": {
"version": "1.3.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz",
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ=="
}, },
"espree": { "espree": {
"version": "7.3.0", "version": "7.3.0",
@ -649,6 +650,13 @@
"acorn": "^7.4.0", "acorn": "^7.4.0",
"acorn-jsx": "^5.2.0", "acorn-jsx": "^5.2.0",
"eslint-visitor-keys": "^1.3.0" "eslint-visitor-keys": "^1.3.0"
},
"dependencies": {
"eslint-visitor-keys": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ=="
}
} }
}, },
"esprima": { "esprima": {
@ -739,15 +747,6 @@
"flat-cache": "^2.0.1" "flat-cache": "^2.0.1"
} }
}, },
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"requires": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
}
},
"flat-cache": { "flat-cache": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
@ -903,9 +902,9 @@
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg=="
}, },
"import-fresh": { "import-fresh": {
"version": "3.2.1", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz",
"integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", "integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==",
"requires": { "requires": {
"parent-module": "^1.0.0", "parent-module": "^1.0.0",
"resolve-from": "^4.0.0" "resolve-from": "^4.0.0"
@ -1055,14 +1054,6 @@
"type-check": "~0.4.0" "type-check": "~0.4.0"
} }
}, },
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"requires": {
"p-locate": "^4.1.0"
}
},
"lodash": { "lodash": {
"version": "4.17.20", "version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
@ -1232,27 +1223,6 @@
"lcid": "^1.0.0" "lcid": "^1.0.0"
} }
}, },
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"requires": {
"p-limit": "^2.2.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
"parent-module": { "parent-module": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -1261,11 +1231,6 @@
"callsites": "^3.0.0" "callsites": "^3.0.0"
} }
}, },
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
},
"path-is-absolute": { "path-is-absolute": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@ -1452,11 +1417,6 @@
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
}, },
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
"resolve-from": { "resolve-from": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@ -1507,11 +1467,6 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
}, },
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"shebang-command": { "shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@ -1732,14 +1687,14 @@
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
}, },
"uuid": { "uuid": {
"version": "8.3.0", "version": "8.3.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
"integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==" "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="
}, },
"v8-compile-cache": { "v8-compile-cache": {
"version": "2.1.1", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz",
"integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==" "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q=="
}, },
"verror": { "verror": {
"version": "1.10.0", "version": "1.10.0",
@ -1759,11 +1714,6 @@
"isexe": "^2.0.0" "isexe": "^2.0.0"
} }
}, },
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
},
"window-size": { "window-size": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz",
@ -1834,9 +1784,9 @@
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
}, },
"wrap-ansi": { "wrap-ansi": {
"version": "6.2.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"requires": { "requires": {
"ansi-styles": "^4.0.0", "ansi-styles": "^4.0.0",
"string-width": "^4.1.0", "string-width": "^4.1.0",
@ -1844,11 +1794,10 @@
}, },
"dependencies": { "dependencies": {
"ansi-styles": { "ansi-styles": {
"version": "4.2.1", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": { "requires": {
"@types/color-name": "^1.1.1",
"color-convert": "^2.0.1" "color-convert": "^2.0.1"
} }
}, },
@ -1901,9 +1850,9 @@
} }
}, },
"y18n": { "y18n": {
"version": "4.0.0", "version": "5.0.5",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg=="
}, },
"yallist": { "yallist": {
"version": "4.0.0", "version": "4.0.0",
@ -1911,21 +1860,17 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}, },
"yargs": { "yargs": {
"version": "15.4.1", "version": "16.1.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.1.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "integrity": "sha512-hAD1RcFP/wfgfxgMVswPE+z3tlPFtxG8/yWUrG2i17sTWGCGqWnxKcLTF4cUKDUK8fzokwsmO9H0TDkRbMHy8w==",
"requires": { "requires": {
"cliui": "^6.0.0", "cliui": "^7.0.2",
"decamelize": "^1.2.0", "escalade": "^3.1.1",
"find-up": "^4.1.0", "get-caller-file": "^2.0.5",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1", "require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0", "string-width": "^4.2.0",
"which-module": "^2.0.0", "y18n": "^5.0.5",
"y18n": "^4.0.0", "yargs-parser": "^20.2.2"
"yargs-parser": "^18.1.2"
}, },
"dependencies": { "dependencies": {
"emoji-regex": { "emoji-regex": {
@ -1951,13 +1896,9 @@
} }
}, },
"yargs-parser": { "yargs-parser": {
"version": "18.1.3", "version": "20.2.4",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA=="
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
} }
} }
} }

View File

@ -20,7 +20,7 @@
"dependencies": { "dependencies": {
"@grpc/proto-loader": "^0.5.5", "@grpc/proto-loader": "^0.5.5",
"bunyan": "^1.8.14", "bunyan": "^1.8.14",
"eslint": "^7.8.1", "eslint": "^7.14.0",
"grpc-uds": "^0.1.4", "grpc-uds": "^0.1.4",
"handlebars": "^4.7.6", "handlebars": "^4.7.6",
"js-yaml": "^3.14.0", "js-yaml": "^3.14.0",
@ -28,8 +28,8 @@
"request": "^2.88.2", "request": "^2.88.2",
"ssh2": "^0.8.9", "ssh2": "^0.8.9",
"uri-js": "^4.4.0", "uri-js": "^4.4.0",
"uuid": "^8.3.0", "uuid": "^8.3.1",
"winston": "^3.3.3", "winston": "^3.3.3",
"yargs": "^15.4.1" "yargs": "^16.1.1"
} }
} }

View File

@ -26,7 +26,7 @@ class ControllerZfsGenericDriver extends ControllerZfsSshBaseDriver {
* @param {*} datasetName * @param {*} datasetName
*/ */
async createShare(call, datasetName) { async createShare(call, datasetName) {
const zb = this.getZetabyte(); const zb = await this.getZetabyte();
const sshClient = this.getSshClient(); const sshClient = this.getSshClient();
let properties; let properties;
@ -105,25 +105,25 @@ class ControllerZfsGenericDriver extends ControllerZfsSshBaseDriver {
switch (this.options.iscsi.shareStrategy) { switch (this.options.iscsi.shareStrategy) {
case "targetCli": case "targetCli":
basename = this.options.iscsi.shareStragetyTargetCli.basename; basename = this.options.iscsi.shareStrategyTargetCli.basename;
let setAttributesText = ""; let setAttributesText = "";
let setAuthText = ""; let setAuthText = "";
if (this.options.iscsi.shareStragetyTargetCli.tpg) { if (this.options.iscsi.shareStrategyTargetCli.tpg) {
if (this.options.iscsi.shareStragetyTargetCli.tpg.attributes) { if (this.options.iscsi.shareStrategyTargetCli.tpg.attributes) {
for (const attributeName in this.options.iscsi for (const attributeName in this.options.iscsi
.shareStragetyTargetCli.tpg.attributes) { .shareStrategyTargetCli.tpg.attributes) {
const attributeValue = this.options.iscsi const attributeValue = this.options.iscsi
.shareStragetyTargetCli.tpg.attributes[attributeName]; .shareStrategyTargetCli.tpg.attributes[attributeName];
setAttributesText += "\n"; setAttributesText += "\n";
setAttributesText += `set attribute ${attributeName}=${attributeValue}`; setAttributesText += `set attribute ${attributeName}=${attributeValue}`;
} }
} }
if (this.options.iscsi.shareStragetyTargetCli.tpg.auth) { if (this.options.iscsi.shareStrategyTargetCli.tpg.auth) {
for (const attributeName in this.options.iscsi for (const attributeName in this.options.iscsi
.shareStragetyTargetCli.tpg.auth) { .shareStrategyTargetCli.tpg.auth) {
const attributeValue = this.options.iscsi const attributeValue = this.options.iscsi
.shareStragetyTargetCli.tpg.auth[attributeName]; .shareStrategyTargetCli.tpg.auth[attributeName];
setAttributesText += "\n"; setAttributesText += "\n";
setAttributesText += `set auth ${attributeName}=${attributeValue}`; setAttributesText += `set auth ${attributeName}=${attributeValue}`;
} }
@ -178,7 +178,7 @@ create /backstores/block/${iscsiName}
} }
async deleteShare(call, datasetName) { async deleteShare(call, datasetName) {
const zb = this.getZetabyte(); const zb = await this.getZetabyte();
const sshClient = this.getSshClient(); const sshClient = this.getSshClient();
let response; let response;
@ -210,7 +210,7 @@ create /backstores/block/${iscsiName}
iscsiName = iscsiName.toLowerCase(); iscsiName = iscsiName.toLowerCase();
switch (this.options.iscsi.shareStrategy) { switch (this.options.iscsi.shareStrategy) {
case "targetCli": case "targetCli":
basename = this.options.iscsi.shareStragetyTargetCli.basename; basename = this.options.iscsi.shareStrategyTargetCli.basename;
response = await this.targetCliCommand( response = await this.targetCliCommand(
` `
cd /iscsi cd /iscsi
@ -260,14 +260,21 @@ delete ${iscsiName}
const sshClient = this.getSshClient(); const sshClient = this.getSshClient();
data = data.trim(); data = data.trim();
let command = "sh";
let args = ["-c"]; let args = ["-c"];
let command = []; let taregetCliCommand = [];
command.push(`echo "${data}"`.trim()); taregetCliCommand.push(`echo "${data}"`.trim());
command.push("|"); taregetCliCommand.push("|");
command.push("targetcli"); taregetCliCommand.push("targetcli");
args.push("'" + command.join(" ") + "'"); if (this.options.iscsi.shareStrategyTargetCli.sudoEnabled) {
return sshClient.exec(sshClient.buildCommand("sh", args)); command = "sudo";
args.unshift("sh");
}
args.push("'" + taregetCliCommand.join(" ") + "'");
return sshClient.exec(sshClient.buildCommand(command, args));
} }
} }

View File

@ -122,7 +122,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
}); });
} }
getZetabyte() { async getZetabyte() {
const sshClient = this.getSshClient(); const sshClient = this.getSshClient();
const options = {}; const options = {};
options.executor = new ZfsSshProcessManager(sshClient); options.executor = new ZfsSshProcessManager(sshClient);
@ -130,14 +130,36 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
if ( if (
this.options.zfs.hasOwnProperty("cli") && this.options.zfs.hasOwnProperty("cli") &&
this.options.zfs.cli &&
this.options.zfs.cli.hasOwnProperty("paths") this.options.zfs.cli.hasOwnProperty("paths")
) { ) {
options.paths = this.options.zfs.cli.paths; options.paths = this.options.zfs.cli.paths;
} }
if (
this.options.zfs.hasOwnProperty("cli") &&
this.options.zfs.cli &&
this.options.zfs.cli.hasOwnProperty("sudoEnabled")
) {
options.sudo = this.getSudoEnabled();
}
if (typeof this.setZetabyteCustomOptions === "function") {
await this.setZetabyteCustomOptions(options);
}
return new Zetabyte(options); return new Zetabyte(options);
} }
getSudoEnabled() {
return this.options.zfs.cli && this.options.zfs.cli.sudoEnabled === true;
}
async getSudoPath() {
const zb = await this.getZetabyte();
return zb.options.paths.sudo || "/usr/bin/sudo";
}
getDatasetParentName() { getDatasetParentName() {
let datasetParentName = this.options.zfs.datasetParentName; let datasetParentName = this.options.zfs.datasetParentName;
datasetParentName = datasetParentName.replace(/\/$/, ""); datasetParentName = datasetParentName.replace(/\/$/, "");
@ -160,7 +182,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
} }
async removeSnapshotsFromDatatset(datasetName, options = {}) { async removeSnapshotsFromDatatset(datasetName, options = {}) {
const zb = this.getZetabyte(); const zb = await this.getZetabyte();
await zb.zfs.destroy(datasetName + "@%", options); await zb.zfs.destroy(datasetName + "@%", options);
} }
@ -250,7 +272,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
const driver = this; const driver = this;
const driverZfsResourceType = this.getDriverZfsResourceType(); const driverZfsResourceType = this.getDriverZfsResourceType();
const sshClient = this.getSshClient(); const sshClient = this.getSshClient();
const zb = this.getZetabyte(); const zb = await this.getZetabyte();
let datasetParentName = this.getVolumeParentDatasetName(); let datasetParentName = this.getVolumeParentDatasetName();
let snapshotParentDatasetName = this.getDetachedSnapshotParentDatasetName(); let snapshotParentDatasetName = this.getDetachedSnapshotParentDatasetName();
@ -671,8 +693,20 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
this.options.zfs.datasetPermissionsMode, this.options.zfs.datasetPermissionsMode,
properties.mountpoint.value, properties.mountpoint.value,
]); ]);
if (this.getSudoEnabled()) {
command = (await this.getSudoPath()) + " " + command;
}
driver.ctx.logger.verbose("set permission command: %s", command); driver.ctx.logger.verbose("set permission command: %s", command);
response = await sshClient.exec(command); response = await sshClient.exec(command);
if (response.code != 0) {
throw new GrpcError(
grpc.status.UNKNOWN,
`error setting permissions on dataset: ${JSON.stringify(
response
)}`
);
}
} }
// set ownership // set ownership
@ -690,8 +724,18 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
: ""), : ""),
properties.mountpoint.value, properties.mountpoint.value,
]); ]);
if (this.getSudoEnabled()) {
command = (await this.getSudoPath()) + " " + command;
}
driver.ctx.logger.verbose("set ownership command: %s", command); driver.ctx.logger.verbose("set ownership command: %s", command);
response = await sshClient.exec(command); response = await sshClient.exec(command);
if (response.code != 0) {
throw new GrpcError(
grpc.status.UNKNOWN,
`error setting ownership on dataset: ${JSON.stringify(response)}`
);
}
} }
// set acls // set acls
@ -703,8 +747,18 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
acl, acl,
properties.mountpoint.value, properties.mountpoint.value,
]); ]);
if (this.getSudoEnabled()) {
command = (await this.getSudoPath()) + " " + command;
}
driver.ctx.logger.verbose("set acl command: %s", command); driver.ctx.logger.verbose("set acl command: %s", command);
response = await sshClient.exec(command); response = await sshClient.exec(command);
if (response.code != 0) {
throw new GrpcError(
grpc.status.UNKNOWN,
`error setting acl on dataset: ${JSON.stringify(response)}`
);
}
} }
} }
@ -772,7 +826,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
*/ */
async DeleteVolume(call) { async DeleteVolume(call) {
const driver = this; const driver = this;
const zb = this.getZetabyte(); const zb = await this.getZetabyte();
let datasetParentName = this.getVolumeParentDatasetName(); let datasetParentName = this.getVolumeParentDatasetName();
let name = call.request.volume_id; let name = call.request.volume_id;
@ -877,7 +931,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
async ControllerExpandVolume(call) { async ControllerExpandVolume(call) {
const driver = this; const driver = this;
const driverZfsResourceType = this.getDriverZfsResourceType(); const driverZfsResourceType = this.getDriverZfsResourceType();
const zb = this.getZetabyte(); const zb = await this.getZetabyte();
let datasetParentName = this.getVolumeParentDatasetName(); let datasetParentName = this.getVolumeParentDatasetName();
let name = call.request.volume_id; let name = call.request.volume_id;
@ -978,7 +1032,11 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
await this.expandVolume(call, datasetName); await this.expandVolume(call, datasetName);
return { return {
capacity_bytes: this.options.zfs.datasetEnableQuotas ? capacity_bytes : 0, capacity_bytes:
this.options.zfs.datasetEnableQuotas ||
driverZfsResourceType == "volume"
? capacity_bytes
: 0,
node_expansion_required: driverZfsResourceType == "volume" ? true : false, node_expansion_required: driverZfsResourceType == "volume" ? true : false,
}; };
} }
@ -990,7 +1048,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
*/ */
async GetCapacity(call) { async GetCapacity(call) {
const driver = this; const driver = this;
const zb = this.getZetabyte(); const zb = await this.getZetabyte();
let datasetParentName = this.getVolumeParentDatasetName(); let datasetParentName = this.getVolumeParentDatasetName();
@ -1027,7 +1085,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
async ListVolumes(call) { async ListVolumes(call) {
const driver = this; const driver = this;
const driverZfsResourceType = this.getDriverZfsResourceType(); const driverZfsResourceType = this.getDriverZfsResourceType();
const zb = this.getZetabyte(); const zb = await this.getZetabyte();
let datasetParentName = this.getVolumeParentDatasetName(); let datasetParentName = this.getVolumeParentDatasetName();
let entries = []; let entries = [];
@ -1212,7 +1270,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
async ListSnapshots(call) { async ListSnapshots(call) {
const driver = this; const driver = this;
const driverZfsResourceType = this.getDriverZfsResourceType(); const driverZfsResourceType = this.getDriverZfsResourceType();
const zb = this.getZetabyte(); const zb = await this.getZetabyte();
let entries = []; let entries = [];
let entries_length = 0; let entries_length = 0;
@ -1444,7 +1502,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
async CreateSnapshot(call) { async CreateSnapshot(call) {
const driver = this; const driver = this;
const driverZfsResourceType = this.getDriverZfsResourceType(); const driverZfsResourceType = this.getDriverZfsResourceType();
const zb = this.getZetabyte(); const zb = await this.getZetabyte();
let detachedSnapshot = false; let detachedSnapshot = false;
try { try {
@ -1678,7 +1736,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
*/ */
async DeleteSnapshot(call) { async DeleteSnapshot(call) {
const driver = this; const driver = this;
const zb = this.getZetabyte(); const zb = await this.getZetabyte();
const snapshot_id = call.request.snapshot_id; const snapshot_id = call.request.snapshot_id;

View File

@ -4,7 +4,7 @@ const USER_AGENT = "democratic-csi-driver";
class Client { class Client {
constructor(options = {}) { constructor(options = {}) {
this.options = options; this.options = JSON.parse(JSON.stringify(options));
this.logger = console; this.logger = console;
// default to v1.0 for now // default to v1.0 for now
@ -19,7 +19,7 @@ class Client {
host: server.host, host: server.host,
port: server.port, port: server.port,
//userinfo: server.username + ":" + server.password, //userinfo: server.username + ":" + server.password,
path: server.apiVersion == 1 ? "/api/v1.0" : "/api/v2.0" path: server.apiVersion == 1 ? "/api/v1.0" : "/api/v2.0",
}; };
return URI.serialize(options); return URI.serialize(options);
} }
@ -55,22 +55,27 @@ class Client {
headers: { headers: {
Accept: "application/json", Accept: "application/json",
"User-Agent": USER_AGENT, "User-Agent": USER_AGENT,
"Content-Type": "application/json" "Content-Type": "application/json",
}, },
json: true, json: true,
qs: data, qs: data,
agentOptions: { agentOptions: {
rejectUnauthorized: !!!client.options.allowInsecure rejectUnauthorized: !!!client.options.allowInsecure,
} },
}; };
request(options, function(err, res, body) { request(options, function (err, res, body) {
client.log_repsonse(...arguments, options); client.log_repsonse(...arguments, options);
if (err) { if (err) {
reject(err); reject(err);
} }
resolve(res); resolve(res);
}).auth(client.options.username, client.options.password); }).auth(
client.options.username,
client.options.password,
true,
client.options.apiKey
);
}); });
} }
@ -87,22 +92,27 @@ class Client {
headers: { headers: {
Accept: "application/json", Accept: "application/json",
"User-Agent": USER_AGENT, "User-Agent": USER_AGENT,
"Content-Type": "application/json" "Content-Type": "application/json",
}, },
json: true, json: true,
body: data, body: data,
agentOptions: { agentOptions: {
rejectUnauthorized: !!!client.options.allowInsecure rejectUnauthorized: !!!client.options.allowInsecure,
} },
}; };
request(options, function(err, res, body) { request(options, function (err, res, body) {
client.log_repsonse(...arguments, options); client.log_repsonse(...arguments, options);
if (err) { if (err) {
reject(err); reject(err);
} }
resolve(res); resolve(res);
}).auth(client.options.username, client.options.password); }).auth(
client.options.username,
client.options.password,
true,
client.options.apiKey
);
}); });
} }
@ -119,22 +129,27 @@ class Client {
headers: { headers: {
Accept: "application/json", Accept: "application/json",
"User-Agent": USER_AGENT, "User-Agent": USER_AGENT,
"Content-Type": "application/json" "Content-Type": "application/json",
}, },
json: true, json: true,
body: data, body: data,
agentOptions: { agentOptions: {
rejectUnauthorized: !!!client.options.allowInsecure rejectUnauthorized: !!!client.options.allowInsecure,
} },
}; };
request(options, function(err, res, body) { request(options, function (err, res, body) {
client.log_repsonse(...arguments, options); client.log_repsonse(...arguments, options);
if (err) { if (err) {
reject(err); reject(err);
} }
resolve(res); resolve(res);
}).auth(client.options.username, client.options.password); }).auth(
client.options.username,
client.options.password,
true,
client.options.apiKey
);
}); });
} }
@ -151,22 +166,27 @@ class Client {
headers: { headers: {
Accept: "application/json", Accept: "application/json",
"User-Agent": USER_AGENT, "User-Agent": USER_AGENT,
"Content-Type": "application/json" "Content-Type": "application/json",
}, },
json: true, json: true,
body: data, body: data,
agentOptions: { agentOptions: {
rejectUnauthorized: !!!client.options.allowInsecure rejectUnauthorized: !!!client.options.allowInsecure,
} },
}; };
request(options, function(err, res, body) { request(options, function (err, res, body) {
client.log_repsonse(...arguments, options); client.log_repsonse(...arguments, options);
if (err) { if (err) {
reject(err); reject(err);
} }
resolve(res); resolve(res);
}).auth(client.options.username, client.options.password); }).auth(
client.options.username,
client.options.password,
true,
client.options.apiKey
);
}); });
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -254,6 +254,7 @@ class CsiBaseDriver {
* @param {*} call * @param {*} call
*/ */
async NodeStageVolume(call) { async NodeStageVolume(call) {
const driver = this;
const mount = new Mount(); const mount = new Mount();
const filesystem = new Filesystem(); const filesystem = new Filesystem();
const iscsi = new ISCSI(); const iscsi = new ISCSI();
@ -310,46 +311,145 @@ class CsiBaseDriver {
device = `//${volume_context.server}/${volume_context.share}`; device = `//${volume_context.server}/${volume_context.share}`;
break; break;
case "iscsi": case "iscsi":
// create DB entry let portals = [];
// https://library.netapp.com/ecmdocs/ECMP1654943/html/GUID-8EC685B4-8CB6-40D8-A8D5-031A3899BCDC.html if (volume_context.portal) {
// put these options in place to force targets managed by csi to be explicitly attached (in the case of unclearn shutdown etc) portals.push(volume_context.portal.trim());
let nodeDB = {
"node.startup": "manual",
};
const nodeDBKeyPrefix = "node-db.";
for (const key in normalizedSecrets) {
if (key.startsWith(nodeDBKeyPrefix)) {
nodeDB[key.substr(nodeDBKeyPrefix.length)] = normalizedSecrets[key];
}
} }
await iscsi.iscsiadm.createNodeDBEntry(
volume_context.iqn,
volume_context.portal,
nodeDB
);
// login
await iscsi.iscsiadm.login(volume_context.iqn, volume_context.portal);
// find device name if (volume_context.portals) {
device = `/dev/disk/by-path/ip-${volume_context.portal}-iscsi-${volume_context.iqn}-lun-${volume_context.lun}`; volume_context.portals.split(",").forEach((portal) => {
portals.push(portal.trim());
});
}
// can take some time for device to show up, loop for some period // ensure full portal value
result = await filesystem.pathExists(device); portals = portals.map((value) => {
let timer_start = Math.round(new Date().getTime() / 1000); if (!value.includes(":")) {
let timer_max = 30; value += ":3260";
while (!result) { }
await sleep(2000);
return value.trim();
});
// ensure unique entries only
portals = [...new Set(portals)];
let iscsiDevices = [];
for (let portal of portals) {
// create DB entry
// https://library.netapp.com/ecmdocs/ECMP1654943/html/GUID-8EC685B4-8CB6-40D8-A8D5-031A3899BCDC.html
// put these options in place to force targets managed by csi to be explicitly attached (in the case of unclearn shutdown etc)
let nodeDB = {
"node.startup": "manual",
};
const nodeDBKeyPrefix = "node-db.";
for (const key in normalizedSecrets) {
if (key.startsWith(nodeDBKeyPrefix)) {
nodeDB[key.substr(nodeDBKeyPrefix.length)] =
normalizedSecrets[key];
}
}
await iscsi.iscsiadm.createNodeDBEntry(
volume_context.iqn,
portal,
nodeDB
);
// login
await iscsi.iscsiadm.login(volume_context.iqn, portal);
// find device name
device = `/dev/disk/by-path/ip-${portal}-iscsi-${volume_context.iqn}-lun-${volume_context.lun}`;
let deviceByPath = device;
// can take some time for device to show up, loop for some period
result = await filesystem.pathExists(device); result = await filesystem.pathExists(device);
let current_time = Math.round(new Date().getTime() / 1000); let timer_start = Math.round(new Date().getTime() / 1000);
if (!result && current_time - timer_start > timer_max) { let timer_max = 30;
throw new GrpcError( let deviceCreated = result;
grpc.status.UNKNOWN, while (!result) {
`hit timeout waiting for device node to appear: ${device}` await sleep(2000);
result = await filesystem.pathExists(device);
if (result) {
deviceCreated = true;
break;
}
let current_time = Math.round(new Date().getTime() / 1000);
if (!result && current_time - timer_start > timer_max) {
driver.ctx.logger.warn(
`hit timeout waiting for device node to appear: ${device}`
);
break;
}
}
if (deviceCreated) {
device = await filesystem.realpath(device);
iscsiDevices.push(device);
driver.ctx.logger.info(
`successfully logged into portal ${portal} and created device ${deviceByPath} with realpath ${device}`
); );
} }
} }
device = await filesystem.realpath(device); // let things settle
// this will help in dm scenarios
await sleep(2000);
// filter duplicates
iscsiDevices = iscsiDevices.filter((value, index, self) => {
return self.indexOf(value) === index;
});
// only throw an error if we were not able to attach to *any* devices
if (iscsiDevices.length < 1) {
throw new GrpcError(
grpc.status.UNKNOWN,
`unable to attach any iscsi devices`
);
}
if (iscsiDevices.length != portals.length) {
driver.ctx.logger.warn(
`failed to attach all iscsi devices/targets/portals`
);
// TODO: allow a parameter to control this behavior in some form
if (false) {
throw new GrpcError(
grpc.status.UNKNOWN,
`unable to attach all iscsi devices`
);
}
}
// compare all device-mapper slaves with the newly created devices
// if any of the new devices are device-mapper slaves treat this as a
// multipath scenario
let allDeviceMapperSlaves = await filesystem.getAllDeviceMapperSlaveDevices();
let commonDevices = allDeviceMapperSlaves.filter((value) =>
iscsiDevices.includes(value)
);
const useMultipath = portals.length > 1 || commonDevices.length > 0;
// discover multipath device to use
if (useMultipath) {
device = await filesystem.getDeviceMapperDeviceFromSlaves(
iscsiDevices,
false
);
if (!device) {
throw new GrpcError(
grpc.status.UNKNOWN,
`failed to discover multipath device`
);
}
}
break; break;
default: default:
throw new GrpcError( throw new GrpcError(
@ -463,6 +563,7 @@ class CsiBaseDriver {
const iscsi = new ISCSI(); const iscsi = new ISCSI();
let result; let result;
let is_block = false; let is_block = false;
let is_device_mapper = false;
let block_device_info; let block_device_info;
let access_type = "mount"; let access_type = "mount";
@ -505,78 +606,100 @@ class CsiBaseDriver {
} }
if (is_block) { if (is_block) {
if (block_device_info.tran == "iscsi") { let realBlockDeviceInfos = [];
// figure out which iscsi session this belongs to and logout // detect if is a multipath device
// scan /dev/disk/by-path/ip-*? is_device_mapper = await filesystem.isDeviceMapperDevice(
// device = `/dev/disk/by-path/ip-${volume_context.portal}-iscsi-${volume_context.iqn}-lun-${volume_context.lun}`; block_device_info.path
// parse output from `iscsiadm -m session -P 3` );
let sessions = await iscsi.iscsiadm.getSessionsDetails();
for (let i = 0; i < sessions.length; i++) {
let session = sessions[i];
let is_attached_to_session = false;
if ( if (is_device_mapper) {
session.attached_scsi_devices && let realBlockDevices = await filesystem.getDeviceMapperDeviceSlaves(
session.attached_scsi_devices.host && block_device_info.path
session.attached_scsi_devices.host.devices );
) { for (const realBlockDevice of realBlockDevices) {
is_attached_to_session = session.attached_scsi_devices.host.devices.some( realBlockDeviceInfos.push(
(device) => { await filesystem.getBlockDevice(realBlockDevice)
if (device.attached_scsi_disk == block_device_info.name) { );
return true; }
} else {
realBlockDeviceInfos = [block_device_info];
}
// TODO: this could be made async to detach all simultaneously
for (const block_device_info_i of realBlockDeviceInfos) {
if (block_device_info_i.tran == "iscsi") {
// figure out which iscsi session this belongs to and logout
// scan /dev/disk/by-path/ip-*?
// device = `/dev/disk/by-path/ip-${volume_context.portal}-iscsi-${volume_context.iqn}-lun-${volume_context.lun}`;
// parse output from `iscsiadm -m session -P 3`
let sessions = await iscsi.iscsiadm.getSessionsDetails();
for (let i = 0; i < sessions.length; i++) {
let session = sessions[i];
let is_attached_to_session = false;
if (
session.attached_scsi_devices &&
session.attached_scsi_devices.host &&
session.attached_scsi_devices.host.devices
) {
is_attached_to_session = session.attached_scsi_devices.host.devices.some(
(device) => {
if (device.attached_scsi_disk == block_device_info_i.name) {
return true;
}
return false;
} }
return false; );
}
);
}
if (is_attached_to_session) {
let timer_start;
let timer_max;
timer_start = Math.round(new Date().getTime() / 1000);
timer_max = 30;
let loggedOut = false;
while (!loggedOut) {
try {
await iscsi.iscsiadm.logout(session.target, [
session.persistent_portal,
]);
loggedOut = true;
} catch (err) {
await sleep(2000);
let current_time = Math.round(new Date().getTime() / 1000);
if (current_time - timer_start > timer_max) {
// not throwing error for now as future invocations would not enter code path anyhow
loggedOut = true;
//throw new GrpcError(
// grpc.status.UNKNOWN,
// `hit timeout trying to logout of iscsi target: ${session.persistent_portal}`
//);
}
}
} }
timer_start = Math.round(new Date().getTime() / 1000); if (is_attached_to_session) {
timer_max = 30; let timer_start;
let deletedEntry = false; let timer_max;
while (!deletedEntry) {
try { timer_start = Math.round(new Date().getTime() / 1000);
await iscsi.iscsiadm.deleteNodeDBEntry( timer_max = 30;
session.target, let loggedOut = false;
session.persistent_portal while (!loggedOut) {
); try {
deletedEntry = true; await iscsi.iscsiadm.logout(session.target, [
} catch (err) { session.persistent_portal,
await sleep(2000); ]);
let current_time = Math.round(new Date().getTime() / 1000); loggedOut = true;
if (current_time - timer_start > timer_max) { } catch (err) {
// not throwing error for now as future invocations would not enter code path anyhow await sleep(2000);
let current_time = Math.round(new Date().getTime() / 1000);
if (current_time - timer_start > timer_max) {
// not throwing error for now as future invocations would not enter code path anyhow
loggedOut = true;
//throw new GrpcError(
// grpc.status.UNKNOWN,
// `hit timeout trying to logout of iscsi target: ${session.persistent_portal}`
//);
}
}
}
timer_start = Math.round(new Date().getTime() / 1000);
timer_max = 30;
let deletedEntry = false;
while (!deletedEntry) {
try {
await iscsi.iscsiadm.deleteNodeDBEntry(
session.target,
session.persistent_portal
);
deletedEntry = true; deletedEntry = true;
//throw new GrpcError( } catch (err) {
// grpc.status.UNKNOWN, await sleep(2000);
// `hit timeout trying to delete iscsi node DB entry: ${session.target}, ${session.persistent_portal}` let current_time = Math.round(new Date().getTime() / 1000);
//); if (current_time - timer_start > timer_max) {
// not throwing error for now as future invocations would not enter code path anyhow
deletedEntry = true;
//throw new GrpcError(
// grpc.status.UNKNOWN,
// `hit timeout trying to delete iscsi node DB entry: ${session.target}, ${session.persistent_portal}`
//);
}
} }
} }
} }
@ -817,6 +940,7 @@ class CsiBaseDriver {
let is_block = false; let is_block = false;
let is_formatted; let is_formatted;
let fs_type; let fs_type;
let is_device_mapper = false;
const volume_id = call.request.volume_id; const volume_id = call.request.volume_id;
const volume_path = call.request.volume_path; const volume_path = call.request.volume_path;
@ -853,7 +977,24 @@ class CsiBaseDriver {
} }
if (is_block) { if (is_block) {
await filesystem.rescanDevice(device); let rescan_devices = [];
// detect if is a multipath device
is_device_mapper = await filesystem.isDeviceMapperDevice(device);
if (is_device_mapper) {
// NOTE: want to make sure we scan the dm device *after* all the underlying slaves
rescan_devices = await filesystem.getDeviceMapperDeviceSlaves(device);
}
rescan_devices.push(device);
for (let sdevice of rescan_devices) {
await filesystem.rescanDevice(sdevice);
}
// let things settle
// it appears the dm devices can take a second to figure things out
await sleep(2000);
if (is_formatted && access_type == "mount") { if (is_formatted && access_type == "mount") {
fs_info = await filesystem.getDeviceFilesystemInfo(device); fs_info = await filesystem.getDeviceFilesystemInfo(device);
fs_type = fs_info.type; fs_type = fs_info.type;

View File

@ -48,14 +48,165 @@ class Filesystem {
const device_path = await filesystem.realpath(device); const device_path = await filesystem.realpath(device);
const blockdevices = await filesystem.getAllBlockDevices(); const blockdevices = await filesystem.getAllBlockDevices();
return blockdevices.some((i) => { return blockdevices.some(async (i) => {
if (i.path == device_path) { if ((await filesystem.realpath(i.path)) == device_path) {
return true; return true;
} }
return false; return false;
}); });
} }
/**
* Attempt to discover if the device is a device-mapper device
*
* @param {*} device
*/
async isDeviceMapperDevice(device) {
const filesystem = this;
const isBlock = await filesystem.isBlockDevice(device);
if (!isBlock) {
return false;
}
device = await filesystem.realpath(device);
return device.includes("dm-");
}
async isDeviceMapperSlaveDevice(device) {
const filesystem = this;
device = await filesystem.realpath(device);
}
/**
* Get all device-mapper devices (ie: dm-0, dm-1, dm-N...)
*/
async getAllDeviceMapperDevices() {
const filesystem = this;
let result;
let devices = [];
let args = [
"-c",
'for file in $(ls -la /dev/mapper/* | grep "\\->" | grep -oP "\\-> .+" | grep -oP " .+"); do echo $(F=$(echo $file | grep -oP "[a-z0-9-]+");echo $F":"$(ls "/sys/block/${F}/slaves/");); done;',
];
try {
result = await filesystem.exec("sh", args);
for (const dm of result.stdout.trim().split("\n")) {
if (dm.length < 1) {
continue;
}
devices.push("/dev/" + dm.split(":")[0].trim());
}
return devices;
} catch (err) {
throw err;
}
}
async getAllDeviceMapperSlaveDevices() {
const filesystem = this;
let result;
let args = [
"-c",
'for file in $(ls -la /dev/mapper/* | grep "\\->" | grep -oP "\\-> .+" | grep -oP " .+"); do echo $(F=$(echo $file | grep -oP "[a-z0-9-]+");echo $F":"$(ls "/sys/block/${F}/slaves/");); done;',
];
let slaves = [];
try {
result = await filesystem.exec("sh", args);
for (const dm of result.stdout.trim().split("\n")) {
if (dm.length < 1) {
continue;
}
const realDevices = dm
.split(":")[1]
.split(" ")
.map((value) => {
return "/dev/" + value.trim();
});
slaves.push(...realDevices);
}
return slaves;
} catch (err) {
throw err;
}
}
/**
* Get all slave devices connected to a device-mapper device
*
* @param {*} device
*/
async getDeviceMapperDeviceSlaves(device) {
const filesystem = this;
device = await filesystem.realpath(device);
let device_info = await filesystem.getBlockDevice(device);
const slaves = [];
let result;
let args = [`/sys/block/${device_info.kname}/slaves/`];
try {
result = await filesystem.exec("ls", args);
for (const entry of result.stdout.split("\n")) {
if (entry.trim().length < 1) {
continue;
}
slaves.push("/dev/" + entry.trim());
}
return slaves;
} catch (err) {
throw err;
}
}
async getDeviceMapperDeviceFromSlaves(slaves, matchAll = true) {
const filesystem = this;
let result;
// get mapping of dm devices to real devices
let args = [
"-c",
'for file in $(ls -la /dev/mapper/* | grep "\\->" | grep -oP "\\-> .+" | grep -oP " .+"); do echo $(F=$(echo $file | grep -oP "[a-z0-9-]+");echo $F":"$(ls "/sys/block/${F}/slaves/");); done;',
];
result = await filesystem.exec("sh", args);
for (const dm of result.stdout.trim().split("\n")) {
if (dm.length < 1) {
continue;
}
const dmDevice = "/dev/" + dm.split(":")[0].trim();
const realDevices = dm
.split(":")[1]
.split(" ")
.map((value) => {
return "/dev/" + value.trim();
});
const intersectDevices = slaves.filter((value) =>
realDevices.includes(value)
);
if (matchAll === false && intersectDevices.length > 0) {
return dmDevice;
}
// if all 3 have the same elements we have a winner
if (
intersectDevices.length == realDevices.length &&
realDevices.length == slaves.length
) {
return dmDevice;
}
}
}
/** /**
* create symlink * create symlink
* *
@ -264,12 +415,19 @@ class Filesystem {
); );
} }
let is_device_mapper_device = await filesystem.isDeviceMapperDevice(device);
result = await filesystem.realpath(device); result = await filesystem.realpath(device);
device_name = result.split("/").pop();
// echo 1 > /sys/block/sdb/device/rescan if (is_device_mapper_device) {
const sys_file = `/sys/block/${device_name}/device/rescan`; // multipath -r /dev/dm-0
fs.writeFileSync(sys_file, "1"); result = await filesystem.exec("multipath", ["-r", device]);
} else {
device_name = result.split("/").pop();
// echo 1 > /sys/block/sdb/device/rescan
const sys_file = `/sys/block/${device_name}/device/rescan`;
fs.writeFileSync(sys_file, "1");
}
} }
/** /**

View File

@ -25,7 +25,7 @@ class ISCSI {
if (!options.executor) { if (!options.executor) {
options.executor = { options.executor = {
spawn: cp.spawn spawn: cp.spawn,
}; };
} }
@ -47,7 +47,7 @@ class ISCSI {
const entries = result.stdout.trim().split("\n"); const entries = result.stdout.trim().split("\n");
const interfaces = []; const interfaces = [];
let fields; let fields;
entries.forEach(entry => { entries.forEach((entry) => {
fields = entry.split(" "); fields = entry.split(" ");
interfaces.push({ interfaces.push({
iface_name: fields[0], iface_name: fields[0],
@ -55,7 +55,7 @@ class ISCSI {
hwaddress: getIscsiValue(fields[1].split(",")[1]), hwaddress: getIscsiValue(fields[1].split(",")[1]),
ipaddress: getIscsiValue(fields[1].split(",")[2]), ipaddress: getIscsiValue(fields[1].split(",")[2]),
net_ifacename: getIscsiValue(fields[1].split(",")[3]), net_ifacename: getIscsiValue(fields[1].split(",")[3]),
initiatorname: getIscsiValue(fields[1].split(",")[4]) initiatorname: getIscsiValue(fields[1].split(",")[4]),
}); });
}); });
@ -75,7 +75,7 @@ class ISCSI {
const entries = result.stdout.trim().split("\n"); const entries = result.stdout.trim().split("\n");
const i = {}; const i = {};
let fields, key, value; let fields, key, value;
entries.forEach(entry => { entries.forEach((entry) => {
if (entry.startsWith("#")) return; if (entry.startsWith("#")) return;
fields = entry.split("="); fields = entry.split("=");
key = fields[0].trim(); key = fields[0].trim();
@ -103,7 +103,7 @@ class ISCSI {
"-p", "-p",
portal, portal,
"-o", "-o",
"new" "new",
]); ]);
await iscsi.exec(options.paths.iscsiadm, args); await iscsi.exec(options.paths.iscsiadm, args);
for (let attribute in attributes) { for (let attribute in attributes) {
@ -120,7 +120,7 @@ class ISCSI {
"--name", "--name",
attribute, attribute,
"--value", "--value",
attributes[attribute] attributes[attribute],
]); ]);
await iscsi.exec(options.paths.iscsiadm, args); await iscsi.exec(options.paths.iscsiadm, args);
} }
@ -142,7 +142,7 @@ class ISCSI {
"-p", "-p",
portal, portal,
"-o", "-o",
"delete" "delete",
]); ]);
await iscsi.exec(options.paths.iscsiadm, args); await iscsi.exec(options.paths.iscsiadm, args);
}, },
@ -174,7 +174,7 @@ class ISCSI {
const entries = result.stdout.trim().split("\n"); const entries = result.stdout.trim().split("\n");
const sessions = []; const sessions = [];
let fields; let fields;
entries.forEach(entry => { entries.forEach((entry) => {
fields = entry.split(" "); fields = entry.split(" ");
sessions.push({ sessions.push({
protocol: entry.split(":")[0], protocol: entry.split(":")[0],
@ -182,7 +182,7 @@ class ISCSI {
portal: fields[2].split(",")[0], portal: fields[2].split(",")[0],
target_portal_group_tag: fields[2].split(",")[1], target_portal_group_tag: fields[2].split(",")[1],
iqn: fields[3].split(":")[0], iqn: fields[3].split(":")[0],
target: fields[3].split(":")[1] target: fields[3].split(":")[1],
}); });
}); });
@ -212,6 +212,7 @@ class ISCSI {
return []; return [];
} }
let currentTarget;
let sessionGroups = []; let sessionGroups = [];
let currentSession = []; let currentSession = [];
@ -221,13 +222,21 @@ class ISCSI {
entries.shift(); entries.shift();
entries.shift(); entries.shift();
// this should break up the lines into groups of lines
// where each group is the full details of a single session
// note that the output of the command bundles/groups all sessions
// by target so extra logic is needed to hanle that
// alternatively we could get all sessions using getSessions()
// and then invoke `iscsiadm -m session -P 3 -r <session id>` in a loop
for (let i = 0; i < entries.length; i++) { for (let i = 0; i < entries.length; i++) {
let entry = entries[i]; let entry = entries[i];
if (entry.startsWith("Target:")) { if (entry.startsWith("Target:")) {
currentTarget = entry;
} else if (entry.trim().startsWith("Current Portal:")) {
if (currentSession.length > 0) { if (currentSession.length > 0) {
sessionGroups.push(currentSession); sessionGroups.push(currentSession);
} }
currentSession = [entry]; currentSession = [currentTarget, entry];
} else { } else {
currentSession.push(entry); currentSession.push(entry);
} }
@ -261,11 +270,7 @@ class ISCSI {
.trim() .trim()
.replace(/ /g, "_") .replace(/ /g, "_")
.replace(/\W/g, ""); .replace(/\W/g, "");
let value = line let value = line.split(":").slice(1).join(":").trim();
.split(":")
.slice(1)
.join(":")
.trim();
if (currentSection) { if (currentSection) {
session[currentSection] = session[currentSection] || {}; session[currentSection] = session[currentSection] || {};
@ -282,7 +287,7 @@ class ISCSI {
.split(":") .split(":")
.slice(1) .slice(1)
.join(":") .join(":")
.trim() .trim(),
}; };
while ( while (
sessionLines[j + 1] && sessionLines[j + 1] &&
@ -308,7 +313,7 @@ class ISCSI {
.split(":") .split(":")
.slice(1) .slice(1)
.join(":") .join(":")
.trim() .trim(),
}); });
} }
@ -322,7 +327,7 @@ class ISCSI {
key = key.charAt(0).toLowerCase() + key.slice(1); key = key.charAt(0).toLowerCase() + key.slice(1);
key = key.replace( key = key.replace(
/[A-Z]/g, /[A-Z]/g,
letter => `_${letter.toLowerCase()}` (letter) => `_${letter.toLowerCase()}`
); );
break; break;
} }
@ -367,12 +372,12 @@ class ISCSI {
const entries = result.stdout.trim().split("\n"); const entries = result.stdout.trim().split("\n");
const targets = []; const targets = [];
entries.forEach(entry => { entries.forEach((entry) => {
targets.push({ targets.push({
portal: entry.split(",")[0], portal: entry.split(",")[0],
target_portal_group_tag: entry.split(" ")[0].split(",")[1], target_portal_group_tag: entry.split(" ")[0].split(",")[1],
iqn: entry.split(" ")[1].split(":")[0], iqn: entry.split(" ")[1].split(":")[0],
target: entry.split(" ")[1].split(":")[1] target: entry.split(" ")[1].split(":")[1],
}); });
}); });
@ -432,7 +437,7 @@ class ISCSI {
} }
return true; return true;
} },
}; };
} }
@ -460,15 +465,15 @@ class ISCSI {
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
child.stdout.on("data", function(data) { child.stdout.on("data", function (data) {
stdout = stdout + data; stdout = stdout + data;
}); });
child.stderr.on("data", function(data) { child.stderr.on("data", function (data) {
stderr = stderr + data; stderr = stderr + data;
}); });
child.on("close", function(code) { child.on("close", function (code) {
const result = { code, stdout, stderr }; const result = { code, stdout, stderr };
if (timeout) { if (timeout) {
clearTimeout(timeout); clearTimeout(timeout);