mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-06-08 03:14:09 +03:00
Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e9b7addb1 | ||
|
|
f652c0f40f | ||
|
|
b8cde6cce1 | ||
|
|
aeea59e280 | ||
|
|
74e563ca3f | ||
|
|
5c1e4143e9 | ||
|
|
52d7ca6bf0 | ||
|
|
75eeea21ee | ||
|
|
c03b87dac0 | ||
|
|
259dc95366 | ||
|
|
cfb9fa2100 | ||
|
|
355ccba81a | ||
|
|
443189fb0a | ||
|
|
2db06f0ef8 | ||
|
|
0094bc4fc9 | ||
|
|
b6f22a62cb | ||
|
|
8a0dfc6220 | ||
|
|
2ab4cea5e5 | ||
|
|
c050abbbad | ||
|
|
3f1637fae8 | ||
|
|
c56b9ed03b | ||
|
|
3fd32e331a | ||
|
|
119dfd01bb | ||
|
|
86a1cd700b | ||
|
|
33895d4a0f | ||
|
|
c57eb0ff83 | ||
|
|
e14ab14e54 | ||
|
|
ca259864e2 | ||
|
|
01bb3c06c7 | ||
|
|
66c4961ff8 | ||
|
|
3e16248ed6 | ||
|
|
5e6c1cd986 | ||
|
|
6c2303764e | ||
|
|
f3ad330635 | ||
|
|
6c362d82cb | ||
|
|
661dd190bb | ||
|
|
630ba810f1 | ||
|
|
b4f44befa3 | ||
|
|
5fc8fb1323 | ||
|
|
8e8f98f712 | ||
|
|
c342f5e37e | ||
|
|
56d7cc8a0d | ||
|
|
4c02e496f7 | ||
|
|
3956003dd0 | ||
|
|
5c3fa59181 | ||
|
|
ee7765b10d | ||
|
|
5810ba57c2 | ||
|
|
e573ef2126 | ||
|
|
823fa085ef | ||
|
|
695c1dc5eb | ||
|
|
cdbe848102 | ||
|
|
5c25070556 | ||
|
|
bb08bab263 | ||
|
|
6ad7fe8eeb | ||
|
|
9ea549ed24 | ||
|
|
63b05c0b9f | ||
|
|
d888b21657 | ||
|
|
1e46961d68 | ||
|
|
72756ab8c7 | ||
|
|
543dc8d337 | ||
|
|
e472f0b23b | ||
|
|
c51ca04a43 | ||
|
|
e37f06dc52 | ||
|
|
5c2099ecfe | ||
|
|
885ba17905 | ||
|
|
b9a06e8e74 | ||
|
|
30c8301b11 | ||
|
|
e53f9e553d | ||
|
|
d6ade02fd3 | ||
|
|
3c90d77858 | ||
|
|
478767d0ed | ||
|
|
02e0b19a62 | ||
|
|
6be4456d88 | ||
|
|
9becc26f4b | ||
|
|
c62399eb3e | ||
|
|
55d728c849 | ||
|
|
808fc0971f |
3
.github/workflows/main.yml
vendored
3
.github/workflows/main.yml
vendored
@@ -34,10 +34,11 @@ jobs:
|
||||
make victoria-metrics-pure
|
||||
make victoria-metrics-arm
|
||||
make victoria-metrics-arm64
|
||||
make vmutils
|
||||
GOOS=freebsd go build -mod=vendor ./app/victoria-metrics
|
||||
GOOS=darwin go build -mod=vendor ./app/victoria-metrics
|
||||
- name: Publish coverage
|
||||
uses: codecov/codecov-action@v1.0.0
|
||||
uses: codecov/codecov-action@v1.0.4
|
||||
with:
|
||||
token: ${{secrets.CODECOV_TOKEN}}
|
||||
file: ./coverage.txt
|
||||
|
||||
36
Makefile
36
Makefile
@@ -19,12 +19,36 @@ include deployment/*/Makefile
|
||||
clean:
|
||||
rm -rf bin/*
|
||||
|
||||
publish: publish-victoria-metrics
|
||||
publish: \
|
||||
publish-victoria-metrics \
|
||||
publish-vmbackup \
|
||||
publish-vmrestore
|
||||
|
||||
package: package-victoria-metrics
|
||||
package: \
|
||||
package-victoria-metrics \
|
||||
package-vmbackup \
|
||||
package-vmrestore
|
||||
|
||||
release: victoria-metrics-prod
|
||||
cd bin && tar czf victoria-metrics-$(PKG_TAG).tar.gz victoria-metrics-prod
|
||||
vmutils: \
|
||||
vmbackup \
|
||||
vmrestore
|
||||
|
||||
release: \
|
||||
release-victoria-metrics \
|
||||
release-vmutils
|
||||
|
||||
release-victoria-metrics: victoria-metrics-prod
|
||||
cd bin && tar czf victoria-metrics-$(PKG_TAG).tar.gz victoria-metrics-prod && \
|
||||
sha256sum victoria-metrics-$(PKG_TAG).tar.gz > victoria-metrics-$(PKG_TAG)_checksums.txt
|
||||
|
||||
release-vmutils: \
|
||||
vmbackup-prod \
|
||||
vmrestore-prod
|
||||
cd bin && tar czf vmutils-$(PKG_TAG).tar.gz vmbackup-prod vmrestore-prod && \
|
||||
sha256sum vmutils-$(PKG_TAG).tar.gz > vmutils-$(PKG_TAG)_checksums.txt
|
||||
|
||||
pprof-cpu:
|
||||
go tool pprof -trim_path=github.com/VictoriaMetrics/VictoriaMetrics@ $(PPROF_FILE)
|
||||
|
||||
fmt:
|
||||
GO111MODULE=on gofmt -l -w -s ./lib
|
||||
@@ -39,13 +63,15 @@ lint: install-golint
|
||||
golint app/...
|
||||
|
||||
install-golint:
|
||||
which golint || GO111MODULE=off go get -u github.com/golang/lint/golint
|
||||
which golint || GO111MODULE=off go get -u golang.org/x/lint/golint
|
||||
|
||||
errcheck: install-errcheck
|
||||
errcheck -exclude=errcheck_excludes.txt ./lib/...
|
||||
errcheck -exclude=errcheck_excludes.txt ./app/vminsert/...
|
||||
errcheck -exclude=errcheck_excludes.txt ./app/vmselect/...
|
||||
errcheck -exclude=errcheck_excludes.txt ./app/vmstorage/...
|
||||
errcheck -exclude=errcheck_excludes.txt ./app/vmbackup/...
|
||||
errcheck -exclude=errcheck_excludes.txt ./app/vmrestore/...
|
||||
|
||||
install-errcheck:
|
||||
which errcheck || GO111MODULE=off go get -u github.com/kisielk/errcheck
|
||||
|
||||
20
README.md
20
README.md
@@ -26,6 +26,7 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
and [selects](https://medium.com/@valyala/when-size-matters-benchmarking-victoriametrics-vs-timescale-and-influxdb-6035811952d4).
|
||||
[Outperforms InfluxDB and TimescaleDB by up to 20x](https://medium.com/@valyala/measuring-vertical-scalability-for-time-series-databases-in-google-cloud-92550d78d8ae).
|
||||
* [Uses 10x less RAM than InfluxDB](https://medium.com/@valyala/insert-benchmarks-with-inch-influxdb-vs-victoriametrics-e31a41ae2893) when working with millions of unique time series (aka high cardinality).
|
||||
* Optimized for time series with high churn rate. Think about [prometheus-operator](https://github.com/coreos/prometheus-operator) metrics from frequent deployments in Kubernetes.
|
||||
* High data compression, so [up to 70x more data points](https://medium.com/@valyala/when-size-matters-benchmarking-victoriametrics-vs-timescale-and-influxdb-6035811952d4)
|
||||
may be crammed into limited storage comparing to TimescaleDB.
|
||||
* Optimized for storage with high-latency IO and low IOPS (HDD and network storage in AWS, Google Cloud, Microsoft Azure, etc). See [graphs from these benchmarks](https://medium.com/@valyala/high-cardinality-tsdb-benchmarks-victoriametrics-vs-timescaledb-vs-influxdb-13e6ee64dd6b).
|
||||
@@ -33,11 +34,13 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
See [vertical scalability benchmarks](https://medium.com/@valyala/measuring-vertical-scalability-for-time-series-databases-in-google-cloud-92550d78d8ae)
|
||||
and [comparing Thanos to VictoriaMetrics cluster](https://medium.com/@valyala/comparing-thanos-to-victoriametrics-cluster-b193bea1683).
|
||||
* Easy operation:
|
||||
* VictoriaMetrics consists of a single executable without external dependencies.
|
||||
* VictoriaMetrics consists of a single [small executable](https://medium.com/@valyala/stripping-dependency-bloat-in-victoriametrics-docker-image-983fb5912b0d) without external dependencies.
|
||||
* All the configuration is done via explicit command-line flags with reasonable defaults.
|
||||
* All the data is stored in a single directory pointed by `-storageDataPath` flag.
|
||||
* Easy backups from [instant snapshots](https://medium.com/@valyala/how-victoriametrics-makes-instant-snapshots-for-multi-terabyte-time-series-data-e1f3fb0e0282).
|
||||
* Storage is protected from corruption on unclean shutdown (i.e. hardware reset or `kill -9`) thanks to [the storage architecture](https://medium.com/@valyala/how-victoriametrics-makes-instant-snapshots-for-multi-terabyte-time-series-data-e1f3fb0e0282).
|
||||
* Easy and fast backups from [instant snapshots](https://medium.com/@valyala/how-victoriametrics-makes-instant-snapshots-for-multi-terabyte-time-series-data-e1f3fb0e0282)
|
||||
to S3 or GCS with [vmbackup](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmbackup/README.md) / [vmrestore](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmrestore/README.md).
|
||||
See [this article](https://medium.com/@valyala/speeding-up-backups-for-big-time-series-databases-533c1a927883) for more details.
|
||||
* Storage is protected from corruption on unclean shutdown (i.e. OOM, hardware reset or `kill -9`) thanks to [the storage architecture](https://medium.com/@valyala/how-victoriametrics-makes-instant-snapshots-for-multi-terabyte-time-series-data-e1f3fb0e0282).
|
||||
* Supports metrics' ingestion and [backfilling](#backfilling) via the following protocols:
|
||||
* [Prometheus remote write API](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write)
|
||||
* [InfluxDB line protocol](https://docs.influxdata.com/influxdb/v1.7/write_protocols/line_protocol_tutorial/)
|
||||
@@ -45,7 +48,7 @@ Cluster version is available [here](https://github.com/VictoriaMetrics/VictoriaM
|
||||
if `-graphiteListenAddr` is set.
|
||||
* [OpenTSDB put message](http://opentsdb.net/docs/build/html/api_telnet/put.html) if `-opentsdbListenAddr` is set.
|
||||
* [HTTP OpenTSDB /api/put requests](http://opentsdb.net/docs/build/html/api_http/put.html) if `-opentsdbHTTPListenAddr` is set.
|
||||
* Ideally works with big amounts of time series data from Kubernetes, IoT sensors, connected cars, industrial telemetry and various Enterprise workloads.
|
||||
* Ideally works with big amounts of time series data from Kubernetes, IoT sensors, connected cars, industrial telemetry, financial data and various Enterprise workloads.
|
||||
* Has open source [cluster version](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/cluster).
|
||||
|
||||
|
||||
@@ -461,8 +464,8 @@ The page will return the following JSON response:
|
||||
```
|
||||
|
||||
Snapshots are created under `<-storageDataPath>/snapshots` directory, where `<-storageDataPath>`
|
||||
is the command-line flag value. Snapshots can be archived to backup storage via `cp -L`, `rsync -L`, `scp -r`
|
||||
or any similar tool that follows symlinks during copying.
|
||||
is the command-line flag value. Snapshots can be archived to backup storage at any time
|
||||
with [vmbackup](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmbackup/README.md).
|
||||
|
||||
The `http://<victoriametrics-addr>:8428/snapshot/list` page contains the list of available snapshots.
|
||||
|
||||
@@ -474,7 +477,8 @@ Navigate to `http://<victoriametrics-addr>:8428/snapshot/delete_all` in order to
|
||||
Steps for restoring from a snapshot:
|
||||
1. Stop VictoriaMetrics with `kill -INT`.
|
||||
2. Remove the entire contents of the directory pointed by `-storageDataPath` command-line flag.
|
||||
3. Copy snapshot contents to the directory pointed by `-storageDataPath`.
|
||||
3. Restore snapshot contents from backup with [vmrestore](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmrestore/README.md)
|
||||
to the directory pointed by `-storageDataPath`.
|
||||
4. Start VictoriaMetrics.
|
||||
|
||||
|
||||
@@ -673,6 +677,7 @@ The most interesting metrics are:
|
||||
|
||||
* `vm_cache_entries{type="storage/hour_metric_ids"}` - the number of time series with new data points during the last hour
|
||||
aka active time series.
|
||||
* `rate(vm_new_timeseries_created_total[5m])` - time series churn rate.
|
||||
* `vm_rows{type="indexdb"}` - the number of rows in inverted index. High value for this number usually mean high churn rate for time series.
|
||||
* Sum of `vm_rows{type="storage/big"}` and `vm_rows{type="storage/small"}` - total number of `(timestamp, value)` data points
|
||||
in the database.
|
||||
@@ -738,6 +743,7 @@ The collected profiles may be analyzed with [go tool pprof](https://github.com/g
|
||||
See [these docs](https://github.com/netdata/netdata#integrations).
|
||||
* [go-graphite/carbonapi](https://github.com/go-graphite/carbonapi) can use VictoriaMetrics as time series backend.
|
||||
See [this example](/blob/master/cmd/carbonapi/carbonapi.example.prometheus.yaml).
|
||||
* [Ansible role for installing VictoriaMetrics](https://github.com/dreamteam-gg/ansible-victoriametrics-role).
|
||||
|
||||
|
||||
## Roadmap
|
||||
|
||||
@@ -32,6 +32,12 @@ victoria-metrics-arm64:
|
||||
victoria-metrics-arm64-prod:
|
||||
APP_NAME=victoria-metrics APP_SUFFIX='-arm64' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=arm64' $(MAKE) app-via-docker
|
||||
|
||||
victoria-metrics-ppc64le:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=ppc64le GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/victoria-metrics-ppc64le ./app/victoria-metrics
|
||||
|
||||
victoria-metrics-ppc64le-prod:
|
||||
APP_NAME=victoria-metrics APP_SUFFIX='-ppc64le' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=ppc64le' $(MAKE) app-via-docker
|
||||
|
||||
victoria-metrics-386:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=386 GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/victoria-metrics-386 ./app/victoria-metrics
|
||||
|
||||
|
||||
@@ -186,7 +186,6 @@ func tearDown() {
|
||||
vmstorage.Stop()
|
||||
vmselect.Stop()
|
||||
fs.MustRemoveAll(storagePath)
|
||||
fs.MustStopDirRemover()
|
||||
}
|
||||
|
||||
func TestWriteRead(t *testing.T) {
|
||||
|
||||
@@ -41,7 +41,7 @@ func PopulateTimeTpl(b []byte, tGlobal time.Time) []byte {
|
||||
case `TIME_NS`:
|
||||
return []byte(fmt.Sprintf("%d", t.UnixNano()))
|
||||
default:
|
||||
log.Fatalf("unkown time pattern %s in %s", parts[0], repl)
|
||||
log.Fatalf("unknown time pattern %s in %s", parts[0], repl)
|
||||
}
|
||||
return repl
|
||||
})
|
||||
|
||||
37
app/vmbackup/Makefile
Normal file
37
app/vmbackup/Makefile
Normal file
@@ -0,0 +1,37 @@
|
||||
# All these commands must run from repository root.
|
||||
|
||||
vmbackup:
|
||||
APP_NAME=vmbackup $(MAKE) app-local
|
||||
|
||||
vmbackup-prod:
|
||||
APP_NAME=vmbackup $(MAKE) app-via-docker
|
||||
|
||||
package-vmbackup:
|
||||
APP_NAME=vmbackup $(MAKE) package-via-docker
|
||||
|
||||
publish-vmbackup:
|
||||
APP_NAME=vmbackup $(MAKE) publish-via-docker
|
||||
|
||||
vmbackup-arm:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmbackup-arm ./app/vmbackup
|
||||
|
||||
vmbackup-arm-prod:
|
||||
APP_NAME=vmbackup APP_SUFFIX='-arm' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=arm' $(MAKE) app-via-docker
|
||||
|
||||
vmbackup-arm64:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmbackup-arm64 ./app/vmbackup
|
||||
|
||||
vmbackup-arm64-prod:
|
||||
APP_NAME=vmbackup APP_SUFFIX='-arm64' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=arm64' $(MAKE) app-via-docker
|
||||
|
||||
vmbackup-386:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=386 GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmbackup-386 ./app/vmbackup
|
||||
|
||||
vmbackup-386-prod:
|
||||
APP_NAME=vmbackup APP_SUFFIX='-386' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=386' $(MAKE) app-via-docker
|
||||
|
||||
vmbackup-pure:
|
||||
APP_NAME=vmbackup $(MAKE) app-local-pure
|
||||
|
||||
vmbackup-pure-prod:
|
||||
APP_NAME=vmbackup APP_SUFFIX='-pure' DOCKER_OPTS='--env CGO_ENABLED=0' $(MAKE) app-via-docker
|
||||
176
app/vmbackup/README.md
Normal file
176
app/vmbackup/README.md
Normal file
@@ -0,0 +1,176 @@
|
||||
## vmbackup
|
||||
|
||||
`vmbackup` creates VictoriaMetrics data backups from [instant snapshots](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#how-to-work-with-snapshots).
|
||||
|
||||
Supported storage systems for backups:
|
||||
|
||||
* [GCS](https://cloud.google.com/storage/). Example: `gcs://<bucket>/<path/to/backup>`
|
||||
* [S3](https://aws.amazon.com/s3/). Example: `s3://<bucket>/<path/to/backup>`
|
||||
* Local filesystem. Example: `fs://</absolute/path/to/backup>`
|
||||
|
||||
Incremental backups and full backups are supported. Incremental backups are created automatically if the destination path already contains data from the previous backup.
|
||||
Full backups can be sped up with `-origin` pointing to already existing backup on the same remote storage. In this case `vmbackup` makes server-side copy for the shared
|
||||
data between the existing backup and new backup. This saves time and costs on data transfer.
|
||||
|
||||
Backup process can be interrupted at any time. It is automatically resumed from the interruption point when restarting `vmbackup` with the same args.
|
||||
|
||||
Backed up data can be restored with [vmrestore](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmrestore/README.md).
|
||||
|
||||
See [this article](https://medium.com/@valyala/speeding-up-backups-for-big-time-series-databases-533c1a927883) for more details.
|
||||
|
||||
|
||||
### Use cases
|
||||
|
||||
#### Regular backups
|
||||
|
||||
Regular backup can be performed with the following command:
|
||||
|
||||
```
|
||||
vmbackup -storageDataPath=</path/to/victoria-metrics-data> -snapshotName=<local-snapshot> -dst=gcs://<bucket>/<path/to/new/backup>
|
||||
```
|
||||
|
||||
* `</path/to/victoria-metrics-data>` - path to VictoriaMetrics data pointed by `-storageDataPath` command-line flag in single-node VictoriaMetrics or in cluster `vmstorage`.
|
||||
There is no need to stop VictoriaMetrics for creating backups, since they are performed from immutable [instant snapshots](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#how-to-work-with-snapshots).
|
||||
* `<local-snapshot>` is the snapshot to backup. See [how to create instant snapshots](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#how-to-work-with-snapshots).
|
||||
* `<bucket>` is already existing name for [GCS bucket](https://cloud.google.com/storage/docs/creating-buckets).
|
||||
* `<path/to/new/backup>` is the destination path where new backup will be placed.
|
||||
|
||||
|
||||
#### Regular backups with server-side copy from existing backup
|
||||
|
||||
If the destination GCS bucket already contains the previous backup at `-origin` path, then new backup can be sped up
|
||||
with the following command:
|
||||
|
||||
```
|
||||
vmbackup -storageDataPath=</path/to/victoria-metrics-data> -snapshotName=<local-snapshot> -dst=gcs://<bucket>/<path/to/new/backup> -origin=gcs://<bucket>/<path/to/existing/backup>
|
||||
```
|
||||
|
||||
This saves time and network bandwidth costs by performing server-side copy for the shared data from the `-origin` to `-dst`.
|
||||
|
||||
|
||||
#### Incremental backups
|
||||
|
||||
Incremental backups are performed if `-dst` points to already existing backup. In this case only new data is uploaded to remote storage.
|
||||
This saves time and network bandwidth costs when working with big backups:
|
||||
|
||||
```
|
||||
vmbackup -storageDataPath=</path/to/victoria-metrics-data> -snapshotName=<local-snapshot> -dst=gcs://<bucket>/<path/to/existing/backup>
|
||||
```
|
||||
|
||||
|
||||
#### Smart backups
|
||||
|
||||
Smart backups mean storing full daily backups into `YYYYMMDD` folders and creating incremental hourly backup into `latest` folder:
|
||||
|
||||
* Run the following command every hour:
|
||||
|
||||
```
|
||||
vmbackup -snapshotName=<latest-snapshot> -dst=gcs://<bucket>/latest
|
||||
```
|
||||
|
||||
Where `<latest-snapshot>` is the latest [snapshot](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#how-to-work-with-snapshots).
|
||||
The command will upload only changed data to `gcs://<bucket>/latest`.
|
||||
|
||||
* Run the following command once a day:
|
||||
|
||||
```
|
||||
vmbackup -snapshotName=<daily-snapshot> -dst=gcs://<bucket>/<YYYYMMDD> -origin=gcs://<bucket>/latest
|
||||
```
|
||||
|
||||
Where `<daily-snapshot>` is the snapshot for the last day `<YYYYMMDD>`.
|
||||
|
||||
|
||||
This apporach saves network bandwidth costs on hourly backups (since they are incremental) and allows recovering data from either the last hour (`latest` backup)
|
||||
or from any day (`YYYYMMDD` backups). Note that hourly backup shouldn't run when creating daily backup.
|
||||
|
||||
Do not forget removing old snapshots and backups when they are no longer needed for saving storage costs.
|
||||
|
||||
|
||||
### How does it work?
|
||||
|
||||
The backup algorithm is the following:
|
||||
|
||||
1. Collect information about files in the `-snapshotName`, in the `-dst` and in the `-origin`.
|
||||
2. Determine files in `-dst`, which are missing in `-snapshotName`, and delete them. These are usually small files, which are already merged into bigger files in the snapshot.
|
||||
3. Determine files from `-snapshotName`, which are missing in `-dst`. These are usually small new files and bigger merged files.
|
||||
4. Determine files from step 3, which exist in the `-origin`, and perform server-side copy of these files from `-origin` to `-dst`.
|
||||
This are usually the biggest and the oldest files, which are shared between backups.
|
||||
5. Upload the remaining files from setp 3 from `-snapshotName` to `-dst`.
|
||||
|
||||
The algorithm splits source files into 100MB chunks in the backup. Each chunk is stored as a separate file in the backup.
|
||||
Such splitting minimizes the amounts of data to re-transfer after temporary errors.
|
||||
|
||||
`vmbackup` relies on [instant snapshot](https://medium.com/@valyala/how-victoriametrics-makes-instant-snapshots-for-multi-terabyte-time-series-data-e1f3fb0e0282) properties:
|
||||
|
||||
- All the files in the snapshot are immutable.
|
||||
- Old files are periodically merged into new files.
|
||||
- Smaller files have higher probability to be merged.
|
||||
- Consecutive snapshots share many identical files.
|
||||
|
||||
These properties allow performing fast and cheap incremental backups and server-side copying from `-origin` paths.
|
||||
See [this article](https://medium.com/@valyala/speeding-up-backups-for-big-time-series-databases-533c1a927883) for more details.
|
||||
`vmbackup` can work improperly or slowly when these properties are violated.
|
||||
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
* If the backup is slow, then try setting higher value for `-concurrency` flag. This will increase the number of concurrent workers that upload data to backup storage.
|
||||
* If `vmbackup` eats all the network bandwidth, then set `-maxBytesPerSecond` to the desired value.
|
||||
* If `vmbackup` has been interrupted due to temporary error, then just restart it with the same args. It will resume the backup process.
|
||||
|
||||
|
||||
### Advanced usage
|
||||
|
||||
Run `vmbackup -help` in order to see all the available options:
|
||||
|
||||
```
|
||||
-concurrency int
|
||||
The number of concurrent workers. Higher concurrency may reduce backup duration (default 10)
|
||||
-configFilePath string
|
||||
Path to file with S3 configs. Configs are loaded from default location if not set.
|
||||
See https://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html
|
||||
-credsFilePath string
|
||||
Path to file with GCS or S3 credentials. Credentials are loaded from default locations if not set.
|
||||
See https://cloud.google.com/iam/docs/creating-managing-service-account-keys and https://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html
|
||||
-dst string
|
||||
Where to put the backup on the remote storage. Example: gcs://bucket/path/to/backup/dir, s3://bucket/path/to/backup/dir or fs:///path/to/local/backup/dir
|
||||
-dst can point to the previous backup. In this case incremental backup is performed, i.e. only changed data is uploaded
|
||||
-loggerLevel string
|
||||
Minimum level of errors to log. Possible values: INFO, ERROR, FATAL, PANIC (default "INFO")
|
||||
-maxBytesPerSecond int
|
||||
The maximum upload speed. There is no limit if it is set to 0
|
||||
-memory.allowedPercent float
|
||||
Allowed percent of system memory VictoriaMetrics caches may occupy (default 60)
|
||||
-origin string
|
||||
Optional origin directory on the remote storage with old backup for server-side copying when performing full backup. This speeds up full backups
|
||||
-snapshotName string
|
||||
Name for the snapshot to backup. See https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#how-to-work-with-snapshots
|
||||
-storageDataPath string
|
||||
Path to VictoriaMetrics data. Must match -storageDataPath from VictoriaMetrics or vmstorage (default "victoria-metrics-data")
|
||||
-version
|
||||
Show VictoriaMetrics version
|
||||
```
|
||||
|
||||
|
||||
### How to build from sources
|
||||
|
||||
It is recommended using [binary releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases) - see `vmutils-*` archives there.
|
||||
|
||||
|
||||
#### Development build
|
||||
|
||||
1. [Install Go](https://golang.org/doc/install). The minimum supported version is Go 1.12.
|
||||
2. Run `make vmbackup` from the root folder of the repository.
|
||||
It builds `vmbackup` binary and puts it into the `bin` folder.
|
||||
|
||||
#### Production build
|
||||
|
||||
1. [Install docker](https://docs.docker.com/install/).
|
||||
2. Run `make vmbackup-prod` from the root folder of the repository.
|
||||
It builds `vmbackup-prod` binary and puts it into the `bin` folder.
|
||||
|
||||
#### Building docker images
|
||||
|
||||
Run `make package-vmbackup`. It builds `victoriametrics/vmbackup:<PKG_TAG>` docker image locally.
|
||||
`<PKG_TAG>` is auto-generated image tag, which depends on source code in the repository.
|
||||
The `<PKG_TAG>` may be manually set via `PKG_TAG=foobar make package-vmbackup`.
|
||||
5
app/vmbackup/deployment/Dockerfile
Normal file
5
app/vmbackup/deployment/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM scratch
|
||||
COPY --from=local/certs:1.0.2 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY bin/vmbackup-prod .
|
||||
EXPOSE 8428
|
||||
ENTRYPOINT ["/vmbackup-prod"]
|
||||
114
app/vmbackup/main.go
Normal file
114
app/vmbackup/main.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/actions"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fslocal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
storageDataPath = flag.String("storageDataPath", "victoria-metrics-data", "Path to VictoriaMetrics data. Must match -storageDataPath from VictoriaMetrics or vmstorage")
|
||||
snapshotName = flag.String("snapshotName", "", "Name for the snapshot to backup. See https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md#how-to-work-with-snapshots")
|
||||
dst = flag.String("dst", "", "Where to put the backup on the remote storage. "+
|
||||
"Example: gcs://bucket/path/to/backup/dir, s3://bucket/path/to/backup/dir or fs:///path/to/local/backup/dir\n"+
|
||||
"-dst can point to the previous backup. In this case incremental backup is performed, i.e. only changed data is uploaded")
|
||||
origin = flag.String("origin", "", "Optional origin directory on the remote storage with old backup for server-side copying when performing full backup. This speeds up full backups")
|
||||
concurrency = flag.Int("concurrency", 10, "The number of concurrent workers. Higher concurrency may reduce backup duration")
|
||||
maxBytesPerSecond = flag.Int("maxBytesPerSecond", 0, "The maximum upload speed. There is no limit if it is set to 0")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
buildinfo.Init()
|
||||
|
||||
srcFS, err := newSrcFS()
|
||||
if err != nil {
|
||||
logger.Fatalf("%s", err)
|
||||
}
|
||||
dstFS, err := newDstFS()
|
||||
if err != nil {
|
||||
logger.Fatalf("%s", err)
|
||||
}
|
||||
originFS, err := newOriginFS()
|
||||
if err != nil {
|
||||
logger.Fatalf("%s", err)
|
||||
}
|
||||
a := &actions.Backup{
|
||||
Concurrency: *concurrency,
|
||||
Src: srcFS,
|
||||
Dst: dstFS,
|
||||
Origin: originFS,
|
||||
}
|
||||
if err := a.Run(); err != nil {
|
||||
logger.Fatalf("cannot create backup: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
const s = `
|
||||
vmbackup performs backups for VictoriaMetrics data from instant snapshots to gcs, s3
|
||||
or local filesystem. Backed up data can be restored with vmrestore.
|
||||
|
||||
See the docs at https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmbackup/README.md .
|
||||
`
|
||||
|
||||
f := flag.CommandLine.Output()
|
||||
fmt.Fprintf(f, "%s\n", s)
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
func newSrcFS() (*fslocal.FS, error) {
|
||||
if len(*snapshotName) == 0 {
|
||||
return nil, fmt.Errorf("`-snapshotName` cannot be empty")
|
||||
}
|
||||
snapshotPath := *storageDataPath + "/snapshots/" + *snapshotName
|
||||
|
||||
// Verify the snapshot exists.
|
||||
f, err := os.Open(snapshotPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open snapshot at %q: %s", snapshotPath, err)
|
||||
}
|
||||
fi, err := f.Stat()
|
||||
_ = f.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot stat %q: %s", snapshotPath, err)
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return nil, fmt.Errorf("snapshot %q must be a directory", snapshotPath)
|
||||
}
|
||||
|
||||
fs := &fslocal.FS{
|
||||
Dir: snapshotPath,
|
||||
MaxBytesPerSecond: *maxBytesPerSecond,
|
||||
}
|
||||
if err := fs.Init(); err != nil {
|
||||
return nil, fmt.Errorf("cannot initialize fs: %s", err)
|
||||
}
|
||||
return fs, nil
|
||||
}
|
||||
|
||||
func newDstFS() (common.RemoteFS, error) {
|
||||
fs, err := actions.NewRemoteFS(*dst)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse `-dst`=%q: %s", *dst, err)
|
||||
}
|
||||
return fs, nil
|
||||
}
|
||||
|
||||
func newOriginFS() (common.RemoteFS, error) {
|
||||
if len(*origin) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
fs, err := actions.NewRemoteFS(*origin)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse `-origin`=%q: %s", *origin, err)
|
||||
}
|
||||
return fs, nil
|
||||
}
|
||||
37
app/vmrestore/Makefile
Normal file
37
app/vmrestore/Makefile
Normal file
@@ -0,0 +1,37 @@
|
||||
# All these commands must run from repository root.
|
||||
|
||||
vmrestore:
|
||||
APP_NAME=vmrestore $(MAKE) app-local
|
||||
|
||||
vmrestore-prod:
|
||||
APP_NAME=vmrestore $(MAKE) app-via-docker
|
||||
|
||||
package-vmrestore:
|
||||
APP_NAME=vmrestore $(MAKE) package-via-docker
|
||||
|
||||
publish-vmrestore:
|
||||
APP_NAME=vmrestore $(MAKE) publish-via-docker
|
||||
|
||||
vmrestore-arm:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmrestore-arm ./app/vmrestore
|
||||
|
||||
vmrestore-arm-prod:
|
||||
APP_NAME=vmrestore APP_SUFFIX='-arm' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=arm' $(MAKE) app-via-docker
|
||||
|
||||
vmrestore-arm64:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmrestore-arm64 ./app/vmrestore
|
||||
|
||||
vmrestore-arm64-prod:
|
||||
APP_NAME=vmrestore APP_SUFFIX='-arm64' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=arm64' $(MAKE) app-via-docker
|
||||
|
||||
vmrestore-386:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=386 GO111MODULE=on go build -mod=vendor -ldflags "$(GO_BUILDINFO)" -o bin/vmrestore-386 ./app/vmrestore
|
||||
|
||||
vmrestore-386-prod:
|
||||
APP_NAME=vmrestore APP_SUFFIX='-386' DOCKER_OPTS='--env CGO_ENABLED=0 --env GOARCH=386' $(MAKE) app-via-docker
|
||||
|
||||
vmrestore-pure:
|
||||
APP_NAME=vmrestore $(MAKE) app-local-pure
|
||||
|
||||
vmrestore-pure-prod:
|
||||
APP_NAME=vmrestore APP_SUFFIX='-pure' DOCKER_OPTS='--env CGO_ENABLED=0' $(MAKE) app-via-docker
|
||||
82
app/vmrestore/README.md
Normal file
82
app/vmrestore/README.md
Normal file
@@ -0,0 +1,82 @@
|
||||
## vmrestore
|
||||
|
||||
`vmrestore` restores data from backups created by [vmbackup](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmbackup/README.md).
|
||||
VictoriaMetrics `v1.29.0` and newer versions must be used for working with the restored data.
|
||||
|
||||
Restore process can be interrupted at any time. It is automatically resumed from the inerruption point
|
||||
when restarting `vmrestore` with the same args.
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
VictoriaMetrics must be stopped during the restore process.
|
||||
|
||||
```
|
||||
vmrestore -src=gcs://<bucket>/<path/to/backup> -storageDataPath=<local/path/to/restore>
|
||||
|
||||
```
|
||||
|
||||
* `<bucket>` is [GCS bucket](https://cloud.google.com/storage/docs/creating-buckets) name.
|
||||
* `<path/to/backup>` is the path to backup made with [vmbackup](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmbackup/README.md) on GCS bucket.
|
||||
* `<local/path/to/restore>` is the path to folder where data will be restored. This folder must be passed
|
||||
to VictoriaMetrics in `-storageDataPath` command-line flag after the restore process is complete.
|
||||
|
||||
The original `-storageDataPath` directory may contain old files. They will be susbstituted by the files from backup.
|
||||
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
* If `vmrestore` eats all the network bandwidth, then set `-maxBytesPerSecond` to the desired value.
|
||||
* If `vmrestore` has been interrupted due to temporary error, then just restart it with the same args. It will resume the restore process.
|
||||
|
||||
|
||||
### Advanced usage
|
||||
|
||||
Run `vmrestore -help` in order to see all the available options:
|
||||
|
||||
```
|
||||
-concurrency int
|
||||
The number of concurrent workers. Higher concurrency may reduce restore duration (default 10)
|
||||
-configFilePath string
|
||||
Path to file with S3 configs. Configs are loaded from default location if not set.
|
||||
See https://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html
|
||||
-credsFilePath string
|
||||
Path to file with GCS or S3 credentials. Credentials are loaded from default locations if not set.
|
||||
See https://cloud.google.com/iam/docs/creating-managing-service-account-keys and https://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html
|
||||
-loggerLevel string
|
||||
Minimum level of errors to log. Possible values: INFO, ERROR, FATAL, PANIC (default "INFO")
|
||||
-maxBytesPerSecond int
|
||||
The maximum download speed. There is no limit if it is set to 0
|
||||
-memory.allowedPercent float
|
||||
Allowed percent of system memory VictoriaMetrics caches may occupy (default 60)
|
||||
-src string
|
||||
Source path with backup on the remote storage. Example: gcs://bucket/path/to/backup/dir, s3://bucket/path/to/backup/dir or fs:///path/to/local/backup/dir
|
||||
-storageDataPath string
|
||||
Destination path where backup must be restored. VictoriaMetrics must be stopped when restoring from backup. -storageDataPath dir can be non-empty. In this case only missing data is downloaded from backup (default "victoria-metrics-data")
|
||||
-version
|
||||
Show VictoriaMetrics version
|
||||
```
|
||||
|
||||
|
||||
### How to build from sources
|
||||
|
||||
It is recommended using [binary releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases) - see `vmutils-*` archives there.
|
||||
|
||||
|
||||
#### Development build
|
||||
|
||||
1. [Install Go](https://golang.org/doc/install). The minimum supported version is Go 1.12.
|
||||
2. Run `make vmrestore` from the root folder of the repository.
|
||||
It builds `vmrestore` binary and puts it into the `bin` folder.
|
||||
|
||||
#### Production build
|
||||
|
||||
1. [Install docker](https://docs.docker.com/install/).
|
||||
2. Run `make vmrestore-prod` from the root folder of the repository.
|
||||
It builds `vmrestore-prod` binary and puts it into the `bin` folder.
|
||||
|
||||
#### Building docker images
|
||||
|
||||
Run `make package-vmrestore`. It builds `victoriametrics/vmrestore:<PKG_TAG>` docker image locally.
|
||||
`<PKG_TAG>` is auto-generated image tag, which depends on source code in the repository.
|
||||
The `<PKG_TAG>` may be manually set via `PKG_TAG=foobar make package-vmrestore`.
|
||||
5
app/vmrestore/deployment/Dockerfile
Normal file
5
app/vmrestore/deployment/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM scratch
|
||||
COPY --from=local/certs:1.0.2 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY bin/vmrestore-prod .
|
||||
EXPOSE 8428
|
||||
ENTRYPOINT ["/vmrestore-prod"]
|
||||
78
app/vmrestore/main.go
Normal file
78
app/vmrestore/main.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/actions"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fslocal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
src = flag.String("src", "", "Source path with backup on the remote storage. "+
|
||||
"Example: gcs://bucket/path/to/backup/dir, s3://bucket/path/to/backup/dir or fs:///path/to/local/backup/dir")
|
||||
storageDataPath = flag.String("storageDataPath", "victoria-metrics-data", "Destination path where backup must be restored. "+
|
||||
"VictoriaMetrics must be stopped when restoring from backup. -storageDataPath dir can be non-empty. In this case only missing data is downloaded from backup")
|
||||
concurrency = flag.Int("concurrency", 10, "The number of concurrent workers. Higher concurrency may reduce restore duration")
|
||||
maxBytesPerSecond = flag.Int("maxBytesPerSecond", 0, "The maximum download speed. There is no limit if it is set to 0")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
buildinfo.Init()
|
||||
|
||||
srcFS, err := newSrcFS()
|
||||
if err != nil {
|
||||
logger.Fatalf("%s", err)
|
||||
}
|
||||
dstFS, err := newDstFS()
|
||||
if err != nil {
|
||||
logger.Fatalf("%s", err)
|
||||
}
|
||||
a := &actions.Restore{
|
||||
Concurrency: *concurrency,
|
||||
Src: srcFS,
|
||||
Dst: dstFS,
|
||||
}
|
||||
if err := a.Run(); err != nil {
|
||||
logger.Fatalf("cannot restore from backup: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
const s = `
|
||||
vmrestore restores VictoriaMetrics data from backups made by vmbackup.
|
||||
|
||||
See the docs at https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmrestore/README.md .
|
||||
`
|
||||
|
||||
f := flag.CommandLine.Output()
|
||||
fmt.Fprintf(f, "%s\n", s)
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
func newDstFS() (*fslocal.FS, error) {
|
||||
if len(*storageDataPath) == 0 {
|
||||
return nil, fmt.Errorf("`-storageDataPath` cannot be empty")
|
||||
}
|
||||
fs := &fslocal.FS{
|
||||
Dir: *storageDataPath,
|
||||
MaxBytesPerSecond: *maxBytesPerSecond,
|
||||
}
|
||||
if err := fs.Init(); err != nil {
|
||||
return nil, fmt.Errorf("cannot initialize local fs: %s", err)
|
||||
}
|
||||
return fs, nil
|
||||
}
|
||||
|
||||
func newSrcFS() (common.RemoteFS, error) {
|
||||
fs, err := actions.NewRemoteFS(*src)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse `-src`=%q: %s", *src, err)
|
||||
}
|
||||
return fs, nil
|
||||
}
|
||||
@@ -26,7 +26,7 @@ var (
|
||||
maxQueryDuration = flag.Duration("search.maxQueryDuration", time.Second*30, "The maximum time for search query execution")
|
||||
maxQueryLen = flag.Int("search.maxQueryLen", 16*1024, "The maximum search query length in bytes")
|
||||
maxLookback = flag.Duration("search.maxLookback", 0, "Synonim to `-search.lookback-delta` from Prometheus. "+
|
||||
"The value is dynamically detected from interval between time series datapoints if not set. It can be overriden on per-query basis via `max_lookback` arg")
|
||||
"The value is dynamically detected from interval between time series datapoints if not set. It can be overridden on per-query basis via `max_lookback` arg")
|
||||
)
|
||||
|
||||
// Default step used if not set.
|
||||
@@ -481,7 +481,7 @@ func QueryHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
return fmt.Errorf(`too long query; got %d bytes; mustn't exceed %d bytes`, len(query), *maxQueryLen)
|
||||
}
|
||||
if ct-start < queryOffset {
|
||||
start -= queryOffset
|
||||
start = ct - queryOffset
|
||||
}
|
||||
if childQuery, windowStr, offsetStr := promql.IsMetricSelectorWithRollup(query); childQuery != "" {
|
||||
var window int64
|
||||
|
||||
5
app/vmselect/promql/arch.go
Normal file
5
app/vmselect/promql/arch.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package promql
|
||||
|
||||
import "unsafe"
|
||||
|
||||
const maxByteSliceLen = 1<<(31+9*(unsafe.Sizeof(int(0))/8)) - 1
|
||||
@@ -1,3 +0,0 @@
|
||||
package promql
|
||||
|
||||
const maxByteSliceLen = 1<<31 - 1
|
||||
@@ -1,3 +0,0 @@
|
||||
package promql
|
||||
|
||||
const maxByteSliceLen = 1 << 40
|
||||
@@ -1,3 +0,0 @@
|
||||
package promql
|
||||
|
||||
const maxByteSliceLen = 1<<31 - 1
|
||||
@@ -1,3 +0,0 @@
|
||||
package promql
|
||||
|
||||
const maxByteSliceLen = 1 << 40
|
||||
@@ -595,7 +595,18 @@ func evalRollupFuncWithMetricExpr(ec *EvalConfig, name string, rf rollupFunc, me
|
||||
// Verify timeseries fit available memory after the rollup.
|
||||
// Take into account points from tssCached.
|
||||
pointsPerTimeseries := 1 + (ec.End-ec.Start)/ec.Step
|
||||
rollupPoints := mulNoOverflow(pointsPerTimeseries, int64(rssLen*len(rcs)))
|
||||
timeseriesLen := rssLen
|
||||
if iafc != nil {
|
||||
// Incremental aggregates require hold only GOMAXPROCS timeseries in memory.
|
||||
timeseriesLen = runtime.GOMAXPROCS(-1)
|
||||
if iafc.ae.Modifier.Op != "" {
|
||||
// Increase the number of timeseries for non-empty group list: `aggr() by (something)`,
|
||||
// since each group can have own set of time series in memory.
|
||||
// Estimate the number of such groups is lower than 100 :)
|
||||
timeseriesLen *= 100
|
||||
}
|
||||
}
|
||||
rollupPoints := mulNoOverflow(pointsPerTimeseries, int64(timeseriesLen*len(rcs)))
|
||||
rollupMemorySize := mulNoOverflow(rollupPoints, 16)
|
||||
rml := getRollupMemoryLimiter()
|
||||
if !rml.Get(uint64(rollupMemorySize)) {
|
||||
|
||||
@@ -986,6 +986,8 @@ func rollupIntegrate(rfa *rollupFuncArg) float64 {
|
||||
timestamp := timestamps[i]
|
||||
dt := float64(timestamp-prevTimestamp) * 1e-3
|
||||
sum += 0.5 * (v + prevValue) * dt
|
||||
prevTimestamp = timestamp
|
||||
prevValue = v
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
@@ -291,7 +291,7 @@ func TestRollupNewRollupFuncSuccess(t *testing.T) {
|
||||
f("stdvar_over_time", 945.7430555555555)
|
||||
f("first_over_time", 123)
|
||||
f("last_over_time", 34)
|
||||
f("integrate", 61.0275)
|
||||
f("integrate", 5.4705)
|
||||
f("distinct_over_time", 8)
|
||||
f("ideriv", 0)
|
||||
f("decreases_over_time", 5)
|
||||
@@ -810,7 +810,7 @@ func TestRollupFuncsNoWindow(t *testing.T) {
|
||||
}
|
||||
rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step)
|
||||
values := rc.Do(nil, testValues, testTimestamps)
|
||||
valuesExpected := []float64{nan, 4.6035, 4.3934999999999995, 2.166, 0.34}
|
||||
valuesExpected := []float64{nan, 1.526, 2.2795, 1.325, 0.34}
|
||||
timestampsExpected := []int64{0, 40, 80, 120, 160}
|
||||
testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected)
|
||||
})
|
||||
|
||||
@@ -305,6 +305,9 @@ func registerStorageMetrics() {
|
||||
return float64(idbm().PartsRefCount)
|
||||
})
|
||||
|
||||
metrics.NewGauge(`vm_new_timeseries_created_total`, func() float64 {
|
||||
return float64(idbm().NewTimeseriesCreated)
|
||||
})
|
||||
metrics.NewGauge(`vm_missing_tsids_for_metric_id_total`, func() float64 {
|
||||
return float64(idbm().MissingTSIDsForMetricID)
|
||||
})
|
||||
@@ -320,6 +323,12 @@ func registerStorageMetrics() {
|
||||
metrics.NewGauge(`vm_date_metric_ids_search_hits_total`, func() float64 {
|
||||
return float64(idbm().DateMetricIDsSearchHits)
|
||||
})
|
||||
metrics.NewGauge(`vm_index_blocks_with_metric_ids_processed_total`, func() float64 {
|
||||
return float64(idbm().IndexBlocksWithMetricIDsProcessed)
|
||||
})
|
||||
metrics.NewGauge(`vm_index_blocks_with_metric_ids_incorrect_order_total`, func() float64 {
|
||||
return float64(idbm().IndexBlocksWithMetricIDsIncorrectOrder)
|
||||
})
|
||||
|
||||
metrics.NewGauge(`vm_assisted_merges_total{type="storage/small"}`, func() float64 {
|
||||
return float64(tm().SmallAssistedMerges)
|
||||
@@ -398,6 +407,20 @@ func registerStorageMetrics() {
|
||||
return float64(idbm().ItemsCount)
|
||||
})
|
||||
|
||||
metrics.NewGauge(`vm_date_range_search_calls_total`, func() float64 {
|
||||
return float64(idbm().DateRangeSearchCalls)
|
||||
})
|
||||
metrics.NewGauge(`vm_date_range_hits_total`, func() float64 {
|
||||
return float64(idbm().DateRangeSearchHits)
|
||||
})
|
||||
|
||||
metrics.NewGauge(`vm_date_metric_id_cache_syncs_total`, func() float64 {
|
||||
return float64(m().DateMetricIDCacheSyncsCount)
|
||||
})
|
||||
metrics.NewGauge(`vm_date_metric_id_cache_resets_total`, func() float64 {
|
||||
return float64(m().DateMetricIDCacheResetsCount)
|
||||
})
|
||||
|
||||
metrics.NewGauge(`vm_cache_entries{type="storage/tsid"}`, func() float64 {
|
||||
return float64(m().TSIDCacheSize)
|
||||
})
|
||||
@@ -447,6 +470,9 @@ func registerStorageMetrics() {
|
||||
metrics.NewGauge(`vm_cache_size_bytes{type="storage/date_metricID"}`, func() float64 {
|
||||
return float64(m().DateMetricIDCacheSizeBytes)
|
||||
})
|
||||
metrics.NewGauge(`vm_cache_size_bytes{type="storage/hour_metric_ids"}`, func() float64 {
|
||||
return float64(m().HourMetricIDCacheSizeBytes)
|
||||
})
|
||||
metrics.NewGauge(`vm_cache_size_bytes{type="indexdb/tagFilters"}`, func() float64 {
|
||||
return float64(idbm().TagCacheSizeBytes)
|
||||
})
|
||||
@@ -463,9 +489,6 @@ func registerStorageMetrics() {
|
||||
metrics.NewGauge(`vm_cache_requests_total{type="storage/metricName"}`, func() float64 {
|
||||
return float64(m().MetricNameCacheRequests)
|
||||
})
|
||||
metrics.NewGauge(`vm_cache_requests_total{type="storage/date_metricID"}`, func() float64 {
|
||||
return float64(m().DateMetricIDCacheRequests)
|
||||
})
|
||||
metrics.NewGauge(`vm_cache_requests_total{type="storage/bigIndexBlocks"}`, func() float64 {
|
||||
return float64(tm().BigIndexBlocksCacheRequests)
|
||||
})
|
||||
@@ -497,9 +520,6 @@ func registerStorageMetrics() {
|
||||
metrics.NewGauge(`vm_cache_misses_total{type="storage/metricName"}`, func() float64 {
|
||||
return float64(m().MetricNameCacheMisses)
|
||||
})
|
||||
metrics.NewGauge(`vm_cache_misses_total{type="storage/date_metricID"}`, func() float64 {
|
||||
return float64(m().DateMetricIDCacheMisses)
|
||||
})
|
||||
metrics.NewGauge(`vm_cache_misses_total{type="storage/bigIndexBlocks"}`, func() float64 {
|
||||
return float64(tm().BigIndexBlocksCacheMisses)
|
||||
})
|
||||
@@ -532,7 +552,4 @@ func registerStorageMetrics() {
|
||||
metrics.NewGauge(`vm_cache_collisions_total{type="storage/metricName"}`, func() float64 {
|
||||
return float64(m().MetricNameCacheCollisions)
|
||||
})
|
||||
metrics.NewGauge(`vm_cache_collisions_total{type="storage/date_metricID"}`, func() float64 {
|
||||
return float64(m().DateMetricIDCacheCollisions)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"type": "grafana",
|
||||
"id": "grafana",
|
||||
"name": "Grafana",
|
||||
"version": "6.3.5"
|
||||
"version": "6.4.4"
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
@@ -60,12 +60,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Overview for single node VictoriaMetrics v1.28.0 or higher",
|
||||
"description": "Overview for single node VictoriaMetrics v1.29.0 or higher",
|
||||
"editable": true,
|
||||
"gnetId": 10229,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"iteration": 1572208904768,
|
||||
"iteration": 1573509727687,
|
||||
"links": [
|
||||
{
|
||||
"icon": "doc",
|
||||
@@ -96,6 +96,7 @@
|
||||
"panels": [
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
@@ -109,6 +110,7 @@
|
||||
},
|
||||
{
|
||||
"content": "<div style=\"text-align: center; font-size: 2em\">$version</div>",
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "",
|
||||
"gridPos": {
|
||||
"h": 3,
|
||||
@@ -470,6 +472,7 @@
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
@@ -873,7 +876,7 @@
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "* `*` - unsupported query path\n* `/write` - insert into VM\n* `/metrics` - query VM system metrics\n* `/query` - query instant values\n* `/query_range` - query over a range of time\n* `/series` - match a certain label set\n* `/label/{}/values` - query a list of label values (variables mostly)",
|
||||
"description": "Shows how many of new time-series are created every second. High churn rate tightly connected with database performance and may result in unexpected OOM's or slow queries. It is recommended to always keep an eye on this metric to avoid unexpected cardinality \"explosions\".\n\nGood references to read:\n* https://www.robustperception.io/cardinality-is-key\n* https://www.robustperception.io/using-tsdb-analyze-to-investigate-churn-and-cardinality",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
@@ -882,6 +885,93 @@
|
||||
"x": 0,
|
||||
"y": 27
|
||||
},
|
||||
"id": 66,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(vm_new_timeseries_created_total{job=\"$job\"}[5m]))",
|
||||
"legendFormat": "churn rate",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "Churn rate",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "* `*` - unsupported query path\n* `/write` - insert into VM\n* `/metrics` - query VM system metrics\n* `/query` - query instant values\n* `/query_range` - query over a range of time\n* `/series` - match a certain label set\n* `/label/{}/values` - query a list of label values (variables mostly)",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 27
|
||||
},
|
||||
"id": 35,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
@@ -960,6 +1050,98 @@
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "Slow queries according to `search.logSlowQueryDuration` flag, which is `5s` by default.",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 35
|
||||
},
|
||||
"id": 60,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": false,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(vm_slow_queries_total{job=\"$job\"}[5m]))",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"intervalFactor": 1,
|
||||
"legendFormat": "slow queries rate",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "Slow queries",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"decimals": null,
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
@@ -973,7 +1155,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 27
|
||||
"y": 35
|
||||
},
|
||||
"id": 59,
|
||||
"legend": {
|
||||
@@ -1082,7 +1264,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 35
|
||||
"y": 43
|
||||
},
|
||||
"id": 37,
|
||||
"legend": {
|
||||
@@ -1174,7 +1356,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 35
|
||||
"y": 43
|
||||
},
|
||||
"id": 49,
|
||||
"legend": {
|
||||
@@ -1255,11 +1437,12 @@
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 43
|
||||
"y": 51
|
||||
},
|
||||
"id": 14,
|
||||
"panels": [],
|
||||
@@ -1279,7 +1462,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 44
|
||||
"y": 52
|
||||
},
|
||||
"id": 10,
|
||||
"legend": {
|
||||
@@ -1378,7 +1561,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 44
|
||||
"y": 52
|
||||
},
|
||||
"id": 34,
|
||||
"legend": {
|
||||
@@ -1483,7 +1666,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 52
|
||||
"y": 60
|
||||
},
|
||||
"id": 30,
|
||||
"legend": {
|
||||
@@ -1573,7 +1756,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 52
|
||||
"y": 60
|
||||
},
|
||||
"id": 36,
|
||||
"legend": {
|
||||
@@ -1663,7 +1846,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 60
|
||||
"y": 68
|
||||
},
|
||||
"id": 53,
|
||||
"legend": {
|
||||
@@ -1753,7 +1936,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 60
|
||||
"y": 68
|
||||
},
|
||||
"id": 55,
|
||||
"legend": {
|
||||
@@ -1829,6 +2012,182 @@
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "The number of on-going merges in storage nodes. It is expected to have high numbers for `storage/small` metric.",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 76
|
||||
},
|
||||
"id": 62,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(vm_active_merges{job=\"$job\"}) by(type)",
|
||||
"legendFormat": "{{type}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "Active merges",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"decimals": 0,
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"description": "The number of rows merged per second by storage nodes.",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 76
|
||||
},
|
||||
"id": 64,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(rate(vm_rows_merged_total{job=\"$job\"}[5m])) by(type)",
|
||||
"legendFormat": "{{type}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "Merge speed",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"decimals": 0,
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
@@ -1842,7 +2201,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 68
|
||||
"y": 84
|
||||
},
|
||||
"id": 58,
|
||||
"legend": {
|
||||
@@ -1923,11 +2282,12 @@
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 76
|
||||
"y": 92
|
||||
},
|
||||
"id": 46,
|
||||
"panels": [],
|
||||
@@ -1947,7 +2307,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 77
|
||||
"y": 93
|
||||
},
|
||||
"id": 44,
|
||||
"legend": {
|
||||
@@ -2046,13 +2406,14 @@
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "${DS_PROMETHEUS}",
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 77
|
||||
"y": 93
|
||||
},
|
||||
"id": 57,
|
||||
"legend": {
|
||||
@@ -2141,7 +2502,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 85
|
||||
"y": 101
|
||||
},
|
||||
"id": 47,
|
||||
"legend": {
|
||||
@@ -2231,7 +2592,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 85
|
||||
"y": 101
|
||||
},
|
||||
"id": 42,
|
||||
"legend": {
|
||||
@@ -2320,7 +2681,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 93
|
||||
"y": 109
|
||||
},
|
||||
"id": 48,
|
||||
"legend": {
|
||||
@@ -2400,7 +2761,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "30s",
|
||||
"schemaVersion": 19,
|
||||
"schemaVersion": 20,
|
||||
"style": "dark",
|
||||
"tags": [],
|
||||
"templating": {
|
||||
@@ -2483,5 +2844,5 @@
|
||||
"timezone": "",
|
||||
"title": "VictoriaMetrics",
|
||||
"uid": "wNf0q_kZk",
|
||||
"version": 3
|
||||
}
|
||||
"version": 4
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
DOCKER_NAMESPACE := victoriametrics
|
||||
BUILDER_IMAGE := local/builder:go1.13.3
|
||||
BUILDER_IMAGE := local/builder:go1.13.4
|
||||
CERTS_IMAGE := local/certs:1.0.2
|
||||
|
||||
package-certs:
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
FROM golang:1.13.3
|
||||
FROM golang:1.13.4
|
||||
STOPSIGNAL SIGINT
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.5'
|
||||
services:
|
||||
prometheus:
|
||||
container_name: prometheus
|
||||
image: prom/prometheus:v2.12.0
|
||||
image: prom/prometheus:v2.14.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
@@ -35,7 +35,7 @@ services:
|
||||
restart: always
|
||||
grafana:
|
||||
container_name: grafana
|
||||
image: grafana/grafana:6.3.5
|
||||
image: grafana/grafana:6.4.4
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
cd /var/lib/grafana &&
|
||||
|
||||
@@ -5,10 +5,10 @@ datasources:
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://prometheus:9090
|
||||
isDefault: false
|
||||
isDefault: true
|
||||
|
||||
- name: VictoriaMetrics
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://victoriametrics:8428
|
||||
isDefault: true
|
||||
isDefault: false
|
||||
|
||||
21
go.mod
21
go.mod
@@ -1,18 +1,29 @@
|
||||
module github.com/VictoriaMetrics/VictoriaMetrics
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.49.0 // indirect
|
||||
cloud.google.com/go/storage v1.4.0
|
||||
github.com/VictoriaMetrics/fastcache v1.5.2
|
||||
github.com/VictoriaMetrics/metrics v1.7.2
|
||||
github.com/cespare/xxhash/v2 v2.1.0
|
||||
github.com/aws/aws-sdk-go v1.25.37
|
||||
github.com/cespare/xxhash/v2 v2.1.1
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect
|
||||
github.com/golang/snappy v0.0.1
|
||||
github.com/google/go-cmp v0.3.0 // indirect
|
||||
github.com/klauspost/compress v1.9.1
|
||||
github.com/jstemmer/go-junit-report v0.9.1 // indirect
|
||||
github.com/klauspost/compress v1.9.2
|
||||
github.com/valyala/fastjson v1.4.1
|
||||
github.com/valyala/fastrand v1.0.0
|
||||
github.com/valyala/gozstd v1.6.2
|
||||
github.com/valyala/histogram v1.0.1
|
||||
github.com/valyala/quicktemplate v1.3.1
|
||||
golang.org/x/sys v0.0.0-20191027211539-f8518d3b3627
|
||||
github.com/valyala/quicktemplate v1.4.1
|
||||
go.opencensus.io v0.22.2 // indirect
|
||||
golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914 // indirect
|
||||
golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2
|
||||
golang.org/x/tools v0.0.0-20191119175705-11e13f1c3fd7 // indirect
|
||||
google.golang.org/api v0.14.0
|
||||
google.golang.org/appengine v1.6.5 // indirect
|
||||
google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11 // indirect
|
||||
google.golang.org/grpc v1.25.1 // indirect
|
||||
)
|
||||
|
||||
go 1.12
|
||||
|
||||
212
go.sum
212
go.sum
@@ -1,5 +1,26 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.49.0 h1:CH+lkubJzcPYB1Ggupcq0+k8Ni2ILdG2lYjDIgavDBQ=
|
||||
cloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY=
|
||||
cloud.google.com/go/bigquery v1.0.1 h1:hL+ycaJpVE9M7nLoiXb/Pn10ENE2u+oddxbD8uu0ZVU=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/pubsub v1.0.1 h1:W9tAK3E57P75u0XLLR82LZyw8VpAnhmyTOxW9qzmyj8=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.4.0 h1:KDdqY5VTXBTqpSbctVTt0mVvfanP6JZzNzLE0qNY100=
|
||||
cloud.google.com/go/storage v1.4.0/go.mod h1:ZusYJWlOshgSBGbt6K3GnB3MT3H1xs2id9+TCl4fDBA=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI=
|
||||
github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
|
||||
github.com/VictoriaMetrics/fastcache v1.5.2 h1:Erd8iIuBAL9kke8JzM4+WxkKuFkHh3ktwLanJvDgR44=
|
||||
github.com/VictoriaMetrics/fastcache v1.5.2/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE=
|
||||
@@ -7,33 +28,78 @@ github.com/VictoriaMetrics/metrics v1.7.2 h1:PzC0SEo5lbbNK7xaYwclCCdoaIGRmXOfflI
|
||||
github.com/VictoriaMetrics/metrics v1.7.2/go.mod h1:LU2j9qq7xqZYXz8tF3/RQnB2z2MbZms5TDiIg9/NHiQ=
|
||||
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
|
||||
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
|
||||
github.com/aws/aws-sdk-go v1.25.37 h1:gBtB/F3dophWpsUQKN/Kni+JzYEH2mGHF4hWNtfED1w=
|
||||
github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.0.1-0.20190104013014-3767db7a7e18 h1:pl4eWIqvFe/Kg3zkn7NxevNzILnZYWDCG7qbA1CJik0=
|
||||
github.com/cespare/xxhash/v2 v2.0.1-0.20190104013014-3767db7a7e18/go.mod h1:HD5P3vAIAh+Y2GAxg0PrPN1P8WkepXGpjbUPDHJqqKM=
|
||||
github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
|
||||
github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE=
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.9.1 h1:TWy0o9J9c6LK9C8t7Msh6IAJNXbsU/nvKLTQUU5HdaY=
|
||||
github.com/klauspost/compress v1.9.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.9.2 h1:LfVyl+ZlLlLDeQ/d2AqfGIIH4qEDu0Ed2S5GyhCWIWY=
|
||||
github.com/klauspost/compress v1.9.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.0.1-0.20190317074736-539464a789e9/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
|
||||
@@ -45,9 +111,133 @@ github.com/valyala/gozstd v1.6.2 h1:MgBfNm0I8IKm51LUTTKfO9vi4BtmoH7kBXeUvgaiZVU=
|
||||
github.com/valyala/gozstd v1.6.2/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
|
||||
github.com/valyala/histogram v1.0.1 h1:FzA7n2Tz/wKRMejgu3PV1vw3htAklTjjuoI6z3d4KDg=
|
||||
github.com/valyala/histogram v1.0.1/go.mod h1:lQy0xA4wUz2+IUnf97SivorsJIp8FxsnRd6x25q7Mto=
|
||||
github.com/valyala/quicktemplate v1.3.1 h1:V9Ixd/ONuoT6C1ipx8XR2dNGSDgIVnvT4ezZ38ZWllU=
|
||||
github.com/valyala/quicktemplate v1.3.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
|
||||
github.com/valyala/quicktemplate v1.4.1 h1:tEtkSN6mTCJlYVT7As5x4wjtkk2hj2thsb0M+AcAVeM=
|
||||
github.com/valyala/quicktemplate v1.4.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sys v0.0.0-20191027211539-f8518d3b3627 h1:/FZUR3d/QsXe4AcJyJFCc40TOj3y6Hs23Y3YJlvVkWo=
|
||||
golang.org/x/sys v0.0.0-20191027211539-f8518d3b3627/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914 h1:MlY3mEfbnWGmUi4rtHOtNnnnN4UJRGSyLPx+DXA5Sq4=
|
||||
golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2 h1:wAW1U21MfVN0sUipAD8952TBjGXMRHFKQugDlQ9RwwE=
|
||||
golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119175705-11e13f1c3fd7 h1:RyLBY/sJ/JwgAsmjUkGy/spRidXrmMj9uK4V6eiBypg=
|
||||
golang.org/x/tools v0.0.0-20191119175705-11e13f1c3fd7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.14.0 h1:uMf5uLi4eQMRrMKhCplNik4U4H8Z6C1br3zOtAa/aDE=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11 h1:51D++eCgOHufw5VfDE9Uzqyyc+OyQIjb9hkYy9LN5Fk=
|
||||
google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
||||
167
lib/backup/actions/backup.go
Normal file
167
lib/backup/actions/backup.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fslocal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fsnil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
// Backup performs backup according to the provided settings.
|
||||
//
|
||||
// Note that the backup works only for VictoriaMetrics snapshots
|
||||
// made via `/snapshot/create`. It works improperly on mutable files.
|
||||
type Backup struct {
|
||||
// Concurrency is the number of concurrent workers during the backup.
|
||||
// Concurrency=1 by default.
|
||||
Concurrency int
|
||||
|
||||
// Src is backup source
|
||||
Src *fslocal.FS
|
||||
|
||||
// Dst is backup destination.
|
||||
//
|
||||
// If dst contains the previous backup data, then incremental backup
|
||||
// is made, i.e. only the changed data is uploaded.
|
||||
//
|
||||
// If dst points to empty dir, then full backup is made.
|
||||
// Origin can be set to the previous backup in order to reduce backup duration
|
||||
// and reduce network bandwidth usage.
|
||||
Dst common.RemoteFS
|
||||
|
||||
// Origin is optional origin for speeding up full backup if Dst points
|
||||
// to empty dir.
|
||||
Origin common.OriginFS
|
||||
}
|
||||
|
||||
// Run runs b with the provided settings.
|
||||
func (b *Backup) Run() error {
|
||||
startTime := time.Now()
|
||||
|
||||
concurrency := b.Concurrency
|
||||
src := b.Src
|
||||
dst := b.Dst
|
||||
origin := b.Origin
|
||||
|
||||
if origin == nil {
|
||||
origin = &fsnil.FS{}
|
||||
}
|
||||
|
||||
logger.Infof("starting backup from %s to %s using origin %s", src, dst, origin)
|
||||
|
||||
logger.Infof("obtaining list of parts at %s", src)
|
||||
srcParts, err := src.ListParts()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot list src parts: %s", err)
|
||||
}
|
||||
logger.Infof("obtaining list of parts at %s", dst)
|
||||
dstParts, err := dst.ListParts()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot list dst parts: %s", err)
|
||||
}
|
||||
logger.Infof("obtaining list of parts at %s", origin)
|
||||
originParts, err := origin.ListParts()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot list origin parts: %s", err)
|
||||
}
|
||||
|
||||
backupSize := getPartsSize(srcParts)
|
||||
|
||||
partsToDelete := common.PartsDifference(dstParts, srcParts)
|
||||
deleteSize := getPartsSize(partsToDelete)
|
||||
if len(partsToDelete) > 0 {
|
||||
logger.Infof("deleting %d parts from %s", len(partsToDelete), dst)
|
||||
deletedParts := uint64(0)
|
||||
err = runParallel(concurrency, partsToDelete, func(p common.Part) error {
|
||||
logger.Infof("deleting %s from %s", &p, dst)
|
||||
if err := dst.DeletePart(p); err != nil {
|
||||
return fmt.Errorf("cannot delete %s from %s: %s", &p, dst, err)
|
||||
}
|
||||
atomic.AddUint64(&deletedParts, 1)
|
||||
return nil
|
||||
}, func(elapsed time.Duration) {
|
||||
n := atomic.LoadUint64(&deletedParts)
|
||||
logger.Infof("deleted %d out of %d parts from %s in %s", n, len(partsToDelete), dst, elapsed)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dst.RemoveEmptyDirs(); err != nil {
|
||||
return fmt.Errorf("cannot remove empty directories at %s: %s", dst, err)
|
||||
}
|
||||
}
|
||||
|
||||
partsToCopy := common.PartsDifference(srcParts, dstParts)
|
||||
originCopyParts := common.PartsIntersect(originParts, partsToCopy)
|
||||
copySize := getPartsSize(originCopyParts)
|
||||
if len(originCopyParts) > 0 {
|
||||
logger.Infof("server-side copying %d parts from %s to %s", len(originCopyParts), origin, dst)
|
||||
copiedParts := uint64(0)
|
||||
err = runParallel(concurrency, originCopyParts, func(p common.Part) error {
|
||||
logger.Infof("server-side copying %s from %s to %s", &p, origin, dst)
|
||||
if err := dst.CopyPart(origin, p); err != nil {
|
||||
return fmt.Errorf("cannot copy %s from %s to %s: %s", &p, origin, dst, err)
|
||||
}
|
||||
atomic.AddUint64(&copiedParts, 1)
|
||||
return nil
|
||||
}, func(elapsed time.Duration) {
|
||||
n := atomic.LoadUint64(&copiedParts)
|
||||
logger.Infof("server-side copied %d out of %d parts from %s to %s in %s", n, len(originCopyParts), origin, dst, elapsed)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
srcCopyParts := common.PartsDifference(partsToCopy, originParts)
|
||||
uploadSize := getPartsSize(srcCopyParts)
|
||||
if len(srcCopyParts) > 0 {
|
||||
logger.Infof("uploading %d parts from %s to %s", len(srcCopyParts), src, dst)
|
||||
bytesUploaded := uint64(0)
|
||||
err = runParallel(concurrency, srcCopyParts, func(p common.Part) error {
|
||||
logger.Infof("uploading %s from %s to %s", &p, src, dst)
|
||||
rc, err := src.NewReadCloser(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create reader for %s from %s: %s", &p, src, err)
|
||||
}
|
||||
sr := &statReader{
|
||||
r: rc,
|
||||
bytesRead: &bytesUploaded,
|
||||
}
|
||||
if err := dst.UploadPart(p, sr); err != nil {
|
||||
return fmt.Errorf("cannot upload %s to %s: %s", &p, dst, err)
|
||||
}
|
||||
if err = rc.Close(); err != nil {
|
||||
return fmt.Errorf("cannot close reader for %s from %s: %s", &p, src, err)
|
||||
}
|
||||
return nil
|
||||
}, func(elapsed time.Duration) {
|
||||
n := atomic.LoadUint64(&bytesUploaded)
|
||||
logger.Infof("uploaded %d out of %d bytes from %s to %s in %s", n, uploadSize, src, dst, elapsed)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
logger.Infof("backed up %d bytes in %s; deleted %d bytes; server-side copied %d bytes; uploaded %d bytes",
|
||||
backupSize, time.Since(startTime), deleteSize, copySize, uploadSize)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type statReader struct {
|
||||
r io.Reader
|
||||
bytesRead *uint64
|
||||
}
|
||||
|
||||
func (sr *statReader) Read(p []byte) (int, error) {
|
||||
n, err := sr.r.Read(p)
|
||||
atomic.AddUint64(sr.bytesRead, uint64(n))
|
||||
return n, err
|
||||
}
|
||||
181
lib/backup/actions/restore.go
Normal file
181
lib/backup/actions/restore.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fslocal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
// Restore restores data according to the provided settings.
|
||||
//
|
||||
// Note that the restore works only for VictoriaMetrics backups made from snapshots.
|
||||
// It works improperly on mutable files.
|
||||
type Restore struct {
|
||||
// Concurrency is the number of concurrent workers to run during restore.
|
||||
// Concurrency=1 is used by default.
|
||||
Concurrency int
|
||||
|
||||
// Src is the source containing backed up data.
|
||||
Src common.RemoteFS
|
||||
|
||||
// Dst is destination to restore the data.
|
||||
//
|
||||
// If dst points to existing directory, then incremental restore is performed,
|
||||
// i.e. only new data is downloaded from src.
|
||||
Dst *fslocal.FS
|
||||
}
|
||||
|
||||
// Run runs r with the provided settings.
|
||||
func (r *Restore) Run() error {
|
||||
startTime := time.Now()
|
||||
|
||||
// Make sure VictoriaMetrics doesn't run during the restore process.
|
||||
if err := fs.MkdirAllIfNotExist(r.Dst.Dir); err != nil {
|
||||
return fmt.Errorf("cannot create dir %q: %s", r.Dst.Dir, err)
|
||||
}
|
||||
flockF, err := fs.CreateFlockFile(r.Dst.Dir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create lock file in %q; make sure VictoriaMetrics doesn't use the dir; error: %s", r.Dst.Dir, err)
|
||||
}
|
||||
defer fs.MustClose(flockF)
|
||||
|
||||
concurrency := r.Concurrency
|
||||
src := r.Src
|
||||
dst := r.Dst
|
||||
logger.Infof("starting restore from %s to %s", src, dst)
|
||||
|
||||
logger.Infof("obtaining list of parts at %s", src)
|
||||
srcParts, err := src.ListParts()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot list src parts: %s", err)
|
||||
}
|
||||
logger.Infof("obtaining list of parts at %s", dst)
|
||||
dstParts, err := dst.ListParts()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot list dst parts: %s", err)
|
||||
}
|
||||
|
||||
backupSize := getPartsSize(srcParts)
|
||||
|
||||
// Validate srcParts. They must cover the whole files.
|
||||
common.SortParts(srcParts)
|
||||
offset := uint64(0)
|
||||
var pOld common.Part
|
||||
var path string
|
||||
for _, p := range srcParts {
|
||||
if p.Path != path {
|
||||
if offset != pOld.FileSize {
|
||||
return fmt.Errorf("invalid size for %q; got %d; want %d", path, offset, pOld.FileSize)
|
||||
}
|
||||
pOld = p
|
||||
path = p.Path
|
||||
offset = 0
|
||||
}
|
||||
if p.Offset < offset {
|
||||
return fmt.Errorf("there is an overlap in %d bytes between %s and %s", offset-p.Offset, &pOld, &p)
|
||||
}
|
||||
if p.Offset > offset {
|
||||
if offset == 0 {
|
||||
return fmt.Errorf("there is a gap in %d bytes from file start to %s", p.Offset, &p)
|
||||
}
|
||||
return fmt.Errorf("there is a gap in %d bytes between %s and %s", p.Offset-offset, &pOld, &p)
|
||||
}
|
||||
if p.Size != p.ActualSize {
|
||||
return fmt.Errorf("invalid size for %s; got %d; want %d", &p, p.ActualSize, p.Size)
|
||||
}
|
||||
offset += p.Size
|
||||
}
|
||||
|
||||
partsToDelete := common.PartsDifference(dstParts, srcParts)
|
||||
deleteSize := uint64(0)
|
||||
if len(partsToDelete) > 0 {
|
||||
// Fully remove local file if certain parts from the remote part are missing.
|
||||
pathsToDelete := make(map[string]bool)
|
||||
for _, p := range partsToDelete {
|
||||
pathsToDelete[p.Path] = true
|
||||
}
|
||||
logger.Infof("deleting %d files from %s", len(pathsToDelete), dst)
|
||||
for path := range pathsToDelete {
|
||||
logger.Infof("deleting %s from %s", path, dst)
|
||||
size, err := dst.DeletePath(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete %s from %s: %s", path, dst, err)
|
||||
}
|
||||
deleteSize += size
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dst.RemoveEmptyDirs(); err != nil {
|
||||
return fmt.Errorf("cannot remove empty directories at %s: %s", dst, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Re-read dstParts, since additional parts may be removed on the previous step.
|
||||
dstParts, err = dst.ListParts()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot list dst parts after the deletion: %s", err)
|
||||
}
|
||||
|
||||
partsToCopy := common.PartsDifference(srcParts, dstParts)
|
||||
downloadSize := getPartsSize(partsToCopy)
|
||||
if len(partsToCopy) > 0 {
|
||||
perPath := make(map[string][]common.Part)
|
||||
for _, p := range partsToCopy {
|
||||
parts := perPath[p.Path]
|
||||
parts = append(parts, p)
|
||||
perPath[p.Path] = parts
|
||||
}
|
||||
logger.Infof("downloading %d parts from %s to %s", len(partsToCopy), src, dst)
|
||||
bytesDownloaded := uint64(0)
|
||||
err = runParallelPerPath(concurrency, perPath, func(parts []common.Part) error {
|
||||
// Sort partsToCopy in order to properly grow file size during downloading.
|
||||
common.SortParts(parts)
|
||||
for _, p := range parts {
|
||||
logger.Infof("downloading %s from %s to %s", &p, src, dst)
|
||||
wc, err := dst.NewWriteCloser(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create writer for %q to %s: %s", &p, dst, err)
|
||||
}
|
||||
sw := &statWriter{
|
||||
w: wc,
|
||||
bytesWritten: &bytesDownloaded,
|
||||
}
|
||||
if err := src.DownloadPart(p, sw); err != nil {
|
||||
return fmt.Errorf("cannot download %s to %s: %s", &p, dst, err)
|
||||
}
|
||||
if err := wc.Close(); err != nil {
|
||||
return fmt.Errorf("cannot close reader fro %s from %s: %s", &p, src, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, func(elapsed time.Duration) {
|
||||
n := atomic.LoadUint64(&bytesDownloaded)
|
||||
logger.Infof("downloaded %d out of %d bytes from %s to %s in %s", n, downloadSize, src, dst, elapsed)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
logger.Infof("restored %d bytes from backup in %s; deleted %d bytes; downloaded %d bytes", backupSize, time.Since(startTime), deleteSize, downloadSize)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type statWriter struct {
|
||||
w io.Writer
|
||||
bytesWritten *uint64
|
||||
}
|
||||
|
||||
func (sw *statWriter) Write(p []byte) (int, error) {
|
||||
n, err := sw.w.Write(p)
|
||||
atomic.AddUint64(sw.bytesWritten, uint64(n))
|
||||
return n, err
|
||||
}
|
||||
231
lib/backup/actions/util.go
Normal file
231
lib/backup/actions/util.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fsremote"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/gcsremote"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/s3remote"
|
||||
)
|
||||
|
||||
var (
|
||||
credsFilePath = flag.String("credsFilePath", "", "Path to file with GCS or S3 credentials. Credentials are loaded from default locations if not set.\n"+
|
||||
"See https://cloud.google.com/iam/docs/creating-managing-service-account-keys and https://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html")
|
||||
configFilePath = flag.String("configFilePath", "", "Path to file with S3 configs. Configs are loaded from default location if not set.\n"+
|
||||
"See https://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html")
|
||||
)
|
||||
|
||||
func runParallel(concurrency int, parts []common.Part, f func(p common.Part) error, progress func(elapsed time.Duration)) error {
|
||||
var err error
|
||||
runWithProgress(progress, func() {
|
||||
err = runParallelInternal(concurrency, parts, f)
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func runParallelPerPath(concurrency int, perPath map[string][]common.Part, f func(parts []common.Part) error, progress func(elapsed time.Duration)) error {
|
||||
var err error
|
||||
runWithProgress(progress, func() {
|
||||
err = runParallelPerPathInternal(concurrency, perPath, f)
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func runParallelPerPathInternal(concurrency int, perPath map[string][]common.Part, f func(parts []common.Part) error) error {
|
||||
if concurrency <= 0 {
|
||||
concurrency = 1
|
||||
}
|
||||
if len(perPath) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// len(perPath) capacity guarantees non-blocking behavior below.
|
||||
resultCh := make(chan error, len(perPath))
|
||||
workCh := make(chan []common.Part, len(perPath))
|
||||
stopCh := make(chan struct{})
|
||||
|
||||
// Start workers
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(concurrency)
|
||||
for i := 0; i < concurrency; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for parts := range workCh {
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
default:
|
||||
}
|
||||
resultCh <- f(parts)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Feed workers with work.
|
||||
for _, parts := range perPath {
|
||||
workCh <- parts
|
||||
}
|
||||
close(workCh)
|
||||
|
||||
// Read results.
|
||||
var err error
|
||||
for i := 0; i < len(perPath); i++ {
|
||||
err = <-resultCh
|
||||
if err != nil {
|
||||
// Stop the work.
|
||||
close(stopCh)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for all the workers to stop.
|
||||
wg.Wait()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func runParallelInternal(concurrency int, parts []common.Part, f func(p common.Part) error) error {
|
||||
if concurrency <= 0 {
|
||||
concurrency = 1
|
||||
}
|
||||
if len(parts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// len(parts) capacity guarantees non-blocking behavior below.
|
||||
resultCh := make(chan error, len(parts))
|
||||
workCh := make(chan common.Part, len(parts))
|
||||
stopCh := make(chan struct{})
|
||||
|
||||
// Start workers
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(concurrency)
|
||||
for i := 0; i < concurrency; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for p := range workCh {
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
default:
|
||||
}
|
||||
resultCh <- f(p)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Feed workers with work.
|
||||
for _, p := range parts {
|
||||
workCh <- p
|
||||
}
|
||||
close(workCh)
|
||||
|
||||
// Read results.
|
||||
var err error
|
||||
for i := 0; i < len(parts); i++ {
|
||||
err = <-resultCh
|
||||
if err != nil {
|
||||
// Stop the work.
|
||||
close(stopCh)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for all the workers to stop.
|
||||
wg.Wait()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func runWithProgress(progress func(elapsed time.Duration), f func()) {
|
||||
startTime := time.Now()
|
||||
doneCh := make(chan struct{})
|
||||
go func() {
|
||||
f()
|
||||
close(doneCh)
|
||||
}()
|
||||
|
||||
tc := time.NewTicker(10 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-doneCh:
|
||||
tc.Stop()
|
||||
// The last progress call.
|
||||
progress(time.Since(startTime))
|
||||
return
|
||||
case <-tc.C:
|
||||
progress(time.Since(startTime))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getPartsSize(parts []common.Part) uint64 {
|
||||
n := uint64(0)
|
||||
for _, p := range parts {
|
||||
n += p.Size
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// NewRemoteFS returns new remote fs from the given path.
|
||||
func NewRemoteFS(path string) (common.RemoteFS, error) {
|
||||
if len(path) == 0 {
|
||||
return nil, fmt.Errorf("path cannot be empty")
|
||||
}
|
||||
n := strings.Index(path, "://")
|
||||
if n < 0 {
|
||||
return nil, fmt.Errorf("Missing scheme in path %q. Supported schemes: `gcs://`, `s3://`, `fs://`", path)
|
||||
}
|
||||
scheme := path[:n]
|
||||
dir := path[n+len("://"):]
|
||||
switch scheme {
|
||||
case "fs":
|
||||
if !strings.HasPrefix(dir, "/") {
|
||||
return nil, fmt.Errorf("dir must be absolute; got %q", dir)
|
||||
}
|
||||
fs := &fsremote.FS{
|
||||
Dir: dir,
|
||||
}
|
||||
return fs, nil
|
||||
case "gcs":
|
||||
n := strings.Index(dir, "/")
|
||||
if n < 0 {
|
||||
return nil, fmt.Errorf("missing directory on the gcs bucket %q", dir)
|
||||
}
|
||||
bucket := dir[:n]
|
||||
dir = dir[n:]
|
||||
fs := &gcsremote.FS{
|
||||
CredsFilePath: *credsFilePath,
|
||||
Bucket: bucket,
|
||||
Dir: dir,
|
||||
}
|
||||
if err := fs.Init(); err != nil {
|
||||
return nil, fmt.Errorf("cannot initialize connection to gcs: %s", err)
|
||||
}
|
||||
return fs, nil
|
||||
case "s3":
|
||||
n := strings.Index(dir, "/")
|
||||
if n < 0 {
|
||||
return nil, fmt.Errorf("missing directory on the s3 bucket %q", dir)
|
||||
}
|
||||
bucket := dir[:n]
|
||||
dir = dir[n:]
|
||||
fs := &s3remote.FS{
|
||||
CredsFilePath: *credsFilePath,
|
||||
ConfigFilePath: *configFilePath,
|
||||
Bucket: bucket,
|
||||
Dir: dir,
|
||||
}
|
||||
if err := fs.Init(); err != nil {
|
||||
return nil, fmt.Errorf("cannot initialize connection to s3: %s", err)
|
||||
}
|
||||
return fs, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported scheme %q in `-dst`", scheme)
|
||||
}
|
||||
}
|
||||
41
lib/backup/common/fs.go
Normal file
41
lib/backup/common/fs.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// OriginFS is an interface for remote origin filesystem.
|
||||
//
|
||||
// This filesystem is used for performing server-side file copies
|
||||
// instead of uploading data from local filesystem.
|
||||
type OriginFS interface {
|
||||
// String must return human-readable representation of OriginFS.
|
||||
String() string
|
||||
|
||||
// ListParts must return all the parts for the OriginFS.
|
||||
ListParts() ([]Part, error)
|
||||
}
|
||||
|
||||
// RemoteFS is a filesystem where backups are stored.
|
||||
type RemoteFS interface {
|
||||
// String must return human-readable representation of RemoteFS.
|
||||
String() string
|
||||
|
||||
// ListParts must return all the parts for the RemoteFS.
|
||||
ListParts() ([]Part, error)
|
||||
|
||||
// DeletePart must delete part p from RemoteFS.
|
||||
DeletePart(p Part) error
|
||||
|
||||
// RemoveEmptyDirs must recursively remove empty directories in RemoteFS.
|
||||
RemoveEmptyDirs() error
|
||||
|
||||
// CopyPart must copy part p from dstFS to RemoteFS.
|
||||
CopyPart(dstFS OriginFS, p Part) error
|
||||
|
||||
// DownloadPart must download part p from RemoteFS to w.
|
||||
DownloadPart(p Part, w io.Writer) error
|
||||
|
||||
// UploadPart must upload part p from r to RemoteFS.
|
||||
UploadPart(p Part, r io.Reader) error
|
||||
}
|
||||
136
lib/backup/common/part.go
Normal file
136
lib/backup/common/part.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
// Part is an atomic unit for transfer during backup / restore.
|
||||
//
|
||||
// Each source file can be split into parts with up to MaxPartSize sizes.
|
||||
type Part struct {
|
||||
// Path is the path to file for backup.
|
||||
Path string
|
||||
|
||||
// FileSize is the size of the whole file for the given part.
|
||||
FileSize uint64
|
||||
|
||||
// Offset is offset in the file to backup.
|
||||
Offset uint64
|
||||
|
||||
// Size is the size of the part to backup starting from Offset.
|
||||
Size uint64
|
||||
|
||||
// ActualSize is the actual size of the part.
|
||||
//
|
||||
// The part is considered broken if it isn't equal to Size.
|
||||
// Such a part must be removed from remote storage.
|
||||
ActualSize uint64
|
||||
}
|
||||
|
||||
func (p *Part) key() string {
|
||||
return fmt.Sprintf("%s%016X%016X%016X%016X", p.Path, p.FileSize, p.Offset, p.Size, p.ActualSize)
|
||||
}
|
||||
|
||||
// String returns human-readable representation of the part.
|
||||
func (p *Part) String() string {
|
||||
return fmt.Sprintf("part{path: %q, file_size: %d, offset: %d, size: %d}", p.Path, p.FileSize, p.Offset, p.Size)
|
||||
}
|
||||
|
||||
// RemotePath returns remote path for the part p and the given prefix.
|
||||
func (p *Part) RemotePath(prefix string) string {
|
||||
for strings.HasSuffix(prefix, "/") {
|
||||
prefix = prefix[:len(prefix)-1]
|
||||
}
|
||||
return fmt.Sprintf("%s/%s/%016X_%016X_%016X", prefix, p.Path, p.FileSize, p.Offset, p.Size)
|
||||
}
|
||||
|
||||
var partNameRegexp = regexp.MustCompile(`^(.+)/([0-9A-F]{16})_([0-9A-F]{16})_([0-9A-F]{16})$`)
|
||||
|
||||
// ParseFromRemotePath parses p from remotePath.
|
||||
//
|
||||
// Returns true on success.
|
||||
func (p *Part) ParseFromRemotePath(remotePath string) bool {
|
||||
tmp := partNameRegexp.FindStringSubmatch(remotePath)
|
||||
if len(tmp) != 5 {
|
||||
return false
|
||||
}
|
||||
path := tmp[1]
|
||||
for strings.HasPrefix(path, "/") {
|
||||
path = path[1:]
|
||||
}
|
||||
fileSize, err := strconv.ParseUint(tmp[2], 16, 64)
|
||||
if err != nil {
|
||||
logger.Panicf("BUG: cannot parse fileSize from %q: %s", tmp[2], err)
|
||||
}
|
||||
offset, err := strconv.ParseUint(tmp[3], 16, 64)
|
||||
if err != nil {
|
||||
logger.Panicf("BUG: cannot parse offset from %q: %s", tmp[3], err)
|
||||
}
|
||||
size, err := strconv.ParseUint(tmp[4], 16, 64)
|
||||
if err != nil {
|
||||
logger.Panicf("BUG: cannot parse size from %q: %s", tmp[4], err)
|
||||
}
|
||||
p.Path = path
|
||||
p.FileSize = fileSize
|
||||
p.Offset = offset
|
||||
p.Size = size
|
||||
return true
|
||||
}
|
||||
|
||||
// MaxPartSize is the maximum size for each part.
|
||||
//
|
||||
// The MaxPartSize reduces bandwidth usage during retires on network errors
|
||||
// when transferring multi-TB files.
|
||||
const MaxPartSize = 128 * 1024 * 1024
|
||||
|
||||
// SortParts sorts parts by (Path, Offset)
|
||||
func SortParts(parts []Part) {
|
||||
sort.Slice(parts, func(i, j int) bool {
|
||||
a := parts[i]
|
||||
b := parts[j]
|
||||
if a.Path != b.Path {
|
||||
return a.Path < b.Path
|
||||
}
|
||||
return a.Offset < b.Offset
|
||||
})
|
||||
}
|
||||
|
||||
// PartsDifference returns a - b
|
||||
func PartsDifference(a, b []Part) []Part {
|
||||
m := make(map[string]bool, len(b))
|
||||
for _, p := range b {
|
||||
k := p.key()
|
||||
m[k] = true
|
||||
}
|
||||
var d []Part
|
||||
for _, p := range a {
|
||||
k := p.key()
|
||||
if !m[k] {
|
||||
d = append(d, p)
|
||||
}
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// PartsIntersect returns the intersection of a and b
|
||||
func PartsIntersect(a, b []Part) []Part {
|
||||
m := make(map[string]bool, len(a))
|
||||
for _, p := range a {
|
||||
k := p.key()
|
||||
m[k] = true
|
||||
}
|
||||
var d []Part
|
||||
for _, p := range b {
|
||||
k := p.key()
|
||||
if m[k] {
|
||||
d = append(d, p)
|
||||
}
|
||||
}
|
||||
return d
|
||||
}
|
||||
251
lib/backup/fscommon/fscommon.go
Normal file
251
lib/backup/fscommon/fscommon.go
Normal file
@@ -0,0 +1,251 @@
|
||||
package fscommon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
// FsyncFile fsyncs path contents and the parent directory contents.
|
||||
func FsyncFile(path string) error {
|
||||
if err := fsync(path); err != nil {
|
||||
_ = os.RemoveAll(path)
|
||||
return fmt.Errorf("cannot fsync file %q: %s", path, err)
|
||||
}
|
||||
dir := filepath.Dir(path)
|
||||
if err := fsync(dir); err != nil {
|
||||
return fmt.Errorf("cannot fsync dir %q: %s", dir, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FsyncDir fsyncs dir contents.
|
||||
func FsyncDir(dir string) error {
|
||||
return fsync(dir)
|
||||
}
|
||||
|
||||
func fsync(path string) error {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.Sync(); err != nil {
|
||||
_ = f.Close()
|
||||
return err
|
||||
}
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
// AppendFiles appends all the files from dir to dst.
|
||||
//
|
||||
// All the appended files will have dir prefix.
|
||||
func AppendFiles(dst []string, dir string) ([]string, error) {
|
||||
d, err := os.Open(dir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open %q: %s", dir, err)
|
||||
}
|
||||
dst, err = appendFilesInternal(dst, d)
|
||||
if err1 := d.Close(); err1 != nil {
|
||||
err = err1
|
||||
}
|
||||
return dst, err
|
||||
}
|
||||
|
||||
func appendFilesInternal(dst []string, d *os.File) ([]string, error) {
|
||||
dir := d.Name()
|
||||
dfi, err := d.Stat()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot stat %q: %s", dir, err)
|
||||
}
|
||||
if !dfi.IsDir() {
|
||||
return nil, fmt.Errorf("%q isn't a directory", dir)
|
||||
}
|
||||
fis, err := d.Readdir(-1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read directory contents in %q: %s", dir, err)
|
||||
}
|
||||
for _, fi := range fis {
|
||||
name := fi.Name()
|
||||
if name == "." || name == ".." {
|
||||
continue
|
||||
}
|
||||
if name == "flock.lock" {
|
||||
// Do not take into account flock.lock files, since they are used
|
||||
// for preventing from concurrent access.
|
||||
continue
|
||||
}
|
||||
path := dir + "/" + name
|
||||
if fi.IsDir() {
|
||||
// Process directory
|
||||
dst, err = AppendFiles(dst, path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot list %q: %s", path, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if fi.Mode()&os.ModeSymlink != os.ModeSymlink {
|
||||
// Process file
|
||||
dst = append(dst, path)
|
||||
continue
|
||||
}
|
||||
pathOrig := path
|
||||
again:
|
||||
// Process symlink
|
||||
pathReal, err := filepath.EvalSymlinks(pathOrig)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) || strings.Contains(err.Error(), "no such file or directory") {
|
||||
// Skip symlink that points to nowhere.
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("cannot resolve symlink %q: %s", pathOrig, err)
|
||||
}
|
||||
sfi, err := os.Stat(pathReal)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot stat %q from symlink %q: %s", pathReal, path, err)
|
||||
}
|
||||
if sfi.IsDir() {
|
||||
// Symlink points to directory
|
||||
dstNew, err := AppendFiles(dst, pathReal)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot list files at %q from symlink %q: %s", pathReal, path, err)
|
||||
}
|
||||
pathReal += "/"
|
||||
for i := len(dst); i < len(dstNew); i++ {
|
||||
x := dstNew[i]
|
||||
if !strings.HasPrefix(x, pathReal) {
|
||||
return nil, fmt.Errorf("unexpected prefix for path %q; want %q", x, pathReal)
|
||||
}
|
||||
dstNew[i] = path + "/" + x[len(pathReal):]
|
||||
}
|
||||
dst = dstNew
|
||||
continue
|
||||
}
|
||||
if sfi.Mode()&os.ModeSymlink != os.ModeSymlink {
|
||||
// Symlink points to file
|
||||
dst = append(dst, path)
|
||||
continue
|
||||
}
|
||||
// Symlink points to symlink. Process it again.
|
||||
pathOrig = pathReal
|
||||
goto again
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
// RemoveEmptyDirs recursively removes empty directories under the given dir.
|
||||
func RemoveEmptyDirs(dir string) error {
|
||||
_, err := removeEmptyDirs(dir)
|
||||
return err
|
||||
}
|
||||
|
||||
func removeEmptyDirs(dir string) (bool, error) {
|
||||
d, err := os.Open(dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return true, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
ok, err := removeEmptyDirsInternal(d)
|
||||
if err1 := d.Close(); err1 != nil {
|
||||
err = err1
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
func removeEmptyDirsInternal(d *os.File) (bool, error) {
|
||||
dir := d.Name()
|
||||
dfi, err := d.Stat()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("cannot stat %q: %s", dir, err)
|
||||
}
|
||||
if !dfi.IsDir() {
|
||||
return false, fmt.Errorf("%q isn't a directory", dir)
|
||||
}
|
||||
fis, err := d.Readdir(-1)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("cannot read directory contents in %q: %s", dir, err)
|
||||
}
|
||||
dirEntries := 0
|
||||
for _, fi := range fis {
|
||||
name := fi.Name()
|
||||
if name == "." || name == ".." {
|
||||
continue
|
||||
}
|
||||
path := dir + "/" + name
|
||||
if fi.IsDir() {
|
||||
// Process directory
|
||||
ok, err := removeEmptyDirs(path)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("cannot list %q: %s", path, err)
|
||||
}
|
||||
if !ok {
|
||||
dirEntries++
|
||||
}
|
||||
continue
|
||||
}
|
||||
if fi.Mode()&os.ModeSymlink != os.ModeSymlink {
|
||||
// Skip plain files.
|
||||
dirEntries++
|
||||
continue
|
||||
}
|
||||
pathOrig := path
|
||||
again:
|
||||
// Process symlink
|
||||
pathReal, err := filepath.EvalSymlinks(pathOrig)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) || strings.Contains(err.Error(), "no such file or directory") {
|
||||
// Remove symlink that points to nowere.
|
||||
logger.Infof("removing broken symlink %q", pathOrig)
|
||||
if err := os.Remove(pathOrig); err != nil {
|
||||
return false, fmt.Errorf("cannot remove %q: %s", pathOrig, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
return false, fmt.Errorf("cannot resolve symlink %q: %s", pathOrig, err)
|
||||
}
|
||||
sfi, err := os.Stat(pathReal)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("cannot stat %q from symlink %q: %s", pathReal, path, err)
|
||||
}
|
||||
if sfi.IsDir() {
|
||||
// Symlink points to directory
|
||||
ok, err := removeEmptyDirs(pathReal)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("cannot list files at %q from symlink %q: %s", pathReal, path, err)
|
||||
}
|
||||
if !ok {
|
||||
dirEntries++
|
||||
} else {
|
||||
// Remove the symlink
|
||||
logger.Infof("removing symlink that points to empty dir %q", pathOrig)
|
||||
if err := os.Remove(pathOrig); err != nil {
|
||||
return false, fmt.Errorf("cannot remove %q: %s", pathOrig, err)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if sfi.Mode()&os.ModeSymlink != os.ModeSymlink {
|
||||
// Symlink points to file. Skip it.
|
||||
dirEntries++
|
||||
continue
|
||||
}
|
||||
// Symlink points to symlink. Process it again.
|
||||
pathOrig = pathReal
|
||||
goto again
|
||||
}
|
||||
if dirEntries > 0 {
|
||||
return false, nil
|
||||
}
|
||||
logger.Infof("removing empty dir %q", dir)
|
||||
if err := os.Remove(dir); err != nil {
|
||||
return false, fmt.Errorf("cannot remove %q: %s", dir, err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
116
lib/backup/fslocal/bandwidth_limiter.go
Normal file
116
lib/backup/fslocal/bandwidth_limiter.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package fslocal
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
type bandwidthLimiter struct {
|
||||
perSecondLimit int
|
||||
|
||||
c *sync.Cond
|
||||
|
||||
// quota for the current second
|
||||
quota int
|
||||
}
|
||||
|
||||
func newBandwidthLimiter(perSecondLimit int) *bandwidthLimiter {
|
||||
if perSecondLimit <= 0 {
|
||||
logger.Panicf("BUG: perSecondLimit must be positive; got %d", perSecondLimit)
|
||||
}
|
||||
var bl bandwidthLimiter
|
||||
bl.perSecondLimit = perSecondLimit
|
||||
var mu sync.Mutex
|
||||
bl.c = sync.NewCond(&mu)
|
||||
go bl.perSecondUpdater()
|
||||
return &bl
|
||||
}
|
||||
|
||||
func (bl *bandwidthLimiter) NewReadCloser(rc io.ReadCloser) *bandwidthLimitedReader {
|
||||
return &bandwidthLimitedReader{
|
||||
rc: rc,
|
||||
bl: bl,
|
||||
}
|
||||
}
|
||||
|
||||
func (bl *bandwidthLimiter) NewWriteCloser(wc io.WriteCloser) *bandwidthLimitedWriter {
|
||||
return &bandwidthLimitedWriter{
|
||||
wc: wc,
|
||||
bl: bl,
|
||||
}
|
||||
}
|
||||
|
||||
type bandwidthLimitedReader struct {
|
||||
rc io.ReadCloser
|
||||
bl *bandwidthLimiter
|
||||
}
|
||||
|
||||
func (blr *bandwidthLimitedReader) Read(p []byte) (int, error) {
|
||||
quota := blr.bl.GetQuota(len(p))
|
||||
return blr.rc.Read(p[:quota])
|
||||
}
|
||||
|
||||
func (blr *bandwidthLimitedReader) Close() error {
|
||||
return blr.rc.Close()
|
||||
}
|
||||
|
||||
type bandwidthLimitedWriter struct {
|
||||
wc io.WriteCloser
|
||||
bl *bandwidthLimiter
|
||||
}
|
||||
|
||||
func (blw *bandwidthLimitedWriter) Write(p []byte) (int, error) {
|
||||
nn := 0
|
||||
for len(p) > 0 {
|
||||
quota := blw.bl.GetQuota(len(p))
|
||||
n, err := blw.wc.Write(p[:quota])
|
||||
nn += n
|
||||
if err != nil {
|
||||
return nn, err
|
||||
}
|
||||
p = p[quota:]
|
||||
}
|
||||
return nn, nil
|
||||
}
|
||||
|
||||
func (blw *bandwidthLimitedWriter) Close() error {
|
||||
return blw.wc.Close()
|
||||
}
|
||||
|
||||
func (bl *bandwidthLimiter) perSecondUpdater() {
|
||||
tc := time.NewTicker(time.Second)
|
||||
c := bl.c
|
||||
for range tc.C {
|
||||
c.L.Lock()
|
||||
bl.quota = bl.perSecondLimit
|
||||
c.Signal()
|
||||
c.L.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// GetQuota returns the number in the range [1..n] - the allowed quota for now.
|
||||
//
|
||||
// The function blocks until at least 1 can be returned from it.
|
||||
func (bl *bandwidthLimiter) GetQuota(n int) int {
|
||||
if n <= 0 {
|
||||
logger.Panicf("BUG: n must be positive; got %d", n)
|
||||
}
|
||||
c := bl.c
|
||||
c.L.Lock()
|
||||
for bl.quota <= 0 {
|
||||
c.Wait()
|
||||
}
|
||||
quota := bl.quota
|
||||
if quota > n {
|
||||
quota = n
|
||||
}
|
||||
bl.quota -= quota
|
||||
if bl.quota > 0 {
|
||||
c.Signal()
|
||||
}
|
||||
c.L.Unlock()
|
||||
return quota
|
||||
}
|
||||
232
lib/backup/fslocal/fslocal.go
Normal file
232
lib/backup/fslocal/fslocal.go
Normal file
@@ -0,0 +1,232 @@
|
||||
package fslocal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fscommon"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/filestream"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
// FS represents local filesystem.
|
||||
//
|
||||
// Backups are made from local fs.
|
||||
// Data is restored from backups to local fs.
|
||||
type FS struct {
|
||||
// Dir is a path to local directory to work with.
|
||||
Dir string
|
||||
|
||||
// MaxBytesPerSecond is the maximum bandwidth usage during backups or restores.
|
||||
MaxBytesPerSecond int
|
||||
|
||||
bl *bandwidthLimiter
|
||||
}
|
||||
|
||||
// Init initializes fs
|
||||
func (fs *FS) Init() error {
|
||||
if fs.MaxBytesPerSecond > 0 {
|
||||
fs.bl = newBandwidthLimiter(fs.MaxBytesPerSecond)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns user-readable representation for the fs.
|
||||
func (fs *FS) String() string {
|
||||
return fmt.Sprintf("fslocal %q", fs.Dir)
|
||||
}
|
||||
|
||||
// ListParts returns all the parts for fs.
|
||||
func (fs *FS) ListParts() ([]common.Part, error) {
|
||||
dir := fs.Dir
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// Return empty part list for non-existing directory.
|
||||
// The directory will be created later.
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
files, err := fscommon.AppendFiles(nil, dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var parts []common.Part
|
||||
dir += "/"
|
||||
for _, file := range files {
|
||||
if !strings.HasPrefix(file, dir) {
|
||||
logger.Panicf("BUG: unexpected prefix for file %q; want %q", file, dir)
|
||||
}
|
||||
fi, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot stat %q: %s", file, err)
|
||||
}
|
||||
path := file[len(dir):]
|
||||
size := uint64(fi.Size())
|
||||
if size == 0 {
|
||||
parts = append(parts, common.Part{
|
||||
Path: path,
|
||||
Offset: 0,
|
||||
Size: 0,
|
||||
})
|
||||
continue
|
||||
}
|
||||
offset := uint64(0)
|
||||
for offset < size {
|
||||
n := size - offset
|
||||
if n > common.MaxPartSize {
|
||||
n = common.MaxPartSize
|
||||
}
|
||||
parts = append(parts, common.Part{
|
||||
Path: path,
|
||||
FileSize: size,
|
||||
Offset: offset,
|
||||
Size: n,
|
||||
ActualSize: n,
|
||||
})
|
||||
offset += n
|
||||
}
|
||||
}
|
||||
return parts, nil
|
||||
}
|
||||
|
||||
// NewReadCloser returns io.ReadCloser for the given part p located in fs.
|
||||
func (fs *FS) NewReadCloser(p common.Part) (io.ReadCloser, error) {
|
||||
path := fs.path(p)
|
||||
r, err := filestream.OpenReaderAt(path, int64(p.Offset), true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open %q at %q: %s", p.Path, fs.Dir, err)
|
||||
}
|
||||
lrc := &limitedReadCloser{
|
||||
r: r,
|
||||
n: p.Size,
|
||||
}
|
||||
if fs.bl == nil {
|
||||
return lrc, nil
|
||||
}
|
||||
blrc := fs.bl.NewReadCloser(lrc)
|
||||
return blrc, nil
|
||||
}
|
||||
|
||||
// NewWriteCloser returns io.WriteCloser for the given part p located in fs.
|
||||
func (fs *FS) NewWriteCloser(p common.Part) (io.WriteCloser, error) {
|
||||
path := fs.path(p)
|
||||
if err := fs.mkdirAll(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w, err := filestream.OpenWriterAt(path, int64(p.Offset), true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open writer for %q at offset %d: %s", path, p.Offset, err)
|
||||
}
|
||||
wc := &writeCloser{
|
||||
w: w,
|
||||
n: p.Size,
|
||||
path: path,
|
||||
}
|
||||
if fs.bl == nil {
|
||||
return wc, nil
|
||||
}
|
||||
blwc := fs.bl.NewWriteCloser(wc)
|
||||
return blwc, nil
|
||||
}
|
||||
|
||||
// DeletePath deletes the given path from fs and returns the size
|
||||
// for the deleted file.
|
||||
func (fs *FS) DeletePath(path string) (uint64, error) {
|
||||
p := common.Part{
|
||||
Path: path,
|
||||
}
|
||||
fullPath := fs.path(p)
|
||||
f, err := os.Open(fullPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// The file could be deleted earlier via symlink.
|
||||
return 0, nil
|
||||
}
|
||||
return 0, fmt.Errorf("cannot open %q at %q: %s", path, fullPath, err)
|
||||
}
|
||||
fi, err := f.Stat()
|
||||
_ = f.Close()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("cannot stat %q at %q: %s", path, fullPath, err)
|
||||
}
|
||||
size := uint64(fi.Size())
|
||||
if err := os.Remove(fullPath); err != nil {
|
||||
return 0, fmt.Errorf("cannot remove %q: %s", fullPath, err)
|
||||
}
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// RemoveEmptyDirs recursively removes all the empty directories in fs.
|
||||
func (fs *FS) RemoveEmptyDirs() error {
|
||||
return fscommon.RemoveEmptyDirs(fs.Dir)
|
||||
}
|
||||
|
||||
func (fs *FS) mkdirAll(filePath string) error {
|
||||
dir := filepath.Dir(filePath)
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
return fmt.Errorf("cannot create directory %q: %s", dir, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *FS) path(p common.Part) string {
|
||||
dir := fs.Dir
|
||||
for strings.HasSuffix(dir, "/") {
|
||||
dir = dir[:len(dir)-1]
|
||||
}
|
||||
return fs.Dir + "/" + p.Path
|
||||
}
|
||||
|
||||
type limitedReadCloser struct {
|
||||
r *filestream.Reader
|
||||
n uint64
|
||||
}
|
||||
|
||||
func (lrc *limitedReadCloser) Read(p []byte) (int, error) {
|
||||
if lrc.n == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if uint64(len(p)) > lrc.n {
|
||||
p = p[:lrc.n]
|
||||
}
|
||||
n, err := lrc.r.Read(p)
|
||||
if n > len(p) {
|
||||
return n, fmt.Errorf("too much data read; got %d bytes; want %d bytes", n, len(p))
|
||||
}
|
||||
lrc.n -= uint64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (lrc *limitedReadCloser) Close() error {
|
||||
lrc.r.MustClose()
|
||||
return nil
|
||||
}
|
||||
|
||||
type writeCloser struct {
|
||||
w *filestream.Writer
|
||||
n uint64
|
||||
path string
|
||||
}
|
||||
|
||||
func (wc *writeCloser) Write(p []byte) (int, error) {
|
||||
n, err := wc.w.Write(p)
|
||||
if uint64(n) > wc.n {
|
||||
return n, fmt.Errorf("too much data written; got %d bytes; want %d bytes", n, wc.n)
|
||||
}
|
||||
wc.n -= uint64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (wc *writeCloser) Close() error {
|
||||
wc.w.MustClose()
|
||||
if wc.n != 0 {
|
||||
return fmt.Errorf("missing data writes for %d bytes", wc.n)
|
||||
}
|
||||
return fscommon.FsyncFile(wc.path)
|
||||
}
|
||||
20
lib/backup/fsnil/fsnil.go
Normal file
20
lib/backup/fsnil/fsnil.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package fsnil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
|
||||
)
|
||||
|
||||
// FS represents nil remote filesystem.
|
||||
type FS struct{}
|
||||
|
||||
// String returns human-readable string representation for fs.
|
||||
func (fs *FS) String() string {
|
||||
return fmt.Sprintf("fsnil")
|
||||
}
|
||||
|
||||
// ListParts returns all the parts from fs.
|
||||
func (fs *FS) ListParts() ([]common.Part, error) {
|
||||
return nil, nil
|
||||
}
|
||||
190
lib/backup/fsremote/fsremote.go
Normal file
190
lib/backup/fsremote/fsremote.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package fsremote
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fscommon"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
// FS represents remote filesystem.
|
||||
//
|
||||
// Backups are uploaded there.
|
||||
// Data is downloaded from there during restore.
|
||||
type FS struct {
|
||||
// Dir is a path to remote directory with backup data.
|
||||
Dir string
|
||||
}
|
||||
|
||||
// String returns human-readable string representation for fs.
|
||||
func (fs *FS) String() string {
|
||||
return fmt.Sprintf("fsremote %q", fs.Dir)
|
||||
}
|
||||
|
||||
// ListParts returns all the parts from fs.
|
||||
func (fs *FS) ListParts() ([]common.Part, error) {
|
||||
dir := fs.Dir
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// Return empty part list for non-existing directory.
|
||||
// The directory will be created later.
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
files, err := fscommon.AppendFiles(nil, dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var parts []common.Part
|
||||
dir += "/"
|
||||
for _, file := range files {
|
||||
if !strings.HasPrefix(file, dir) {
|
||||
logger.Panicf("BUG: unexpected prefix for file %q; want %q", file, dir)
|
||||
}
|
||||
var p common.Part
|
||||
if !p.ParseFromRemotePath(file[len(dir):]) {
|
||||
logger.Infof("skipping unknown file %s", file)
|
||||
continue
|
||||
}
|
||||
// Check for correct part size.
|
||||
fi, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot stat file %q for part %q: %s", file, p.Path, err)
|
||||
}
|
||||
p.ActualSize = uint64(fi.Size())
|
||||
parts = append(parts, p)
|
||||
}
|
||||
return parts, nil
|
||||
}
|
||||
|
||||
// DeletePart deletes the given part p from fs.
|
||||
func (fs *FS) DeletePart(p common.Part) error {
|
||||
path := fs.path(p)
|
||||
if err := os.Remove(path); err != nil {
|
||||
return fmt.Errorf("cannot remove %q: %s", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveEmptyDirs recursively removes all the empty directories in fs.
|
||||
func (fs *FS) RemoveEmptyDirs() error {
|
||||
return fscommon.RemoveEmptyDirs(fs.Dir)
|
||||
}
|
||||
|
||||
// CopyPart copies the part p from srcFS to fs.
|
||||
//
|
||||
// srcFS must have *FS type.
|
||||
func (fs *FS) CopyPart(srcFS common.OriginFS, p common.Part) error {
|
||||
src, ok := srcFS.(*FS)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot perform server-side copying from %s to %s: both of them must be fsremote", srcFS, fs)
|
||||
}
|
||||
srcPath := src.path(p)
|
||||
dstPath := fs.path(p)
|
||||
if err := fs.mkdirAll(dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
// Attempt to create hardlink from srcPath to dstPath.
|
||||
if err := os.Link(srcPath, dstPath); err == nil {
|
||||
return fscommon.FsyncFile(dstPath)
|
||||
}
|
||||
|
||||
// Cannot create hardlink. Just copy file contents
|
||||
srcFile, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot open file %q: %s", srcPath, err)
|
||||
}
|
||||
dstFile, err := os.Create(dstPath)
|
||||
if err != nil {
|
||||
_ = srcFile.Close()
|
||||
return fmt.Errorf("cannot create file %q: %s", dstPath, err)
|
||||
}
|
||||
n, err := io.Copy(dstFile, srcFile)
|
||||
if err1 := dstFile.Close(); err1 != nil {
|
||||
err = err1
|
||||
}
|
||||
if err1 := srcFile.Close(); err1 != nil {
|
||||
err = err1
|
||||
}
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(dstPath)
|
||||
return err
|
||||
}
|
||||
if uint64(n) != p.Size {
|
||||
_ = os.RemoveAll(dstPath)
|
||||
return fmt.Errorf("unexpected number of bytes copied from %q to %q; got %d bytes; want %d bytes", srcPath, dstPath, n, p.Size)
|
||||
}
|
||||
if err := fscommon.FsyncFile(dstPath); err != nil {
|
||||
_ = os.RemoveAll(dstPath)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DownloadPart download part p from fs to w.
|
||||
func (fs *FS) DownloadPart(p common.Part, w io.Writer) error {
|
||||
path := fs.path(p)
|
||||
r, err := os.Open(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot open %q: %s", path, err)
|
||||
}
|
||||
n, err := io.Copy(w, r)
|
||||
if err1 := r.Close(); err1 != nil && err == nil {
|
||||
err = err1
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot download data from %q: %s", path, err)
|
||||
}
|
||||
if uint64(n) != p.Size {
|
||||
return fmt.Errorf("wrong data size downloaded from %q; got %d bytes; want %d bytes", path, n, p.Size)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UploadPart uploads p from r to fs.
|
||||
func (fs *FS) UploadPart(p common.Part, r io.Reader) error {
|
||||
path := fs.path(p)
|
||||
if err := fs.mkdirAll(path); err != nil {
|
||||
return err
|
||||
}
|
||||
w, err := os.Create(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create file %q: %s", path, err)
|
||||
}
|
||||
n, err := io.Copy(w, r)
|
||||
if err1 := w.Close(); err1 != nil && err == nil {
|
||||
err = err1
|
||||
}
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(path)
|
||||
return fmt.Errorf("cannot upload data to %q: %s", path, err)
|
||||
}
|
||||
if uint64(n) != p.Size {
|
||||
_ = os.RemoveAll(path)
|
||||
return fmt.Errorf("wrong data size uploaded to %q; got %d bytes; want %d bytes", path, n, p.Size)
|
||||
}
|
||||
if err := fscommon.FsyncFile(path); err != nil {
|
||||
_ = os.RemoveAll(path)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *FS) mkdirAll(filePath string) error {
|
||||
dir := filepath.Dir(filePath)
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
return fmt.Errorf("cannot create directory %q: %s", dir, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *FS) path(p common.Part) string {
|
||||
return p.RemotePath(fs.Dir)
|
||||
}
|
||||
189
lib/backup/gcsremote/gcs.go
Normal file
189
lib/backup/gcsremote/gcs.go
Normal file
@@ -0,0 +1,189 @@
|
||||
package gcsremote
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"cloud.google.com/go/storage"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
// FS represents filesystem for backups in GCS.
|
||||
//
|
||||
// Init must be called before calling other FS methods.
|
||||
type FS struct {
|
||||
// Path to GCP credentials file.
|
||||
//
|
||||
// Default credentials are used if empty.
|
||||
CredsFilePath string
|
||||
|
||||
// GCS bucket to use.
|
||||
Bucket string
|
||||
|
||||
// Directory in the bucket to write to.
|
||||
Dir string
|
||||
|
||||
bkt *storage.BucketHandle
|
||||
}
|
||||
|
||||
// Init initializes fs.
|
||||
func (fs *FS) Init() error {
|
||||
if fs.bkt != nil {
|
||||
logger.Panicf("BUG: fs.Init has been already called")
|
||||
}
|
||||
for strings.HasPrefix(fs.Dir, "/") {
|
||||
fs.Dir = fs.Dir[1:]
|
||||
}
|
||||
if !strings.HasSuffix(fs.Dir, "/") {
|
||||
fs.Dir += "/"
|
||||
}
|
||||
ctx := context.Background()
|
||||
var client *storage.Client
|
||||
if len(fs.CredsFilePath) > 0 {
|
||||
creds := option.WithCredentialsFile(fs.CredsFilePath)
|
||||
c, err := storage.NewClient(ctx, creds)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create gcs client with credsFile %q: %s", fs.CredsFilePath, err)
|
||||
}
|
||||
client = c
|
||||
} else {
|
||||
c, err := storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create default gcs client: %q", err)
|
||||
}
|
||||
client = c
|
||||
}
|
||||
fs.bkt = client.Bucket(fs.Bucket)
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns human-readable description for fs.
|
||||
func (fs *FS) String() string {
|
||||
return fmt.Sprintf("GCS{bucket: %q, dir: %q}", fs.Bucket, fs.Dir)
|
||||
}
|
||||
|
||||
// selectAttrs contains object attributes to select in ListParts.
|
||||
var selectAttrs = []string{
|
||||
"Name",
|
||||
"Size",
|
||||
}
|
||||
|
||||
// ListParts returns all the parts for fs.
|
||||
func (fs *FS) ListParts() ([]common.Part, error) {
|
||||
dir := fs.Dir
|
||||
ctx := context.Background()
|
||||
q := &storage.Query{
|
||||
Prefix: dir,
|
||||
}
|
||||
if err := q.SetAttrSelection(selectAttrs); err != nil {
|
||||
return nil, fmt.Errorf("error in SetAttrSelection: %s", err)
|
||||
}
|
||||
it := fs.bkt.Objects(ctx, q)
|
||||
var parts []common.Part
|
||||
for {
|
||||
attr, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
return parts, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error when iterating objects at %q: %s", dir, err)
|
||||
}
|
||||
file := attr.Name
|
||||
if !strings.HasPrefix(file, dir) {
|
||||
return nil, fmt.Errorf("unexpected prefix for gcs key %q; want %q", file, dir)
|
||||
}
|
||||
var p common.Part
|
||||
if !p.ParseFromRemotePath(file[len(dir):]) {
|
||||
logger.Infof("skipping unknown object %q", file)
|
||||
continue
|
||||
}
|
||||
p.ActualSize = uint64(attr.Size)
|
||||
parts = append(parts, p)
|
||||
}
|
||||
}
|
||||
|
||||
// DeletePart deletes part p from fs.
|
||||
func (fs *FS) DeletePart(p common.Part) error {
|
||||
o := fs.object(p)
|
||||
ctx := context.Background()
|
||||
if err := o.Delete(ctx); err != nil {
|
||||
return fmt.Errorf("cannot delete %q at %s (remote path %q): %s", p.Path, fs, o.ObjectName(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveEmptyDirs recursively removes empty dirs in fs.
|
||||
func (fs *FS) RemoveEmptyDirs() error {
|
||||
// GCS has no directories, so nothing to remove.
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyPart copies p from srcFS to fs.
|
||||
func (fs *FS) CopyPart(srcFS common.OriginFS, p common.Part) error {
|
||||
src, ok := srcFS.(*FS)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot perform server-side copying from %s to %s: both of them must be GCS", srcFS, fs)
|
||||
}
|
||||
srcObj := src.object(p)
|
||||
dstObj := fs.object(p)
|
||||
|
||||
copier := dstObj.CopierFrom(srcObj)
|
||||
ctx := context.Background()
|
||||
attr, err := copier.Run(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot copy %q from %s to %s: %s", p.Path, src, fs, err)
|
||||
}
|
||||
if uint64(attr.Size) != p.Size {
|
||||
return fmt.Errorf("unexpected %q size after copying from %s to %s; got %d bytes; want %d bytes", p.Path, src, fs, attr.Size, p.Size)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DownloadPart downloads part p from fs to w.
|
||||
func (fs *FS) DownloadPart(p common.Part, w io.Writer) error {
|
||||
o := fs.object(p)
|
||||
ctx := context.Background()
|
||||
r, err := o.NewReader(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot open reader for %q at %s (remote path %q): %s", p.Path, fs, o.ObjectName(), err)
|
||||
}
|
||||
n, err := io.Copy(w, r)
|
||||
if err1 := r.Close(); err1 != nil && err == nil {
|
||||
err = err1
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot download %q from at %s (remote path %q): %s", p.Path, fs, o.ObjectName(), err)
|
||||
}
|
||||
if uint64(n) != p.Size {
|
||||
return fmt.Errorf("wrong data size downloaded from %q at %s; got %d bytes; want %d bytes", p.Path, fs, n, p.Size)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UploadPart uploads part p from r to fs.
|
||||
func (fs *FS) UploadPart(p common.Part, r io.Reader) error {
|
||||
o := fs.object(p)
|
||||
ctx := context.Background()
|
||||
w := o.NewWriter(ctx)
|
||||
n, err := io.Copy(w, r)
|
||||
if err1 := w.Close(); err1 != nil && err == nil {
|
||||
err = err1
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot upload data to %q at %s (remote path %q): %s", p.Path, fs, o.ObjectName(), err)
|
||||
}
|
||||
if uint64(n) != p.Size {
|
||||
return fmt.Errorf("wrong data size uploaded to %q at %s; got %d bytes; want %d bytes", p.Path, fs, n, p.Size)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *FS) object(p common.Part) *storage.ObjectHandle {
|
||||
path := p.RemotePath(fs.Dir)
|
||||
return fs.bkt.Object(path)
|
||||
}
|
||||
220
lib/backup/s3remote/s3.go
Normal file
220
lib/backup/s3remote/s3.go
Normal file
@@ -0,0 +1,220 @@
|
||||
package s3remote
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
)
|
||||
|
||||
// FS represents filesystem for backups in S3.
|
||||
//
|
||||
// Init must be called before calling other FS methods.
|
||||
type FS struct {
|
||||
// Path to S3 credentials file.
|
||||
CredsFilePath string
|
||||
|
||||
// Pat to S3 configs file.
|
||||
ConfigFilePath string
|
||||
|
||||
// GCS bucket to use.
|
||||
Bucket string
|
||||
|
||||
// Directory in the bucket to write to.
|
||||
Dir string
|
||||
|
||||
s3 *s3.S3
|
||||
uploader *s3manager.Uploader
|
||||
}
|
||||
|
||||
// Init initializes fs.
|
||||
func (fs *FS) Init() error {
|
||||
if fs.s3 != nil {
|
||||
logger.Panicf("BUG: Init is already called")
|
||||
}
|
||||
for strings.HasPrefix(fs.Dir, "/") {
|
||||
fs.Dir = fs.Dir[1:]
|
||||
}
|
||||
if !strings.HasSuffix(fs.Dir, "/") {
|
||||
fs.Dir += "/"
|
||||
}
|
||||
opts := session.Options{
|
||||
SharedConfigState: session.SharedConfigEnable,
|
||||
}
|
||||
if len(fs.CredsFilePath) > 0 {
|
||||
opts.SharedConfigFiles = []string{
|
||||
fs.ConfigFilePath,
|
||||
fs.CredsFilePath,
|
||||
}
|
||||
}
|
||||
sess, err := session.NewSessionWithOptions(opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create S3 session: %s", err)
|
||||
}
|
||||
|
||||
// Determine bucket region.
|
||||
ctx := context.Background()
|
||||
region, err := s3manager.GetBucketRegion(ctx, sess, fs.Bucket, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot determine region for bucket %q: %s", fs.Bucket, err)
|
||||
}
|
||||
sess.Config.WithRegion(region)
|
||||
logger.Infof("bucket %q is stored at region %q; switching to this region", fs.Bucket, region)
|
||||
|
||||
fs.s3 = s3.New(sess)
|
||||
fs.uploader = s3manager.NewUploader(sess, func(u *s3manager.Uploader) {
|
||||
// We manage upload concurrency by ourselves.
|
||||
u.Concurrency = 1
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns human-readable description for fs.
|
||||
func (fs *FS) String() string {
|
||||
return fmt.Sprintf("S3{bucket: %q, dir: %q}", fs.Bucket, fs.Dir)
|
||||
}
|
||||
|
||||
// ListParts returns all the parts for fs.
|
||||
func (fs *FS) ListParts() ([]common.Part, error) {
|
||||
dir := fs.Dir
|
||||
input := &s3.ListObjectsV2Input{
|
||||
Bucket: aws.String(fs.Bucket),
|
||||
Prefix: aws.String(dir),
|
||||
}
|
||||
var errOuter error
|
||||
var parts []common.Part
|
||||
err := fs.s3.ListObjectsV2Pages(input, func(page *s3.ListObjectsV2Output, lastPage bool) bool {
|
||||
for _, o := range page.Contents {
|
||||
file := *o.Key
|
||||
if !strings.HasPrefix(file, dir) {
|
||||
errOuter = fmt.Errorf("unexpected prefix for s3 key %q; want %q", file, dir)
|
||||
return false
|
||||
}
|
||||
var p common.Part
|
||||
if !p.ParseFromRemotePath(file[len(dir):]) {
|
||||
logger.Infof("skipping unknown object %q", file)
|
||||
continue
|
||||
}
|
||||
p.ActualSize = uint64(*o.Size)
|
||||
parts = append(parts, p)
|
||||
}
|
||||
return !lastPage
|
||||
})
|
||||
if errOuter != nil && err == nil {
|
||||
err = errOuter
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error when listing s3 objects inside dir %q: %s", dir, err)
|
||||
}
|
||||
return parts, nil
|
||||
}
|
||||
|
||||
// DeletePart deletes part p from fs.
|
||||
func (fs *FS) DeletePart(p common.Part) error {
|
||||
path := fs.path(p)
|
||||
input := &s3.DeleteObjectInput{
|
||||
Bucket: aws.String(fs.Bucket),
|
||||
Key: aws.String(path),
|
||||
}
|
||||
_, err := fs.s3.DeleteObject(input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete %q at %s (remote path %q): %s", p.Path, fs, path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveEmptyDirs recursively removes empty dirs in fs.
|
||||
func (fs *FS) RemoveEmptyDirs() error {
|
||||
// S3 has no directories, so nothing to remove.
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyPart copies p from srcFS to fs.
|
||||
func (fs *FS) CopyPart(srcFS common.OriginFS, p common.Part) error {
|
||||
src, ok := srcFS.(*FS)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot perform server-side copying from %s to %s: both of them must be S3", srcFS, fs)
|
||||
}
|
||||
srcPath := src.path(p)
|
||||
dstPath := fs.path(p)
|
||||
copySource := fmt.Sprintf("/%s/%s", src.Bucket, srcPath)
|
||||
|
||||
input := &s3.CopyObjectInput{
|
||||
Bucket: aws.String(fs.Bucket),
|
||||
CopySource: aws.String(copySource),
|
||||
Key: aws.String(dstPath),
|
||||
}
|
||||
_, err := fs.s3.CopyObject(input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot copy %q from %s to %s (copySource %q): %s", p.Path, src, fs, copySource, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DownloadPart downloads part p from fs to w.
|
||||
func (fs *FS) DownloadPart(p common.Part, w io.Writer) error {
|
||||
path := fs.path(p)
|
||||
input := &s3.GetObjectInput{
|
||||
Bucket: aws.String(fs.Bucket),
|
||||
Key: aws.String(path),
|
||||
}
|
||||
o, err := fs.s3.GetObject(input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot open %q at %s (remote path %q): %s", p.Path, fs, path, err)
|
||||
}
|
||||
r := o.Body
|
||||
n, err := io.Copy(w, r)
|
||||
if err1 := r.Close(); err1 != nil && err == nil {
|
||||
err = err1
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot download %q from at %s (remote path %q): %s", p.Path, fs, path, err)
|
||||
}
|
||||
if uint64(n) != p.Size {
|
||||
return fmt.Errorf("wrong data size downloaded from %q at %s; got %d bytes; want %d bytes", p.Path, fs, n, p.Size)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UploadPart uploads part p from r to fs.
|
||||
func (fs *FS) UploadPart(p common.Part, r io.Reader) error {
|
||||
path := fs.path(p)
|
||||
sr := &statReader{
|
||||
r: r,
|
||||
}
|
||||
input := &s3manager.UploadInput{
|
||||
Bucket: aws.String(fs.Bucket),
|
||||
Key: aws.String(path),
|
||||
Body: sr,
|
||||
}
|
||||
_, err := fs.uploader.Upload(input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot upoad data to %q at %s (remote path %q): %s", p.Path, fs, path, err)
|
||||
}
|
||||
if uint64(sr.size) != p.Size {
|
||||
return fmt.Errorf("wrong data size uploaded to %q at %s; got %d bytes; want %d bytes", p.Path, fs, sr.size, p.Size)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *FS) path(p common.Part) string {
|
||||
return p.RemotePath(fs.Dir)
|
||||
}
|
||||
|
||||
type statReader struct {
|
||||
r io.Reader
|
||||
size int64
|
||||
}
|
||||
|
||||
func (sr *statReader) Read(p []byte) (int, error) {
|
||||
n, err := sr.r.Read(p)
|
||||
sr.size += int64(n)
|
||||
return n, err
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package bytesutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -20,3 +21,10 @@ func TestResize(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestToUnsafeString(t *testing.T) {
|
||||
s := "str"
|
||||
if !bytes.Equal([]byte("str"), ToUnsafeBytes(s)) {
|
||||
t.Fatalf(`[]bytes(%s) doesnt equal to %s `, s, s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package decimal
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fastnum"
|
||||
)
|
||||
|
||||
// CalibrateScale calibrates a and b with the corresponding exponents ae, be
|
||||
@@ -43,10 +45,6 @@ func CalibrateScale(a []int64, ae int16, b []int64, be int16) (e int16) {
|
||||
}
|
||||
if downExp > 0 {
|
||||
for i, v := range b {
|
||||
if v == vInfPos || v == vInfNeg {
|
||||
// Special case for these values - do not touch them.
|
||||
continue
|
||||
}
|
||||
adjExp := downExp
|
||||
for adjExp > 0 {
|
||||
v /= 10
|
||||
@@ -84,17 +82,32 @@ func AppendDecimalToFloat(dst []float64, va []int64, e int16) []float64 {
|
||||
// Extend dst capacity in order to eliminate memory allocations below.
|
||||
dst = ExtendFloat64sCapacity(dst, len(va))
|
||||
|
||||
if fastnum.IsInt64Zeros(va) {
|
||||
return fastnum.AppendFloat64Zeros(dst, len(va))
|
||||
}
|
||||
if e == 0 {
|
||||
if fastnum.IsInt64Ones(va) {
|
||||
return fastnum.AppendFloat64Ones(dst, len(va))
|
||||
}
|
||||
for _, v := range va {
|
||||
f := float64(v)
|
||||
dst = append(dst, f)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// increase conversion precision for negative exponents by dividing by e10
|
||||
if e < 0 {
|
||||
e10 := math.Pow10(int(-e))
|
||||
for _, v := range va {
|
||||
f := float64(v) / e10
|
||||
dst = append(dst, f)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
e10 := math.Pow10(int(e))
|
||||
for _, v := range va {
|
||||
// Manually inline ToFloat here for better performance
|
||||
var f float64
|
||||
if v == vInfPos {
|
||||
f = infPos
|
||||
} else if v == vInfNeg {
|
||||
f = infNeg
|
||||
} else {
|
||||
f = float64(v) * e10
|
||||
}
|
||||
f := float64(v) * e10
|
||||
dst = append(dst, f)
|
||||
}
|
||||
return dst
|
||||
@@ -108,6 +121,14 @@ func AppendFloatToDecimal(dst []int64, src []float64) (va []int64, e int16) {
|
||||
if len(src) == 0 {
|
||||
return dst, 0
|
||||
}
|
||||
if fastnum.IsFloat64Zeros(src) {
|
||||
dst = fastnum.AppendInt64Zeros(dst, len(src))
|
||||
return dst, 0
|
||||
}
|
||||
if fastnum.IsFloat64Ones(src) {
|
||||
dst = fastnum.AppendInt64Ones(dst, len(src))
|
||||
return dst, 0
|
||||
}
|
||||
|
||||
// Extend dst capacity in order to eliminate memory allocations below.
|
||||
dst = ExtendInt64sCapacity(dst, len(src))
|
||||
@@ -237,13 +258,12 @@ func maxUpExponent(v int64) int16 {
|
||||
|
||||
// ToFloat returns f=v*10^e.
|
||||
func ToFloat(v int64, e int16) float64 {
|
||||
if v == vInfPos {
|
||||
return infPos
|
||||
f := float64(v)
|
||||
// increase conversion precision for negative exponents by dividing by e10
|
||||
if e < 0 {
|
||||
return f / math.Pow10(int(-e))
|
||||
}
|
||||
if v == vInfNeg {
|
||||
return infNeg
|
||||
}
|
||||
return float64(v) * math.Pow10(int(e))
|
||||
return f * math.Pow10(int(e))
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -254,11 +274,6 @@ const (
|
||||
vMin = -1<<63 + 1
|
||||
)
|
||||
|
||||
var (
|
||||
infPos = math.Inf(1)
|
||||
infNeg = math.Inf(-1)
|
||||
)
|
||||
|
||||
// FromFloat converts f to v*10^e.
|
||||
//
|
||||
// It tries minimizing v.
|
||||
@@ -288,7 +303,7 @@ func FromFloat(f float64) (int64, int16) {
|
||||
}
|
||||
|
||||
func fromFloatInf(f float64) (int64, int16) {
|
||||
// Special case for Inf
|
||||
// Limit infs by max and min values for int64
|
||||
if math.IsInf(f, 1) {
|
||||
return vInfPos, 0
|
||||
}
|
||||
|
||||
@@ -43,6 +43,9 @@ func TestPositiveFloatToDecimal(t *testing.T) {
|
||||
f(1234567890123456789e-14, 1234567890123, -8)
|
||||
f(1234567890123456789e-17, 12345678901234, -12)
|
||||
f(1234567890123456789e-20, 1234567890123, -14)
|
||||
|
||||
f(0.000874957, 874957, -9)
|
||||
f(0.001130435, 1130435, -9)
|
||||
}
|
||||
|
||||
func TestAppendDecimalToFloat(t *testing.T) {
|
||||
@@ -52,6 +55,16 @@ func TestAppendDecimalToFloat(t *testing.T) {
|
||||
testAppendDecimalToFloat(t, []int64{0}, -10, []float64{0})
|
||||
testAppendDecimalToFloat(t, []int64{-1, -10, 0, 100}, 2, []float64{-1e2, -1e3, 0, 1e4})
|
||||
testAppendDecimalToFloat(t, []int64{-1, -10, 0, 100}, -2, []float64{-1e-2, -1e-1, 0, 1})
|
||||
testAppendDecimalToFloat(t, []int64{874957, 1130435}, -5, []float64{8.74957, 1.130435e1})
|
||||
testAppendDecimalToFloat(t, []int64{874957, 1130435}, -6, []float64{8.74957e-1, 1.130435})
|
||||
testAppendDecimalToFloat(t, []int64{874957, 1130435}, -7, []float64{8.74957e-2, 1.130435e-1})
|
||||
testAppendDecimalToFloat(t, []int64{874957, 1130435}, -8, []float64{8.74957e-3, 1.130435e-2})
|
||||
testAppendDecimalToFloat(t, []int64{874957, 1130435}, -9, []float64{8.74957e-4, 1.130435e-3})
|
||||
testAppendDecimalToFloat(t, []int64{874957, 1130435}, -10, []float64{8.74957e-5, 1.130435e-4})
|
||||
testAppendDecimalToFloat(t, []int64{874957, 1130435}, -11, []float64{8.74957e-6, 1.130435e-5})
|
||||
testAppendDecimalToFloat(t, []int64{874957, 1130435}, -12, []float64{8.74957e-7, 1.130435e-6})
|
||||
testAppendDecimalToFloat(t, []int64{874957, 1130435}, -13, []float64{8.74957e-8, 1.130435e-7})
|
||||
testAppendDecimalToFloat(t, []int64{vInfPos, vInfNeg, 1, 2}, 0, []float64{9.223372036854776e+18, -9.223372036854776e+18, 1, 2})
|
||||
}
|
||||
|
||||
func testAppendDecimalToFloat(t *testing.T, va []int64, e int16, fExpected []float64) {
|
||||
@@ -93,7 +106,7 @@ func TestCalibrateScale(t *testing.T) {
|
||||
testCalibrateScale(t, []int64{vInfPos, 1200}, []int64{500, 100}, 0, 2, []int64{vInfPos, 1200}, []int64{500e2, 100e2}, 0)
|
||||
testCalibrateScale(t, []int64{vInfPos, 1200}, []int64{500, 100}, 0, -2, []int64{vInfPos, 1200}, []int64{5, 1}, 0)
|
||||
testCalibrateScale(t, []int64{vInfPos, 1200}, []int64{3500, 100}, 0, -3, []int64{vInfPos, 1200}, []int64{3, 0}, 0)
|
||||
testCalibrateScale(t, []int64{vInfPos, 1200}, []int64{35, 1}, 0, 40, []int64{vInfPos, 0}, []int64{35e17, 1e17}, 23)
|
||||
testCalibrateScale(t, []int64{vInfPos, 1200}, []int64{35, 1}, 0, 40, []int64{0, 0}, []int64{35e17, 1e17}, 23)
|
||||
testCalibrateScale(t, []int64{vInfPos, 1200}, []int64{35, 1}, 40, 0, []int64{vInfPos, 1200}, []int64{0, 0}, 40)
|
||||
testCalibrateScale(t, []int64{vInfNeg, 1200}, []int64{35, 1}, 35, -5, []int64{vInfNeg, 1200}, []int64{0, 0}, 35)
|
||||
testCalibrateScale(t, []int64{vMax, vMin, 123}, []int64{100}, 0, 3, []int64{vMax, vMin, 123}, []int64{100e3}, 0)
|
||||
@@ -159,6 +172,8 @@ func TestMaxUpExponent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
f(vInfPos, 0)
|
||||
f(vInfNeg, 0)
|
||||
f(0, 1024)
|
||||
f(-1<<63, 0)
|
||||
f((-1<<63)+1, 0)
|
||||
@@ -206,6 +221,9 @@ func TestAppendFloatToDecimal(t *testing.T) {
|
||||
// no-op
|
||||
testAppendFloatToDecimal(t, []float64{}, nil, 0)
|
||||
testAppendFloatToDecimal(t, []float64{0}, []int64{0}, 0)
|
||||
testAppendFloatToDecimal(t, []float64{infPos, infNeg, 123}, []int64{vInfPos, vInfNeg, 123}, 0)
|
||||
testAppendFloatToDecimal(t, []float64{infPos, infNeg, 123, 1e-4, 1e32}, []int64{92233, -92233, 0, 0, 1000000000000000000}, 14)
|
||||
testAppendFloatToDecimal(t, []float64{float64(vInfPos), float64(vInfNeg), 123}, []int64{9223372036854775000, -9223372036854775000, 123}, 0)
|
||||
testAppendFloatToDecimal(t, []float64{0, -0, 1, -1, 12345678, -123456789}, []int64{0, 0, 1, -1, 12345678, -123456789}, 0)
|
||||
|
||||
// upExp
|
||||
@@ -323,11 +341,15 @@ func TestFloatToDecimalRoundtrip(t *testing.T) {
|
||||
f(12.34567890125)
|
||||
f(-1234567.8901256789)
|
||||
f(15e18)
|
||||
f(0.000874957)
|
||||
f(0.001130435)
|
||||
|
||||
f(math.Inf(1))
|
||||
f(math.Inf(-1))
|
||||
f(1<<63 - 1)
|
||||
f(-1 << 63)
|
||||
f(2933434554455e245)
|
||||
f(3439234258934e-245)
|
||||
f(float64(vInfPos))
|
||||
f(float64(vInfNeg))
|
||||
f(infPos)
|
||||
f(infNeg)
|
||||
|
||||
for i := 0; i < 1e4; i++ {
|
||||
v := rand.NormFloat64()
|
||||
@@ -351,9 +373,26 @@ func roundFloat(f float64, exp int) float64 {
|
||||
}
|
||||
|
||||
func equalFloat(f1, f2 float64) bool {
|
||||
f1 = adjustInf(f1)
|
||||
f2 = adjustInf(f2)
|
||||
if math.IsInf(f1, 0) {
|
||||
return math.IsInf(f1, 1) == math.IsInf(f2, 1) || math.IsInf(f1, -1) == math.IsInf(f2, -1)
|
||||
}
|
||||
eps := math.Abs(f1 - f2)
|
||||
return eps == 0 || eps*conversionPrecision < math.Abs(f1)+math.Abs(f2)
|
||||
}
|
||||
|
||||
func adjustInf(f float64) float64 {
|
||||
if f == float64(vInfPos) {
|
||||
return infPos
|
||||
}
|
||||
if f == float64(vInfNeg) {
|
||||
return infNeg
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
var (
|
||||
infPos = math.Inf(1)
|
||||
infNeg = math.Inf(-1)
|
||||
)
|
||||
|
||||
@@ -8,17 +8,41 @@ import (
|
||||
)
|
||||
|
||||
func BenchmarkAppendDecimalToFloat(b *testing.B) {
|
||||
b.Run("RealFloat", func(b *testing.B) {
|
||||
benchmarkAppendDecimalToFloat(b, testVA, vaScale)
|
||||
})
|
||||
b.Run("Integers", func(b *testing.B) {
|
||||
benchmarkAppendDecimalToFloat(b, testIntegers, integersScale)
|
||||
})
|
||||
b.Run("Zeros", func(b *testing.B) {
|
||||
benchmarkAppendDecimalToFloat(b, testZeros, 0)
|
||||
})
|
||||
b.Run("Ones", func(b *testing.B) {
|
||||
benchmarkAppendDecimalToFloat(b, testOnes, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkAppendDecimalToFloat(b *testing.B, a []int64, scale int16) {
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(len(testVA)))
|
||||
b.SetBytes(int64(len(a)))
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var fa []float64
|
||||
for pb.Next() {
|
||||
fa = AppendDecimalToFloat(fa[:0], testVA, 0)
|
||||
fa = AppendDecimalToFloat(fa[:0], a, scale)
|
||||
atomic.AddUint64(&Sink, uint64(len(fa)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var testZeros = make([]int64, 8*1024)
|
||||
var testOnes = func() []int64 {
|
||||
a := make([]int64, 8*1024)
|
||||
for i := 0; i < len(a); i++ {
|
||||
a[i] = 1
|
||||
}
|
||||
return a
|
||||
}()
|
||||
|
||||
func BenchmarkAppendFloatToDecimal(b *testing.B) {
|
||||
b.Run("RealFloat", func(b *testing.B) {
|
||||
benchmarkAppendFloatToDecimal(b, testFAReal)
|
||||
@@ -26,8 +50,23 @@ func BenchmarkAppendFloatToDecimal(b *testing.B) {
|
||||
b.Run("Integers", func(b *testing.B) {
|
||||
benchmarkAppendFloatToDecimal(b, testFAInteger)
|
||||
})
|
||||
b.Run("Zeros", func(b *testing.B) {
|
||||
benchmarkAppendFloatToDecimal(b, testFZeros)
|
||||
})
|
||||
b.Run("Ones", func(b *testing.B) {
|
||||
benchmarkAppendFloatToDecimal(b, testFOnes)
|
||||
})
|
||||
}
|
||||
|
||||
var testFZeros = make([]float64, 8*1024)
|
||||
var testFOnes = func() []float64 {
|
||||
a := make([]float64, 8*1024)
|
||||
for i := 0; i < len(a); i++ {
|
||||
a[i] = 1
|
||||
}
|
||||
return a
|
||||
}()
|
||||
|
||||
func benchmarkAppendFloatToDecimal(b *testing.B, fa []float64) {
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(len(fa)))
|
||||
@@ -47,7 +86,7 @@ func benchmarkAppendFloatToDecimal(b *testing.B, fa []float64) {
|
||||
var testFAReal = func() []float64 {
|
||||
fa := make([]float64, 8*1024)
|
||||
for i := 0; i < len(fa); i++ {
|
||||
fa[i] = rand.NormFloat64() * 1e6
|
||||
fa[i] = rand.NormFloat64() * 1e-6
|
||||
}
|
||||
return fa
|
||||
}()
|
||||
@@ -60,10 +99,8 @@ var testFAInteger = func() []float64 {
|
||||
return fa
|
||||
}()
|
||||
|
||||
var testVA = func() []int64 {
|
||||
va, _ := AppendFloatToDecimal(nil, testFAReal)
|
||||
return va
|
||||
}()
|
||||
var testVA, vaScale = AppendFloatToDecimal(nil, testFAReal)
|
||||
var testIntegers, integersScale = AppendFloatToDecimal(nil, testFAInteger)
|
||||
|
||||
func BenchmarkFromFloat(b *testing.B) {
|
||||
for _, f := range []float64{0, 1234, 12334345, 12343.4344, 123.45678901e12, 12.3454435e30} {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fastnum"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
@@ -201,6 +202,14 @@ func unmarshalInt64Array(dst []int64, src []byte, mt MarshalType, firstValue int
|
||||
if len(src) > 0 {
|
||||
return nil, fmt.Errorf("unexpected data left in const encoding: %d bytes", len(src))
|
||||
}
|
||||
if firstValue == 0 {
|
||||
dst = fastnum.AppendInt64Zeros(dst, itemsCount)
|
||||
return dst, nil
|
||||
}
|
||||
if firstValue == 1 {
|
||||
dst = fastnum.AppendInt64Ones(dst, itemsCount)
|
||||
return dst, nil
|
||||
}
|
||||
for itemsCount > 0 {
|
||||
dst = append(dst, firstValue)
|
||||
itemsCount--
|
||||
@@ -267,6 +276,14 @@ func isConst(a []int64) bool {
|
||||
if len(a) == 0 {
|
||||
return false
|
||||
}
|
||||
if fastnum.IsInt64Zeros(a) {
|
||||
// Fast path for array containing only zeros.
|
||||
return true
|
||||
}
|
||||
if fastnum.IsInt64Ones(a) {
|
||||
// Fast path for array containing only ones.
|
||||
return true
|
||||
}
|
||||
v1 := a[0]
|
||||
for _, v := range a {
|
||||
if v != v1 {
|
||||
|
||||
@@ -32,7 +32,7 @@ func BenchmarkUnmarshalGaugeArray(b *testing.B) {
|
||||
var dst []int64
|
||||
var err error
|
||||
for pb.Next() {
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledGaugeArray, MarshalTypeZSTDNearestDelta, 0, len(benchGaugeArray))
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledGaugeArray, MarshalTypeZSTDNearestDelta, benchGaugeArray[0], len(benchGaugeArray))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot unmarshal gauge array: %s", err))
|
||||
}
|
||||
@@ -79,7 +79,7 @@ func BenchmarkUnmarshalDeltaConstArray(b *testing.B) {
|
||||
var dst []int64
|
||||
var err error
|
||||
for pb.Next() {
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledDeltaConstArray, MarshalTypeDeltaConst, 0, len(benchDeltaConstArray))
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledDeltaConstArray, MarshalTypeDeltaConst, benchDeltaConstArray[0], len(benchDeltaConstArray))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot unmarshal delta const array: %s", err))
|
||||
}
|
||||
@@ -126,7 +126,7 @@ func BenchmarkUnmarshalConstArray(b *testing.B) {
|
||||
var dst []int64
|
||||
var err error
|
||||
for pb.Next() {
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledConstArray, MarshalTypeConst, 0, len(benchConstArray))
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledConstArray, MarshalTypeConst, benchConstArray[0], len(benchConstArray))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot unmarshal const array: %s", err))
|
||||
}
|
||||
@@ -171,7 +171,7 @@ func BenchmarkUnmarshalZeroConstArray(b *testing.B) {
|
||||
var dst []int64
|
||||
var err error
|
||||
for pb.Next() {
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledZeroConstArray, MarshalTypeConst, 0, len(benchZeroConstArray))
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledZeroConstArray, MarshalTypeConst, benchZeroConstArray[0], len(benchZeroConstArray))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot unmarshal zero const array: %s", err))
|
||||
}
|
||||
@@ -210,7 +210,7 @@ func BenchmarkUnmarshalInt64Array(b *testing.B) {
|
||||
var dst []int64
|
||||
var err error
|
||||
for pb.Next() {
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledInt64Array, benchMarshalType, 0, len(benchInt64Array))
|
||||
dst, err = unmarshalInt64Array(dst[:0], benchMarshaledInt64Array, benchMarshalType, benchInt64Array[0], len(benchInt64Array))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot unmarshal int64 array: %s", err))
|
||||
}
|
||||
|
||||
144
lib/fastnum/fastnum.go
Normal file
144
lib/fastnum/fastnum.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package fastnum
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// AppendInt64Zeros appends items zeros to dst and returns the result.
|
||||
//
|
||||
// It is faster than the corresponding loop.
|
||||
func AppendInt64Zeros(dst []int64, items int) []int64 {
|
||||
return appendInt64Data(dst, items, int64Zeros[:])
|
||||
}
|
||||
|
||||
// AppendInt64Ones appends items ones to dst and returns the result.
|
||||
//
|
||||
// It is faster than the corresponding loop.
|
||||
func AppendInt64Ones(dst []int64, items int) []int64 {
|
||||
return appendInt64Data(dst, items, int64Ones[:])
|
||||
}
|
||||
|
||||
// AppendFloat64Zeros appends items zeros to dst and returns the result.
|
||||
//
|
||||
// It is faster than the corresponding loop.
|
||||
func AppendFloat64Zeros(dst []float64, items int) []float64 {
|
||||
return appendFloat64Data(dst, items, float64Zeros[:])
|
||||
}
|
||||
|
||||
// AppendFloat64Ones appends items ones to dst and returns the result.
|
||||
//
|
||||
// It is faster than the corresponding loop.
|
||||
func AppendFloat64Ones(dst []float64, items int) []float64 {
|
||||
return appendFloat64Data(dst, items, float64Ones[:])
|
||||
}
|
||||
|
||||
// IsInt64Zeros checks whether a contains only zeros.
|
||||
func IsInt64Zeros(a []int64) bool {
|
||||
return isInt64Data(a, int64Zeros[:])
|
||||
}
|
||||
|
||||
// IsInt64Ones checks whether a contains only ones.
|
||||
func IsInt64Ones(a []int64) bool {
|
||||
return isInt64Data(a, int64Ones[:])
|
||||
}
|
||||
|
||||
// IsFloat64Zeros checks whether a contains only zeros.
|
||||
func IsFloat64Zeros(a []float64) bool {
|
||||
return isFloat64Data(a, float64Zeros[:])
|
||||
}
|
||||
|
||||
// IsFloat64Ones checks whether a contains only ones.
|
||||
func IsFloat64Ones(a []float64) bool {
|
||||
return isFloat64Data(a, float64Ones[:])
|
||||
}
|
||||
|
||||
func appendInt64Data(dst []int64, items int, src []int64) []int64 {
|
||||
for items > 0 {
|
||||
n := len(src)
|
||||
if n > items {
|
||||
n = items
|
||||
}
|
||||
dst = append(dst, src[:n]...)
|
||||
items -= n
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func appendFloat64Data(dst []float64, items int, src []float64) []float64 {
|
||||
for items > 0 {
|
||||
n := len(src)
|
||||
if n > items {
|
||||
n = items
|
||||
}
|
||||
dst = append(dst, src[:n]...)
|
||||
items -= n
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func isInt64Data(a, data []int64) bool {
|
||||
if len(a) == 0 {
|
||||
return true
|
||||
}
|
||||
if len(data) != 8*1024 {
|
||||
panic("len(data) must equal to 8*1024")
|
||||
}
|
||||
b := (*[64 * 1024]byte)(unsafe.Pointer(&data[0]))
|
||||
for len(a) > 0 {
|
||||
n := len(data)
|
||||
if n > len(a) {
|
||||
n = len(a)
|
||||
}
|
||||
x := a[:n]
|
||||
a = a[n:]
|
||||
xb := (*[64 * 1024]byte)(unsafe.Pointer(&x[0]))
|
||||
xbLen := len(x) * 8
|
||||
if !bytes.Equal(xb[:xbLen], b[:xbLen]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isFloat64Data(a, data []float64) bool {
|
||||
if len(a) == 0 {
|
||||
return true
|
||||
}
|
||||
if len(data) != 8*1024 {
|
||||
panic("len(data) must equal to 8*1024")
|
||||
}
|
||||
b := (*[64 * 1024]byte)(unsafe.Pointer(&data[0]))
|
||||
for len(a) > 0 {
|
||||
n := len(data)
|
||||
if n > len(a) {
|
||||
n = len(a)
|
||||
}
|
||||
x := a[:n]
|
||||
a = a[n:]
|
||||
xb := (*[64 * 1024]byte)(unsafe.Pointer(&x[0]))
|
||||
xbLen := len(x) * 8
|
||||
if !bytes.Equal(xb[:xbLen], b[:xbLen]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var (
|
||||
int64Zeros [8 * 1024]int64
|
||||
int64Ones = func() (a [8 * 1024]int64) {
|
||||
for i := 0; i < len(a); i++ {
|
||||
a[i] = 1
|
||||
}
|
||||
return a
|
||||
}()
|
||||
|
||||
float64Zeros [8 * 1024]float64
|
||||
float64Ones = func() (a [8 * 1024]float64) {
|
||||
for i := 0; i < len(a); i++ {
|
||||
a[i] = 1
|
||||
}
|
||||
return a
|
||||
}()
|
||||
)
|
||||
192
lib/fastnum/fastnum_test.go
Normal file
192
lib/fastnum/fastnum_test.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package fastnum
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsInt64Zeros(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := make([]int64, n)
|
||||
if !IsInt64Zeros(a) {
|
||||
t.Fatalf("IsInt64Zeros must return true")
|
||||
}
|
||||
if len(a) > 0 {
|
||||
a[len(a)-1] = 1
|
||||
if IsInt64Zeros(a) {
|
||||
t.Fatalf("IsInt64Zeros must return false")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInt64Ones(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := make([]int64, n)
|
||||
for i := 0; i < n; i++ {
|
||||
a[i] = 1
|
||||
}
|
||||
if !IsInt64Ones(a) {
|
||||
t.Fatalf("IsInt64Ones must return true")
|
||||
}
|
||||
if len(a) > 0 {
|
||||
a[len(a)-1] = 0
|
||||
if IsInt64Ones(a) {
|
||||
t.Fatalf("IsInt64Ones must return false")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsFloat64Zeros(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := make([]float64, n)
|
||||
if !IsFloat64Zeros(a) {
|
||||
t.Fatalf("IsInt64Zeros must return true")
|
||||
}
|
||||
if len(a) > 0 {
|
||||
a[len(a)-1] = 1
|
||||
if IsFloat64Zeros(a) {
|
||||
t.Fatalf("IsInt64Zeros must return false")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsFloat64Ones(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := make([]float64, n)
|
||||
for i := 0; i < n; i++ {
|
||||
a[i] = 1
|
||||
}
|
||||
if !IsFloat64Ones(a) {
|
||||
t.Fatalf("IsInt64Ones must return true")
|
||||
}
|
||||
if len(a) > 0 {
|
||||
a[len(a)-1] = 0
|
||||
if IsFloat64Ones(a) {
|
||||
t.Fatalf("IsInt64Ones must return false")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendInt64Zeros(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := AppendInt64Zeros(nil, n)
|
||||
if len(a) != n {
|
||||
t.Fatalf("unexpected len(a); got %d; want %d", len(a), n)
|
||||
}
|
||||
if !IsInt64Zeros(a) {
|
||||
t.Fatalf("IsInt64Zeros must return true")
|
||||
}
|
||||
|
||||
prefix := []int64{1, 2, 3}
|
||||
a = AppendInt64Zeros(prefix, n)
|
||||
if len(a) != len(prefix)+n {
|
||||
t.Fatalf("unexpected len(a) with prefix; got %d; want %d", len(a), len(prefix)+n)
|
||||
}
|
||||
for i := 0; i < len(prefix); i++ {
|
||||
if a[i] != prefix[i] {
|
||||
t.Fatalf("unexpected prefix[%d]; got %d; want %d", i, a[i], prefix[i])
|
||||
}
|
||||
}
|
||||
if !IsInt64Zeros(a[len(prefix):]) {
|
||||
t.Fatalf("IsInt64Zeros for prefixed a must return true")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendInt64Ones(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := AppendInt64Ones(nil, n)
|
||||
if len(a) != n {
|
||||
t.Fatalf("unexpected len(a); got %d; want %d", len(a), n)
|
||||
}
|
||||
if !IsInt64Ones(a) {
|
||||
t.Fatalf("IsInt64Ones must return true")
|
||||
}
|
||||
|
||||
prefix := []int64{1, 2, 3}
|
||||
a = AppendInt64Ones(prefix, n)
|
||||
if len(a) != len(prefix)+n {
|
||||
t.Fatalf("unexpected len(a) with prefix; got %d; want %d", len(a), len(prefix)+n)
|
||||
}
|
||||
for i := 0; i < len(prefix); i++ {
|
||||
if a[i] != prefix[i] {
|
||||
t.Fatalf("unexpected prefix[%d]; got %d; want %d", i, a[i], prefix[i])
|
||||
}
|
||||
}
|
||||
if !IsInt64Ones(a[len(prefix):]) {
|
||||
t.Fatalf("IsInt64Ones for prefixed a must return true")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendFloat64Zeros(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := AppendFloat64Zeros(nil, n)
|
||||
if len(a) != n {
|
||||
t.Fatalf("unexpected len(a); got %d; want %d", len(a), n)
|
||||
}
|
||||
if !IsFloat64Zeros(a) {
|
||||
t.Fatalf("IsFloat64Zeros must return true")
|
||||
}
|
||||
|
||||
prefix := []float64{1, 2, 3}
|
||||
a = AppendFloat64Zeros(prefix, n)
|
||||
if len(a) != len(prefix)+n {
|
||||
t.Fatalf("unexpected len(a) with prefix; got %d; want %d", len(a), len(prefix)+n)
|
||||
}
|
||||
for i := 0; i < len(prefix); i++ {
|
||||
if a[i] != prefix[i] {
|
||||
t.Fatalf("unexpected prefix[%d]; got %f; want %f", i, a[i], prefix[i])
|
||||
}
|
||||
}
|
||||
if !IsFloat64Zeros(a[len(prefix):]) {
|
||||
t.Fatalf("IsFloat64Zeros for prefixed a must return true")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendFloat64Ones(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := AppendFloat64Ones(nil, n)
|
||||
if len(a) != n {
|
||||
t.Fatalf("unexpected len(a); got %d; want %d", len(a), n)
|
||||
}
|
||||
if !IsFloat64Ones(a) {
|
||||
t.Fatalf("IsFloat64Ones must return true")
|
||||
}
|
||||
|
||||
prefix := []float64{1, 2, 3}
|
||||
a = AppendFloat64Ones(prefix, n)
|
||||
if len(a) != len(prefix)+n {
|
||||
t.Fatalf("unexpected len(a) with prefix; got %d; want %d", len(a), len(prefix)+n)
|
||||
}
|
||||
for i := 0; i < len(prefix); i++ {
|
||||
if a[i] != prefix[i] {
|
||||
t.Fatalf("unexpected prefix[%d]; got %f; want %f", i, a[i], prefix[i])
|
||||
}
|
||||
}
|
||||
if !IsFloat64Ones(a[len(prefix):]) {
|
||||
t.Fatalf("IsFloat64Ones for prefixed a must return true")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package filestream
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
@@ -51,6 +52,26 @@ type Reader struct {
|
||||
st streamTracker
|
||||
}
|
||||
|
||||
// OpenReaderAt opens the file at the given path in nocache mode at the given offset.
|
||||
//
|
||||
// If nocache is set, then the reader doesn't pollute OS page cache.
|
||||
func OpenReaderAt(path string, offset int64, nocache bool) (*Reader, error) {
|
||||
r, err := Open(path, nocache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n, err := r.f.Seek(offset, io.SeekStart)
|
||||
if err != nil {
|
||||
r.MustClose()
|
||||
return nil, fmt.Errorf("cannot seek to offset=%d for %q: %s", offset, path, err)
|
||||
}
|
||||
if n != offset {
|
||||
r.MustClose()
|
||||
return nil, fmt.Errorf("invalid seek offset for %q; got %d; want %d", path, n, offset)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Open opens the file from the given path in nocache mode.
|
||||
//
|
||||
// If nocache is set, then the reader doesn't pollute OS page cache.
|
||||
@@ -143,6 +164,28 @@ type Writer struct {
|
||||
st streamTracker
|
||||
}
|
||||
|
||||
// OpenWriterAt opens the file at path in nocache mode for writing at the given offset.
|
||||
//
|
||||
// The file at path is created if it is missing.
|
||||
//
|
||||
// If nocache is set, the writer doesn't pollute OS page cache.
|
||||
func OpenWriterAt(path string, offset int64, nocache bool) (*Writer, error) {
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open %q: %s", path, err)
|
||||
}
|
||||
n, err := f.Seek(offset, io.SeekStart)
|
||||
if err != nil {
|
||||
_ = f.Close()
|
||||
return nil, fmt.Errorf("cannot seek to offset=%d in %q: %s", offset, path, err)
|
||||
}
|
||||
if n != offset {
|
||||
_ = f.Close()
|
||||
return nil, fmt.Errorf("invalid seek offset for %q; got %d; want %d", path, n, offset)
|
||||
}
|
||||
return newWriter(f, nocache), nil
|
||||
}
|
||||
|
||||
// Create creates the file for the given path in nocache mode.
|
||||
//
|
||||
// If nocache is set, the writer doesn't pollute OS page cache.
|
||||
@@ -151,6 +194,10 @@ func Create(path string, nocache bool) (*Writer, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create file %q: %s", path, err)
|
||||
}
|
||||
return newWriter(f, nocache), nil
|
||||
}
|
||||
|
||||
func newWriter(f *os.File, nocache bool) *Writer {
|
||||
w := &Writer{
|
||||
f: f,
|
||||
bw: getBufioWriter(f),
|
||||
@@ -159,7 +206,7 @@ func Create(path string, nocache bool) (*Writer, error) {
|
||||
w.st.fd = f.Fd()
|
||||
}
|
||||
writersCount.Inc()
|
||||
return w, nil
|
||||
return w
|
||||
}
|
||||
|
||||
// MustClose syncs the underlying file to storage and then closes it.
|
||||
|
||||
@@ -28,7 +28,7 @@ var (
|
||||
tlsKeyFile = flag.String("tlsKeyFile", "", "Path to file with TLS key. Used only if tls=true")
|
||||
|
||||
httpAuthUsername = flag.String("httpAuth.username", "", "Username for HTTP Basic Auth. The authentication is disabled if empty. See also -httpAuth.password")
|
||||
httpAuthPassword = flag.String("httpAuth.password", "", "Password for HTTP Basic Auth. The authentication is disabled -httpAuth.username is empty")
|
||||
httpAuthPassword = flag.String("httpAuth.password", "", "Password for HTTP Basic Auth. The authentication is disabled if -httpAuth.username is empty")
|
||||
metricsAuthKey = flag.String("metricsAuthKey", "", "Auth key for /metrics. It overrides httpAuth settings")
|
||||
pprofAuthKey = flag.String("pprofAuthKey", "", "Auth key for /debug/pprof. It overrides httpAuth settings")
|
||||
|
||||
|
||||
@@ -911,7 +911,10 @@ var mergeWorkersCount = func() int {
|
||||
}()
|
||||
|
||||
func openParts(path string) ([]*partWrapper, error) {
|
||||
// Verify that the directory for the parts exists.
|
||||
// The path can be missing after restoring from backup, so create it if needed.
|
||||
if err := fs.MkdirAllIfNotExist(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open difrectory: %s", err)
|
||||
|
||||
@@ -25,9 +25,13 @@ type blockStreamReader struct {
|
||||
|
||||
ph partHeader
|
||||
|
||||
timestampsReader filestream.ReadCloser
|
||||
valuesReader filestream.ReadCloser
|
||||
indexReader filestream.ReadCloser
|
||||
// Use io.Reader type for timestampsReader and valuesReader
|
||||
// in order to remove I2I conversion in readBlock
|
||||
// when passing them to fs.ReadFullData
|
||||
timestampsReader io.Reader
|
||||
valuesReader io.Reader
|
||||
|
||||
indexReader filestream.ReadCloser
|
||||
|
||||
mrs []metaindexRow
|
||||
|
||||
@@ -56,6 +60,11 @@ type blockStreamReader struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (bsr *blockStreamReader) assertWriteClosers() {
|
||||
_ = bsr.timestampsReader.(filestream.ReadCloser)
|
||||
_ = bsr.valuesReader.(filestream.ReadCloser)
|
||||
}
|
||||
|
||||
func (bsr *blockStreamReader) reset() {
|
||||
bsr.Block.Reset()
|
||||
|
||||
@@ -108,6 +117,8 @@ func (bsr *blockStreamReader) InitFromInmemoryPart(mp *inmemoryPart) {
|
||||
if err != nil {
|
||||
logger.Panicf("BUG: cannot unmarshal metaindex rows from inmemoryPart: %s", err)
|
||||
}
|
||||
|
||||
bsr.assertWriteClosers()
|
||||
}
|
||||
|
||||
// InitFromFilePart initializes bsr from a file-based part on the given path.
|
||||
@@ -167,6 +178,8 @@ func (bsr *blockStreamReader) InitFromFilePart(path string) error {
|
||||
bsr.indexReader = indexFile
|
||||
bsr.mrs = mrs
|
||||
|
||||
bsr.assertWriteClosers()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -174,8 +187,8 @@ func (bsr *blockStreamReader) InitFromFilePart(path string) error {
|
||||
//
|
||||
// It closes *Reader files passed to Init.
|
||||
func (bsr *blockStreamReader) MustClose() {
|
||||
bsr.timestampsReader.MustClose()
|
||||
bsr.valuesReader.MustClose()
|
||||
bsr.timestampsReader.(filestream.ReadCloser).MustClose()
|
||||
bsr.valuesReader.(filestream.ReadCloser).MustClose()
|
||||
bsr.indexReader.MustClose()
|
||||
|
||||
bsr.reset()
|
||||
|
||||
@@ -2,6 +2,7 @@ package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -17,10 +18,14 @@ type blockStreamWriter struct {
|
||||
compressLevel int
|
||||
path string
|
||||
|
||||
timestampsWriter filestream.WriteCloser
|
||||
valuesWriter filestream.WriteCloser
|
||||
indexWriter filestream.WriteCloser
|
||||
metaindexWriter filestream.WriteCloser
|
||||
// Use io.Writer type for timestampsWriter and valuesWriter
|
||||
// in order to remove I2I conversion in WriteExternalBlock
|
||||
// when passing them to fs.MustWriteData
|
||||
timestampsWriter io.Writer
|
||||
valuesWriter io.Writer
|
||||
|
||||
indexWriter filestream.WriteCloser
|
||||
metaindexWriter filestream.WriteCloser
|
||||
|
||||
mr metaindexRow
|
||||
|
||||
@@ -35,6 +40,11 @@ type blockStreamWriter struct {
|
||||
compressedMetaindexData []byte
|
||||
}
|
||||
|
||||
func (bsw *blockStreamWriter) assertWriteClosers() {
|
||||
_ = bsw.timestampsWriter.(filestream.WriteCloser)
|
||||
_ = bsw.valuesWriter.(filestream.WriteCloser)
|
||||
}
|
||||
|
||||
// Init initializes bsw with the given writers.
|
||||
func (bsw *blockStreamWriter) reset() {
|
||||
bsw.compressLevel = 0
|
||||
@@ -67,6 +77,8 @@ func (bsw *blockStreamWriter) InitFromInmemoryPart(mp *inmemoryPart) {
|
||||
bsw.valuesWriter = &mp.valuesData
|
||||
bsw.indexWriter = &mp.indexData
|
||||
bsw.metaindexWriter = &mp.metaindexData
|
||||
|
||||
bsw.assertWriteClosers()
|
||||
}
|
||||
|
||||
// InitFromFilePart initializes bsw from a file-based part on the given path.
|
||||
@@ -126,6 +138,8 @@ func (bsw *blockStreamWriter) InitFromFilePart(path string, nocache bool, compre
|
||||
bsw.indexWriter = indexFile
|
||||
bsw.metaindexWriter = metaindexFile
|
||||
|
||||
bsw.assertWriteClosers()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -141,8 +155,8 @@ func (bsw *blockStreamWriter) MustClose() {
|
||||
fs.MustWriteData(bsw.metaindexWriter, bsw.compressedMetaindexData)
|
||||
|
||||
// Close writers.
|
||||
bsw.timestampsWriter.MustClose()
|
||||
bsw.valuesWriter.MustClose()
|
||||
bsw.timestampsWriter.(filestream.WriteCloser).MustClose()
|
||||
bsw.valuesWriter.(filestream.WriteCloser).MustClose()
|
||||
bsw.indexWriter.MustClose()
|
||||
bsw.metaindexWriter.MustClose()
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/uint64set"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/workingsetcache"
|
||||
)
|
||||
|
||||
@@ -25,15 +26,15 @@ func TestMergeTagToMetricIDsRows(t *testing.T) {
|
||||
data = append(data, item...)
|
||||
itemsB = append(itemsB, data[len(data)-len(item):])
|
||||
}
|
||||
if err := checkItemsSorted(itemsB); err != nil {
|
||||
t.Fatalf("source items aren't sorted: %s", err)
|
||||
if !checkItemsSorted(itemsB) {
|
||||
t.Fatalf("source items aren't sorted; items:\n%q", itemsB)
|
||||
}
|
||||
resultData, resultItemsB := mergeTagToMetricIDsRows(data, itemsB)
|
||||
if len(resultItemsB) != len(expectedItems) {
|
||||
t.Fatalf("unexpected len(resultItemsB); got %d; want %d", len(resultItemsB), len(expectedItems))
|
||||
}
|
||||
if err := checkItemsSorted(resultItemsB); err != nil {
|
||||
t.Fatalf("result items aren't sorted: %s", err)
|
||||
if !checkItemsSorted(resultItemsB) {
|
||||
t.Fatalf("result items aren't sorted; items:\n%q", resultItemsB)
|
||||
}
|
||||
for i, item := range resultItemsB {
|
||||
if !bytes.HasPrefix(resultData, item) {
|
||||
@@ -52,8 +53,11 @@ func TestMergeTagToMetricIDsRows(t *testing.T) {
|
||||
t.Fatalf("unexpected items;\ngot\n%X\nwant\n%X", resultItems, expectedItems)
|
||||
}
|
||||
}
|
||||
x := func(key, value string, metricIDs []uint64) string {
|
||||
dst := marshalCommonPrefix(nil, nsPrefixTagToMetricIDs)
|
||||
xy := func(nsPrefix byte, key, value string, metricIDs []uint64) string {
|
||||
dst := marshalCommonPrefix(nil, nsPrefix)
|
||||
if nsPrefix == nsPrefixDateTagToMetricIDs {
|
||||
dst = encoding.MarshalUint64(dst, 1234567901233)
|
||||
}
|
||||
t := &Tag{
|
||||
Key: []byte(key),
|
||||
Value: []byte(value),
|
||||
@@ -64,6 +68,12 @@ func TestMergeTagToMetricIDsRows(t *testing.T) {
|
||||
}
|
||||
return string(dst)
|
||||
}
|
||||
x := func(key, value string, metricIDs []uint64) string {
|
||||
return xy(nsPrefixTagToMetricIDs, key, value, metricIDs)
|
||||
}
|
||||
y := func(key, value string, metricIDs []uint64) string {
|
||||
return xy(nsPrefixDateTagToMetricIDs, key, value, metricIDs)
|
||||
}
|
||||
|
||||
f(nil, nil)
|
||||
f([]string{}, nil)
|
||||
@@ -80,6 +90,19 @@ func TestMergeTagToMetricIDsRows(t *testing.T) {
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
})
|
||||
f([]string{
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
y("", "", []uint64{0}),
|
||||
y("", "", []uint64{0}),
|
||||
y("", "", []uint64{0}),
|
||||
}, []string{
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
y("", "", []uint64{0}),
|
||||
y("", "", []uint64{0}),
|
||||
})
|
||||
f([]string{
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
@@ -102,6 +125,17 @@ func TestMergeTagToMetricIDsRows(t *testing.T) {
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
})
|
||||
f([]string{
|
||||
"\x00asdf",
|
||||
y("", "", []uint64{0}),
|
||||
y("", "", []uint64{0}),
|
||||
y("", "", []uint64{0}),
|
||||
y("", "", []uint64{0}),
|
||||
}, []string{
|
||||
"\x00asdf",
|
||||
y("", "", []uint64{0}),
|
||||
y("", "", []uint64{0}),
|
||||
})
|
||||
f([]string{
|
||||
"\x00asdf",
|
||||
x("", "", []uint64{0}),
|
||||
@@ -114,6 +148,19 @@ func TestMergeTagToMetricIDsRows(t *testing.T) {
|
||||
x("", "", []uint64{0}),
|
||||
"xyz",
|
||||
})
|
||||
f([]string{
|
||||
"\x00asdf",
|
||||
x("", "", []uint64{0}),
|
||||
x("", "", []uint64{0}),
|
||||
y("", "", []uint64{0}),
|
||||
y("", "", []uint64{0}),
|
||||
"xyz",
|
||||
}, []string{
|
||||
"\x00asdf",
|
||||
x("", "", []uint64{0}),
|
||||
y("", "", []uint64{0}),
|
||||
"xyz",
|
||||
})
|
||||
f([]string{
|
||||
"\x00asdf",
|
||||
x("", "", []uint64{1}),
|
||||
@@ -210,10 +257,13 @@ func TestMergeTagToMetricIDsRows(t *testing.T) {
|
||||
"\x00aa",
|
||||
x("foo", "bar", metricIDs),
|
||||
x("foo", "bar", metricIDs),
|
||||
y("foo", "bar", metricIDs),
|
||||
y("foo", "bar", metricIDs),
|
||||
"x",
|
||||
}, []string{
|
||||
"\x00aa",
|
||||
x("foo", "bar", metricIDs),
|
||||
y("foo", "bar", metricIDs),
|
||||
"x",
|
||||
})
|
||||
|
||||
@@ -271,10 +321,12 @@ func TestMergeTagToMetricIDsRows(t *testing.T) {
|
||||
"\x00aa",
|
||||
x("foo", "bar", metricIDs),
|
||||
x("foo", "bar", metricIDs),
|
||||
y("foo", "bar", metricIDs),
|
||||
"x",
|
||||
}, []string{
|
||||
"\x00aa",
|
||||
x("foo", "bar", []uint64{123}),
|
||||
y("foo", "bar", []uint64{123}),
|
||||
"x",
|
||||
})
|
||||
|
||||
@@ -301,11 +353,13 @@ func TestMergeTagToMetricIDsRows(t *testing.T) {
|
||||
x("foo", "bar", metricIDs),
|
||||
x("foo", "bar", []uint64{123, 123, 125}),
|
||||
x("foo", "bar", []uint64{123, 124}),
|
||||
y("foo", "bar", []uint64{123, 124}),
|
||||
}, []string{
|
||||
"\x00aa",
|
||||
x("foo", "bar", metricIDs),
|
||||
x("foo", "bar", []uint64{123, 123, 125}),
|
||||
x("foo", "bar", []uint64{123, 124}),
|
||||
y("foo", "bar", []uint64{123, 124}),
|
||||
})
|
||||
f([]string{
|
||||
x("foo", "bar", metricIDs),
|
||||
@@ -1277,6 +1331,148 @@ func TestMatchTagFilters(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchTSIDWithTimeRange(t *testing.T) {
|
||||
metricIDCache := workingsetcache.New(1234, time.Hour)
|
||||
metricNameCache := workingsetcache.New(1234, time.Hour)
|
||||
defer metricIDCache.Stop()
|
||||
defer metricNameCache.Stop()
|
||||
|
||||
currMetricIDs := &hourMetricIDs{
|
||||
isFull: true,
|
||||
m: &uint64set.Set{},
|
||||
}
|
||||
|
||||
var hmCurr atomic.Value
|
||||
hmCurr.Store(currMetricIDs)
|
||||
|
||||
prevMetricIDs := &hourMetricIDs{
|
||||
isFull: true,
|
||||
m: &uint64set.Set{},
|
||||
}
|
||||
var hmPrev atomic.Value
|
||||
hmPrev.Store(prevMetricIDs)
|
||||
|
||||
dbName := "test-index-db-ts-range"
|
||||
db, err := openIndexDB(dbName, metricIDCache, metricNameCache, &hmCurr, &hmPrev)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open indexDB: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
db.MustClose()
|
||||
if err := os.RemoveAll(dbName); err != nil {
|
||||
t.Fatalf("cannot remove indexDB: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
is := db.getIndexSearch()
|
||||
defer db.putIndexSearch(is)
|
||||
|
||||
// Create a bunch of per-day time series
|
||||
const days = 5
|
||||
const metricsPerDay = 1000
|
||||
theDay := time.Date(2019, time.October, 15, 5, 1, 0, 0, time.UTC)
|
||||
now := uint64(timestampFromTime(theDay))
|
||||
currMetricIDs.hour = now / msecPerHour
|
||||
prevMetricIDs.hour = (now - msecPerHour) / msecPerHour
|
||||
baseDate := now / msecPerDay
|
||||
var metricNameBuf []byte
|
||||
for day := 0; day < days; day++ {
|
||||
var tsids []TSID
|
||||
for metric := 0; metric < metricsPerDay; metric++ {
|
||||
var mn MetricName
|
||||
mn.MetricGroup = []byte("testMetric")
|
||||
mn.AddTag(
|
||||
"constant",
|
||||
"const",
|
||||
)
|
||||
mn.AddTag(
|
||||
"day",
|
||||
fmt.Sprintf("%v", day),
|
||||
)
|
||||
mn.AddTag(
|
||||
"uniqueid",
|
||||
fmt.Sprintf("%v", metric),
|
||||
)
|
||||
mn.sortTags()
|
||||
|
||||
metricNameBuf = mn.Marshal(metricNameBuf[:0])
|
||||
var tsid TSID
|
||||
if err := is.GetOrCreateTSIDByName(&tsid, metricNameBuf); err != nil {
|
||||
t.Fatalf("unexpected error when creating tsid for mn:\n%s: %s", &mn, err)
|
||||
}
|
||||
tsids = append(tsids, tsid)
|
||||
}
|
||||
|
||||
// Add the metrics to the per-day stores
|
||||
date := baseDate - uint64(day*msecPerDay)
|
||||
for i := range tsids {
|
||||
tsid := &tsids[i]
|
||||
if err := db.storeDateMetricID(date, tsid.MetricID); err != nil {
|
||||
t.Fatalf("error in storeDateMetricID(%d, %d): %s", date, tsid.MetricID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Add the the hour metrics caches
|
||||
if day == 0 {
|
||||
for i := 0; i < 256; i++ {
|
||||
prevMetricIDs.m.Add(tsids[i].MetricID)
|
||||
currMetricIDs.m.Add(tsids[i].MetricID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Flush index to disk, so it becomes visible for search
|
||||
db.tb.DebugFlush()
|
||||
|
||||
// Create a filter that will match series that occur across multiple days
|
||||
tfs := NewTagFilters()
|
||||
if err := tfs.Add([]byte("constant"), []byte("const"), false, false); err != nil {
|
||||
t.Fatalf("cannot add filter: %s", err)
|
||||
}
|
||||
|
||||
// Perform a search that can be fulfilled out of the hour metrics cache.
|
||||
// This should return the metrics in the hourly cache
|
||||
tr := TimeRange{
|
||||
MinTimestamp: int64(now - msecPerHour + 1),
|
||||
MaxTimestamp: int64(now),
|
||||
}
|
||||
matchedTSIDs, err := db.searchTSIDs([]*TagFilters{tfs}, tr, 10000)
|
||||
if err != nil {
|
||||
t.Fatalf("error searching tsids: %v", err)
|
||||
}
|
||||
if len(matchedTSIDs) != 256 {
|
||||
t.Fatal("Expected time series for current hour, got", len(matchedTSIDs))
|
||||
}
|
||||
|
||||
// Perform a search within a day that falls out out of the hour metrics cache.
|
||||
// This should return the metrics for the day
|
||||
tr = TimeRange{
|
||||
MinTimestamp: int64(now - 2*msecPerHour - 1),
|
||||
MaxTimestamp: int64(now),
|
||||
}
|
||||
matchedTSIDs, err = db.searchTSIDs([]*TagFilters{tfs}, tr, 10000)
|
||||
if err != nil {
|
||||
t.Fatalf("error searching tsids: %v", err)
|
||||
}
|
||||
if len(matchedTSIDs) != metricsPerDay {
|
||||
t.Fatal("Expected time series for current day, got", len(matchedTSIDs))
|
||||
}
|
||||
|
||||
// Perform a search across all the days, should match all metrics
|
||||
tr = TimeRange{
|
||||
MinTimestamp: int64(now - msecPerDay*days),
|
||||
MaxTimestamp: int64(now),
|
||||
}
|
||||
|
||||
matchedTSIDs, err = db.searchTSIDs([]*TagFilters{tfs}, tr, 10000)
|
||||
if err != nil {
|
||||
t.Fatalf("error searching tsids: %v", err)
|
||||
}
|
||||
if len(matchedTSIDs) != metricsPerDay*days {
|
||||
t.Fatal("Expected time series for all days, got", len(matchedTSIDs))
|
||||
}
|
||||
}
|
||||
|
||||
func toTFPointers(tfs []tagFilter) []*tagFilter {
|
||||
tfps := make([]*tagFilter, len(tfs))
|
||||
for i := range tfs {
|
||||
|
||||
@@ -108,6 +108,190 @@ func benchmarkIndexDBAddTSIDs(db *indexDB, tsid *TSID, mn *MetricName, startOffs
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHeadPostingForMatchers(b *testing.B) {
|
||||
// This benchmark is equivalent to https://github.com/prometheus/prometheus/blob/23c0299d85bfeb5d9b59e994861553a25ca578e5/tsdb/head_bench_test.go#L52
|
||||
// See https://www.robustperception.io/evaluating-performance-and-correctness for more details.
|
||||
metricIDCache := workingsetcache.New(1234, time.Hour)
|
||||
metricNameCache := workingsetcache.New(1234, time.Hour)
|
||||
defer metricIDCache.Stop()
|
||||
defer metricNameCache.Stop()
|
||||
|
||||
var hmCurr atomic.Value
|
||||
hmCurr.Store(&hourMetricIDs{})
|
||||
var hmPrev atomic.Value
|
||||
hmPrev.Store(&hourMetricIDs{})
|
||||
|
||||
const dbName = "bench-head-posting-for-matchers"
|
||||
db, err := openIndexDB(dbName, metricIDCache, metricNameCache, &hmCurr, &hmPrev)
|
||||
if err != nil {
|
||||
b.Fatalf("cannot open indexDB: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
db.MustClose()
|
||||
if err := os.RemoveAll(dbName); err != nil {
|
||||
b.Fatalf("cannot remove indexDB: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Fill the db with data as in https://github.com/prometheus/prometheus/blob/23c0299d85bfeb5d9b59e994861553a25ca578e5/tsdb/head_bench_test.go#L66
|
||||
var mn MetricName
|
||||
var metricName []byte
|
||||
var tsid TSID
|
||||
addSeries := func(kvs ...string) {
|
||||
mn.Reset()
|
||||
for i := 0; i < len(kvs); i += 2 {
|
||||
mn.AddTag(kvs[i], kvs[i+1])
|
||||
}
|
||||
mn.sortTags()
|
||||
metricName = mn.Marshal(metricName[:0])
|
||||
if err := db.createTSIDByName(&tsid, metricName); err != nil {
|
||||
b.Fatalf("cannot insert record: %s", err)
|
||||
}
|
||||
}
|
||||
for n := 0; n < 10; n++ {
|
||||
ns := strconv.Itoa(n)
|
||||
for i := 0; i < 100000; i++ {
|
||||
is := strconv.Itoa(i)
|
||||
addSeries("i", is, "n", ns, "j", "foo")
|
||||
// Have some series that won't be matched, to properly test inverted matches.
|
||||
addSeries("i", is, "n", ns, "j", "bar")
|
||||
addSeries("i", is, "n", "0_"+ns, "j", "bar")
|
||||
addSeries("i", is, "n", "1_"+ns, "j", "bar")
|
||||
addSeries("i", is, "n", "2_"+ns, "j", "foo")
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure all the items can be searched.
|
||||
db.tb.DebugFlush()
|
||||
b.ResetTimer()
|
||||
|
||||
benchSearch := func(b *testing.B, tfs *TagFilters, expectedMetricIDs int) {
|
||||
is := db.getIndexSearch()
|
||||
defer db.putIndexSearch(is)
|
||||
tfss := []*TagFilters{tfs}
|
||||
tr := TimeRange{
|
||||
MinTimestamp: 0,
|
||||
MaxTimestamp: timestampFromTime(time.Now()),
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
metricIDs, err := is.searchMetricIDs(tfss, tr, 2e9)
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error in searchMetricIDs: %s", err)
|
||||
}
|
||||
if len(metricIDs) != expectedMetricIDs {
|
||||
b.Fatalf("unexpected metricIDs found; got %d; want %d", len(metricIDs), expectedMetricIDs)
|
||||
}
|
||||
}
|
||||
}
|
||||
addTagFilter := func(tfs *TagFilters, key, value string, isNegative, isRegexp bool) {
|
||||
if err := tfs.Add([]byte(key), []byte(value), isNegative, isRegexp); err != nil {
|
||||
b.Fatalf("cannot add tag filter %q=%q, isNegative=%v, isRegexp=%v", key, value, isNegative, isRegexp)
|
||||
}
|
||||
}
|
||||
|
||||
b.Run(`n="1"`, func(b *testing.B) {
|
||||
tfs := NewTagFilters()
|
||||
addTagFilter(tfs, "n", "1", false, false)
|
||||
benchSearch(b, tfs, 2e5)
|
||||
})
|
||||
b.Run(`n="1",j="foo"`, func(b *testing.B) {
|
||||
tfs := NewTagFilters()
|
||||
addTagFilter(tfs, "n", "1", false, false)
|
||||
addTagFilter(tfs, "j", "foo", false, false)
|
||||
benchSearch(b, tfs, 1e5)
|
||||
})
|
||||
b.Run(`j="foo",n="1"`, func(b *testing.B) {
|
||||
tfs := NewTagFilters()
|
||||
addTagFilter(tfs, "j", "foo", false, false)
|
||||
addTagFilter(tfs, "n", "1", false, false)
|
||||
benchSearch(b, tfs, 1e5)
|
||||
})
|
||||
b.Run(`n="1",j!="foo"`, func(b *testing.B) {
|
||||
tfs := NewTagFilters()
|
||||
addTagFilter(tfs, "n", "1", false, false)
|
||||
addTagFilter(tfs, "j", "foo", true, false)
|
||||
benchSearch(b, tfs, 1e5)
|
||||
})
|
||||
b.Run(`i=~".*"`, func(b *testing.B) {
|
||||
tfs := NewTagFilters()
|
||||
addTagFilter(tfs, "i", ".*", false, true)
|
||||
benchSearch(b, tfs, 5e6)
|
||||
})
|
||||
b.Run(`i=~".+"`, func(b *testing.B) {
|
||||
tfs := NewTagFilters()
|
||||
addTagFilter(tfs, "i", ".+", false, true)
|
||||
benchSearch(b, tfs, 5e6)
|
||||
})
|
||||
b.Run(`i=~""`, func(b *testing.B) {
|
||||
tfs := NewTagFilters()
|
||||
addTagFilter(tfs, "i", "", false, true)
|
||||
benchSearch(b, tfs, 0)
|
||||
})
|
||||
b.Run(`i!=""`, func(b *testing.B) {
|
||||
tfs := NewTagFilters()
|
||||
addTagFilter(tfs, "i", "", true, false)
|
||||
benchSearch(b, tfs, 5e6)
|
||||
})
|
||||
b.Run(`n="1",i=~".*",j="foo"`, func(b *testing.B) {
|
||||
tfs := NewTagFilters()
|
||||
addTagFilter(tfs, "n", "1", false, false)
|
||||
addTagFilter(tfs, "i", ".*", false, true)
|
||||
addTagFilter(tfs, "j", "foo", false, false)
|
||||
benchSearch(b, tfs, 1e5)
|
||||
})
|
||||
b.Run(`n="1",i=~".*",i!="2",j="foo"`, func(b *testing.B) {
|
||||
tfs := NewTagFilters()
|
||||
addTagFilter(tfs, "n", "1", false, false)
|
||||
addTagFilter(tfs, "i", ".*", false, true)
|
||||
addTagFilter(tfs, "i", "2", true, false)
|
||||
addTagFilter(tfs, "j", "foo", false, false)
|
||||
benchSearch(b, tfs, 1e5-1)
|
||||
})
|
||||
b.Run(`n="1",i!=""`, func(b *testing.B) {
|
||||
tfs := NewTagFilters()
|
||||
addTagFilter(tfs, "n", "1", false, false)
|
||||
addTagFilter(tfs, "i", "", true, false)
|
||||
benchSearch(b, tfs, 2e5)
|
||||
})
|
||||
b.Run(`n="1",i!="",j="foo"`, func(b *testing.B) {
|
||||
tfs := NewTagFilters()
|
||||
addTagFilter(tfs, "n", "1", false, false)
|
||||
addTagFilter(tfs, "i", "", true, false)
|
||||
addTagFilter(tfs, "j", "foo", false, false)
|
||||
benchSearch(b, tfs, 1e5)
|
||||
})
|
||||
b.Run(`n="1",i=~".+",j="foo"`, func(b *testing.B) {
|
||||
tfs := NewTagFilters()
|
||||
addTagFilter(tfs, "n", "1", false, false)
|
||||
addTagFilter(tfs, "i", ".+", false, true)
|
||||
addTagFilter(tfs, "j", "foo", false, false)
|
||||
benchSearch(b, tfs, 1e5)
|
||||
})
|
||||
b.Run(`n="1",i=~"1.+",j="foo"`, func(b *testing.B) {
|
||||
tfs := NewTagFilters()
|
||||
addTagFilter(tfs, "n", "1", false, false)
|
||||
addTagFilter(tfs, "i", "1.+", false, true)
|
||||
addTagFilter(tfs, "j", "foo", false, false)
|
||||
benchSearch(b, tfs, 11110)
|
||||
})
|
||||
b.Run(`n="1",i=~".+",i!="2",j="foo"`, func(b *testing.B) {
|
||||
tfs := NewTagFilters()
|
||||
addTagFilter(tfs, "n", "1", false, false)
|
||||
addTagFilter(tfs, "i", ".+", false, true)
|
||||
addTagFilter(tfs, "i", "2", true, false)
|
||||
addTagFilter(tfs, "j", "foo", false, false)
|
||||
benchSearch(b, tfs, 1e5-1)
|
||||
})
|
||||
b.Run(`n="1",i=~".+",i!~"2.*",j="foo"`, func(b *testing.B) {
|
||||
tfs := NewTagFilters()
|
||||
addTagFilter(tfs, "n", "1", false, false)
|
||||
addTagFilter(tfs, "i", ".+", false, true)
|
||||
addTagFilter(tfs, "i", "2.*", true, true)
|
||||
addTagFilter(tfs, "j", "foo", false, false)
|
||||
benchSearch(b, tfs, 88889)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkIndexDBGetTSIDs(b *testing.B) {
|
||||
metricIDCache := workingsetcache.New(1234, time.Hour)
|
||||
metricNameCache := workingsetcache.New(1234, time.Hour)
|
||||
|
||||
@@ -142,3 +142,82 @@ func TestMetricNameMarshalUnmarshalRaw(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricNameCopyFrom(t *testing.T) {
|
||||
var from MetricName
|
||||
from.MetricGroup = []byte("group")
|
||||
from.AddTag("key", "value")
|
||||
|
||||
var to MetricName
|
||||
to.CopyFrom(&from)
|
||||
|
||||
var expected MetricName
|
||||
expected.MetricGroup = []byte("group")
|
||||
expected.AddTag("key", "value")
|
||||
|
||||
if !reflect.DeepEqual(expected, to) {
|
||||
t.Fatalf("expecting equal metics exp: %s, got %s", &expected, &to)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricNameRemoveTagsOn(t *testing.T) {
|
||||
var emptyMN MetricName
|
||||
emptyMN.MetricGroup = []byte("name")
|
||||
emptyMN.AddTag("key", "value")
|
||||
emptyMN.RemoveTagsOn(nil)
|
||||
if len(emptyMN.MetricGroup) != 0 || len(emptyMN.Tags) != 0 {
|
||||
t.Fatalf("expecitng empty metric name got %s", &emptyMN)
|
||||
}
|
||||
|
||||
var asIsMN MetricName
|
||||
asIsMN.MetricGroup = []byte("name")
|
||||
asIsMN.AddTag("key", "value")
|
||||
asIsMN.RemoveTagsOn([]string{"__name__", "key"})
|
||||
var expAsIsMN MetricName
|
||||
expAsIsMN.MetricGroup = []byte("name")
|
||||
expAsIsMN.AddTag("key", "value")
|
||||
if !reflect.DeepEqual(expAsIsMN, asIsMN) {
|
||||
t.Fatalf("expecitng %s got %s", &expAsIsMN, &asIsMN)
|
||||
}
|
||||
|
||||
var mn MetricName
|
||||
mn.MetricGroup = []byte("name")
|
||||
mn.AddTag("foo", "bar")
|
||||
mn.AddTag("baz", "qux")
|
||||
mn.RemoveTagsOn([]string{"baz"})
|
||||
var expMN MetricName
|
||||
expMN.AddTag("baz", "qux")
|
||||
if !reflect.DeepEqual(expMN.Tags, mn.Tags) || len(mn.MetricGroup) != len(expMN.MetricGroup) {
|
||||
t.Fatalf("expecitng %s got %s", &expMN, &mn)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricNameRemoveTag(t *testing.T) {
|
||||
var mn MetricName
|
||||
mn.MetricGroup = []byte("name")
|
||||
mn.AddTag("foo", "bar")
|
||||
mn.AddTag("baz", "qux")
|
||||
mn.RemoveTag("__name__")
|
||||
if len(mn.MetricGroup) != 0 {
|
||||
t.Fatalf("expecting empty metric group got %s", &mn)
|
||||
}
|
||||
mn.RemoveTag("foo")
|
||||
var expMN MetricName
|
||||
expMN.AddTag("baz", "qux")
|
||||
if !reflect.DeepEqual(expMN.Tags, mn.Tags) || len(mn.MetricGroup) != len(expMN.MetricGroup) {
|
||||
t.Fatalf("expecitng %s got %s", &expMN, &mn)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricNameRemoveTagsIgnoring(t *testing.T) {
|
||||
var mn MetricName
|
||||
mn.MetricGroup = []byte("name")
|
||||
mn.AddTag("foo", "bar")
|
||||
mn.AddTag("baz", "qux")
|
||||
mn.RemoveTagsIgnoring([]string{"__name__", "foo"})
|
||||
var expMN MetricName
|
||||
expMN.AddTag("baz", "qux")
|
||||
if !reflect.DeepEqual(expMN.Tags, mn.Tags) || len(mn.MetricGroup) != len(expMN.MetricGroup) {
|
||||
t.Fatalf("expecitng %s got %s", &expMN, &mn)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ const defaultPartsToMerge = 15
|
||||
// It must be smaller than defaultPartsToMerge.
|
||||
// Lower value improves select performance at the cost of increased
|
||||
// write amplification.
|
||||
const finalPartsToMerge = 2
|
||||
const finalPartsToMerge = 3
|
||||
|
||||
// getMaxRowsPerPartition returns the maximum number of rows that haven't been converted into parts yet.
|
||||
func getMaxRawRowsPerPartition() int {
|
||||
@@ -1253,7 +1253,10 @@ func appendPartsToMerge(dst, src []*partWrapper, maxPartsToMerge int, maxRows ui
|
||||
}
|
||||
|
||||
func openParts(pathPrefix1, pathPrefix2, path string) ([]*partWrapper, error) {
|
||||
// Verify that the directory for the parts exists.
|
||||
// The path can be missing after restoring from backup, so create it if needed.
|
||||
if err := fs.MkdirAllIfNotExist(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open directory %q: %s", path, err)
|
||||
|
||||
@@ -71,7 +71,16 @@ func TestSearchQueryMarshalUnmarshal(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSearch(t *testing.T) {
|
||||
path := "TestSearch"
|
||||
t.Run("global_inverted_index", func(t *testing.T) {
|
||||
testSearchGeneric(t, false)
|
||||
})
|
||||
t.Run("perday_inverted_index", func(t *testing.T) {
|
||||
testSearchGeneric(t, true)
|
||||
})
|
||||
}
|
||||
|
||||
func testSearchGeneric(t *testing.T, forcePerDayInvertedIndex bool) {
|
||||
path := fmt.Sprintf("TestSearch_%v", forcePerDayInvertedIndex)
|
||||
st, err := OpenStorage(path, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open storage %q: %s", path, err)
|
||||
@@ -96,7 +105,7 @@ func TestSearch(t *testing.T) {
|
||||
{[]byte("instance"), []byte("8.8.8.8:1234")},
|
||||
}
|
||||
startTimestamp := timestampFromTime(time.Now())
|
||||
startTimestamp -= startTimestamp % (1e3 * 3600 * 24)
|
||||
startTimestamp -= startTimestamp % (1e3 * 60 * 30)
|
||||
blockRowsCount := 0
|
||||
for i := 0; i < rowsCount; i++ {
|
||||
mn.MetricGroup = []byte(fmt.Sprintf("metric_%d", i%metricGroupsCount))
|
||||
@@ -125,6 +134,13 @@ func TestSearch(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("cannot re-open storage %q: %s", path, err)
|
||||
}
|
||||
if forcePerDayInvertedIndex {
|
||||
idb := st.idb()
|
||||
idb.startDateForPerDayInvertedIndex = 0
|
||||
idb.doExtDB(func(extDB *indexDB) {
|
||||
extDB.startDateForPerDayInvertedIndex = 0
|
||||
})
|
||||
}
|
||||
|
||||
// Run search.
|
||||
tr := TimeRange{
|
||||
@@ -133,7 +149,7 @@ func TestSearch(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("serial", func(t *testing.T) {
|
||||
if err := testSearch(st, tr, mrs, accountsCount); err != nil {
|
||||
if err := testSearchInternal(st, tr, mrs, accountsCount); err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
})
|
||||
@@ -142,7 +158,7 @@ func TestSearch(t *testing.T) {
|
||||
ch := make(chan error, 3)
|
||||
for i := 0; i < cap(ch); i++ {
|
||||
go func() {
|
||||
ch <- testSearch(st, tr, mrs, accountsCount)
|
||||
ch <- testSearchInternal(st, tr, mrs, accountsCount)
|
||||
}()
|
||||
}
|
||||
for i := 0; i < cap(ch); i++ {
|
||||
@@ -158,7 +174,7 @@ func TestSearch(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func testSearch(st *Storage, tr TimeRange, mrs []MetricRow, accountsCount int) error {
|
||||
func testSearchInternal(st *Storage, tr TimeRange, mrs []MetricRow, accountsCount int) error {
|
||||
var s Search
|
||||
for i := 0; i < 10; i++ {
|
||||
// Prepare TagFilters for search.
|
||||
|
||||
@@ -59,17 +59,17 @@ type Storage struct {
|
||||
metricNameCache *workingsetcache.Cache
|
||||
|
||||
// dateMetricIDCache is (Date, MetricID) cache.
|
||||
dateMetricIDCache *workingsetcache.Cache
|
||||
dateMetricIDCache *dateMetricIDCache
|
||||
|
||||
// Fast cache for MetricID values occured during the current hour.
|
||||
// Fast cache for MetricID values occurred during the current hour.
|
||||
currHourMetricIDs atomic.Value
|
||||
|
||||
// Fast cache for MetricID values occured during the previous hour.
|
||||
// Fast cache for MetricID values occurred during the previous hour.
|
||||
prevHourMetricIDs atomic.Value
|
||||
|
||||
// Pending MetricID values to be added to currHourMetricIDs.
|
||||
pendingHourMetricIDsLock sync.Mutex
|
||||
pendingHourMetricIDs *uint64set.Set
|
||||
pendingHourEntriesLock sync.Mutex
|
||||
pendingHourEntries *uint64set.Set
|
||||
|
||||
stop chan struct{}
|
||||
|
||||
@@ -118,14 +118,14 @@ func OpenStorage(path string, retentionMonths int) (*Storage, error) {
|
||||
s.tsidCache = s.mustLoadCache("MetricName->TSID", "metricName_tsid", mem/3)
|
||||
s.metricIDCache = s.mustLoadCache("MetricID->TSID", "metricID_tsid", mem/16)
|
||||
s.metricNameCache = s.mustLoadCache("MetricID->MetricName", "metricID_metricName", mem/8)
|
||||
s.dateMetricIDCache = s.mustLoadCache("Date->MetricID", "date_metricID", mem/32)
|
||||
s.dateMetricIDCache = newDateMetricIDCache()
|
||||
|
||||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
hmCurr := s.mustLoadHourMetricIDs(hour, "curr_hour_metric_ids")
|
||||
hmPrev := s.mustLoadHourMetricIDs(hour-1, "prev_hour_metric_ids")
|
||||
s.currHourMetricIDs.Store(hmCurr)
|
||||
s.prevHourMetricIDs.Store(hmPrev)
|
||||
s.pendingHourMetricIDs = &uint64set.Set{}
|
||||
s.pendingHourEntries = &uint64set.Set{}
|
||||
|
||||
// Load indexdb
|
||||
idbPath := path + "/indexdb"
|
||||
@@ -305,13 +305,13 @@ type Metrics struct {
|
||||
MetricNameCacheMisses uint64
|
||||
MetricNameCacheCollisions uint64
|
||||
|
||||
DateMetricIDCacheSize uint64
|
||||
DateMetricIDCacheSizeBytes uint64
|
||||
DateMetricIDCacheRequests uint64
|
||||
DateMetricIDCacheMisses uint64
|
||||
DateMetricIDCacheCollisions uint64
|
||||
DateMetricIDCacheSize uint64
|
||||
DateMetricIDCacheSizeBytes uint64
|
||||
DateMetricIDCacheSyncsCount uint64
|
||||
DateMetricIDCacheResetsCount uint64
|
||||
|
||||
HourMetricIDCacheSize uint64
|
||||
HourMetricIDCacheSize uint64
|
||||
HourMetricIDCacheSizeBytes uint64
|
||||
|
||||
IndexDBMetrics IndexDBMetrics
|
||||
TableMetrics TableMetrics
|
||||
@@ -357,13 +357,10 @@ func (s *Storage) UpdateMetrics(m *Metrics) {
|
||||
m.MetricNameCacheMisses += cs.Misses
|
||||
m.MetricNameCacheCollisions += cs.Collisions
|
||||
|
||||
cs.Reset()
|
||||
s.dateMetricIDCache.UpdateStats(&cs)
|
||||
m.DateMetricIDCacheSize += cs.EntriesCount
|
||||
m.DateMetricIDCacheSizeBytes += cs.BytesSize
|
||||
m.DateMetricIDCacheRequests += cs.GetCalls
|
||||
m.DateMetricIDCacheMisses += cs.Misses
|
||||
m.DateMetricIDCacheCollisions += cs.Collisions
|
||||
m.DateMetricIDCacheSize += uint64(s.dateMetricIDCache.EntriesCount())
|
||||
m.DateMetricIDCacheSizeBytes += uint64(s.dateMetricIDCache.SizeBytes())
|
||||
m.DateMetricIDCacheSyncsCount += atomic.LoadUint64(&s.dateMetricIDCache.syncsCount)
|
||||
m.DateMetricIDCacheResetsCount += atomic.LoadUint64(&s.dateMetricIDCache.resetsCount)
|
||||
|
||||
hmCurr := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
hmPrev := s.prevHourMetricIDs.Load().(*hourMetricIDs)
|
||||
@@ -372,6 +369,8 @@ func (s *Storage) UpdateMetrics(m *Metrics) {
|
||||
hourMetricIDsLen = hmCurr.m.Len()
|
||||
}
|
||||
m.HourMetricIDCacheSize += uint64(hourMetricIDsLen)
|
||||
m.HourMetricIDCacheSizeBytes += hmCurr.m.SizeBytes()
|
||||
m.HourMetricIDCacheSizeBytes += hmPrev.m.SizeBytes()
|
||||
|
||||
s.idb().UpdateMetrics(&m.IndexDBMetrics)
|
||||
s.tb.UpdateMetrics(&m.TableMetrics)
|
||||
@@ -412,6 +411,7 @@ func (s *Storage) currHourMetricIDsUpdater() {
|
||||
for {
|
||||
select {
|
||||
case <-s.stop:
|
||||
s.updateCurrHourMetricIDs()
|
||||
return
|
||||
case <-t.C:
|
||||
s.updateCurrHourMetricIDs()
|
||||
@@ -467,7 +467,6 @@ func (s *Storage) MustClose() {
|
||||
s.mustSaveAndStopCache(s.tsidCache, "MetricName->TSID", "metricName_tsid")
|
||||
s.mustSaveAndStopCache(s.metricIDCache, "MetricID->TSID", "metricID_tsid")
|
||||
s.mustSaveAndStopCache(s.metricNameCache, "MetricID->MetricName", "metricID_metricName")
|
||||
s.mustSaveAndStopCache(s.dateMetricIDCache, "Date->MetricID", "date_metricID")
|
||||
|
||||
hmCurr := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
s.mustSaveHourMetricIDs(hmCurr, "curr_hour_metric_ids")
|
||||
@@ -486,7 +485,9 @@ func (s *Storage) mustLoadHourMetricIDs(hour uint64, name string) *hourMetricIDs
|
||||
startTime := time.Now()
|
||||
if !fs.IsPathExist(path) {
|
||||
logger.Infof("nothing to load from %q", path)
|
||||
return &hourMetricIDs{}
|
||||
return &hourMetricIDs{
|
||||
hour: hour,
|
||||
}
|
||||
}
|
||||
src, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
@@ -495,21 +496,31 @@ func (s *Storage) mustLoadHourMetricIDs(hour uint64, name string) *hourMetricIDs
|
||||
srcOrigLen := len(src)
|
||||
if len(src) < 24 {
|
||||
logger.Errorf("discarding %s, since it has broken header; got %d bytes; want %d bytes", path, len(src), 24)
|
||||
return &hourMetricIDs{}
|
||||
return &hourMetricIDs{
|
||||
hour: hour,
|
||||
}
|
||||
}
|
||||
|
||||
// Unmarshal header
|
||||
isFull := encoding.UnmarshalUint64(src)
|
||||
src = src[8:]
|
||||
hourLoaded := encoding.UnmarshalUint64(src)
|
||||
src = src[8:]
|
||||
if hourLoaded != hour {
|
||||
logger.Infof("discarding %s, since it is outdated", name)
|
||||
return &hourMetricIDs{}
|
||||
logger.Infof("discarding %s, since it contains outdated hour; got %d; want %d", name, hourLoaded, hour)
|
||||
return &hourMetricIDs{
|
||||
hour: hour,
|
||||
}
|
||||
}
|
||||
|
||||
// Unmarshal hm.m
|
||||
hmLen := encoding.UnmarshalUint64(src)
|
||||
src = src[8:]
|
||||
if uint64(len(src)) != 8*hmLen {
|
||||
logger.Errorf("discarding %s, since it has broken body; got %d bytes; want %d bytes", path, len(src), 8*hmLen)
|
||||
return &hourMetricIDs{}
|
||||
if uint64(len(src)) < 8*hmLen {
|
||||
logger.Errorf("discarding %s, since it has broken hm.m data; got %d bytes; want at least %d bytes", path, len(src), 8*hmLen)
|
||||
return &hourMetricIDs{
|
||||
hour: hour,
|
||||
}
|
||||
}
|
||||
m := &uint64set.Set{}
|
||||
for i := uint64(0); i < hmLen; i++ {
|
||||
@@ -517,6 +528,7 @@ func (s *Storage) mustLoadHourMetricIDs(hour uint64, name string) *hourMetricIDs
|
||||
src = src[8:]
|
||||
m.Add(metricID)
|
||||
}
|
||||
|
||||
logger.Infof("loaded %s from %q in %s; entriesCount: %d; sizeBytes: %d", name, path, time.Since(startTime), hmLen, srcOrigLen)
|
||||
return &hourMetricIDs{
|
||||
m: m,
|
||||
@@ -534,12 +546,17 @@ func (s *Storage) mustSaveHourMetricIDs(hm *hourMetricIDs, name string) {
|
||||
if hm.isFull {
|
||||
isFull = 1
|
||||
}
|
||||
|
||||
// Marshal header
|
||||
dst = encoding.MarshalUint64(dst, isFull)
|
||||
dst = encoding.MarshalUint64(dst, hm.hour)
|
||||
|
||||
// Marshal hm.m
|
||||
dst = encoding.MarshalUint64(dst, uint64(hm.m.Len()))
|
||||
for _, metricID := range hm.m.AppendTo(nil) {
|
||||
dst = encoding.MarshalUint64(dst, metricID)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(path, dst, 0644); err != nil {
|
||||
logger.Panicf("FATAL: cannot write %d bytes to %q: %s", len(dst), path, err)
|
||||
}
|
||||
@@ -691,8 +708,7 @@ func (mr *MetricRow) String() string {
|
||||
if err := mn.unmarshalRaw(mr.MetricNameRaw); err == nil {
|
||||
metricName = mn.String()
|
||||
}
|
||||
return fmt.Sprintf("MetricName=%s, Timestamp=%d, Value=%f\n",
|
||||
metricName, mr.Timestamp, mr.Value)
|
||||
return fmt.Sprintf("MetricName=%s, Timestamp=%d, Value=%f\n", metricName, mr.Timestamp, mr.Value)
|
||||
}
|
||||
|
||||
// Marshal appends marshaled mr to dst and returns the result.
|
||||
@@ -772,9 +788,6 @@ var (
|
||||
)
|
||||
|
||||
func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]rawRow, error) {
|
||||
// Return only the last error, since it has no sense in returning all errors.
|
||||
var lastWarn error
|
||||
|
||||
var is *indexSearch
|
||||
var mn *MetricName
|
||||
var kb *bytesutil.ByteBuffer
|
||||
@@ -788,6 +801,8 @@ func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]ra
|
||||
rows = rows[:rowsLen+len(mrs)]
|
||||
j := 0
|
||||
minTimestamp, maxTimestamp := s.tb.getMinMaxTimestamps()
|
||||
// Return only the last error, since it has no sense in returning all errors.
|
||||
var lastWarn error
|
||||
for i := range mrs {
|
||||
mr := &mrs[i]
|
||||
if math.IsNaN(mr.Value) {
|
||||
@@ -859,7 +874,7 @@ func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]ra
|
||||
if err := s.tb.AddRows(rows); err != nil {
|
||||
lastError = fmt.Errorf("cannot add rows to table: %s", err)
|
||||
}
|
||||
if err := s.updateDateMetricIDCache(rows, lastError); err != nil {
|
||||
if err := s.updatePerDateData(rows, lastError); err != nil {
|
||||
lastError = err
|
||||
}
|
||||
if lastError != nil {
|
||||
@@ -868,16 +883,12 @@ func (s *Storage) add(rows []rawRow, mrs []MetricRow, precisionBits uint8) ([]ra
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func (s *Storage) updateDateMetricIDCache(rows []rawRow, lastError error) error {
|
||||
func (s *Storage) updatePerDateData(rows []rawRow, lastError error) error {
|
||||
var date uint64
|
||||
var hour uint64
|
||||
var prevTimestamp int64
|
||||
kb := kbPool.Get()
|
||||
defer kbPool.Put(kb)
|
||||
kb.B = bytesutil.Resize(kb.B, 16)
|
||||
keyBuf := kb.B
|
||||
a := (*[2]uint64)(unsafe.Pointer(&keyBuf[0]))
|
||||
idb := s.idb()
|
||||
hm := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
for i := range rows {
|
||||
r := &rows[i]
|
||||
if r.Timestamp != prevTimestamp {
|
||||
@@ -886,49 +897,201 @@ func (s *Storage) updateDateMetricIDCache(rows []rawRow, lastError error) error
|
||||
prevTimestamp = r.Timestamp
|
||||
}
|
||||
metricID := r.TSID.MetricID
|
||||
hm := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if hour == hm.hour {
|
||||
// The r belongs to the current hour. Check for the current hour cache.
|
||||
if hm.m.Has(metricID) {
|
||||
// Fast path: the metricID is in the current hour cache.
|
||||
// This means the metricID has been already added to per-day inverted index.
|
||||
continue
|
||||
}
|
||||
s.pendingHourMetricIDsLock.Lock()
|
||||
s.pendingHourMetricIDs.Add(metricID)
|
||||
s.pendingHourMetricIDsLock.Unlock()
|
||||
s.pendingHourEntriesLock.Lock()
|
||||
s.pendingHourEntries.Add(metricID)
|
||||
s.pendingHourEntriesLock.Unlock()
|
||||
}
|
||||
|
||||
// Slower path: check global cache for (date, metricID) entry.
|
||||
a[0] = date
|
||||
a[1] = metricID
|
||||
if s.dateMetricIDCache.Has(keyBuf) {
|
||||
if s.dateMetricIDCache.Has(date, metricID) {
|
||||
// The metricID has been already added to per-day inverted index.
|
||||
continue
|
||||
}
|
||||
|
||||
// Slow path: store the entry in the (date, metricID) cache and in the indexDB.
|
||||
// Slow path: store the (date, metricID) entry in the indexDB.
|
||||
// It is OK if the (date, metricID) entry is added multiple times to db
|
||||
// by concurrent goroutines.
|
||||
s.dateMetricIDCache.Set(keyBuf, nil)
|
||||
if err := idb.storeDateMetricID(date, metricID); err != nil {
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
|
||||
// The metric must be added to cache only after it has been successfully added to indexDB.
|
||||
s.dateMetricIDCache.Set(date, metricID)
|
||||
}
|
||||
return lastError
|
||||
}
|
||||
|
||||
// dateMetricIDCache is fast cache for holding (date, metricID) entries.
|
||||
//
|
||||
// It should be faster than map[date]*uint64set.Set on multicore systems.
|
||||
type dateMetricIDCache struct {
|
||||
// 64-bit counters must be at the top of the structure to be properly aligned on 32-bit arches.
|
||||
syncsCount uint64
|
||||
resetsCount uint64
|
||||
|
||||
// Contains immutable map
|
||||
byDate atomic.Value
|
||||
|
||||
// Contains mutable map protected by mu
|
||||
byDateMutable *byDateMetricIDMap
|
||||
lastSyncTime time.Time
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func newDateMetricIDCache() *dateMetricIDCache {
|
||||
var dmc dateMetricIDCache
|
||||
dmc.Reset()
|
||||
return &dmc
|
||||
}
|
||||
|
||||
func (dmc *dateMetricIDCache) Reset() {
|
||||
dmc.mu.Lock()
|
||||
// Do not reset syncsCount and resetsCount
|
||||
dmc.byDate.Store(newByDateMetricIDMap())
|
||||
dmc.byDateMutable = newByDateMetricIDMap()
|
||||
dmc.lastSyncTime = time.Now()
|
||||
dmc.mu.Unlock()
|
||||
|
||||
atomic.AddUint64(&dmc.resetsCount, 1)
|
||||
}
|
||||
|
||||
func (dmc *dateMetricIDCache) EntriesCount() int {
|
||||
byDate := dmc.byDate.Load().(*byDateMetricIDMap)
|
||||
n := 0
|
||||
for _, e := range byDate.m {
|
||||
n += e.v.Len()
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (dmc *dateMetricIDCache) SizeBytes() uint64 {
|
||||
byDate := dmc.byDate.Load().(*byDateMetricIDMap)
|
||||
n := uint64(0)
|
||||
for _, e := range byDate.m {
|
||||
n += e.v.SizeBytes()
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (dmc *dateMetricIDCache) Has(date, metricID uint64) bool {
|
||||
byDate := dmc.byDate.Load().(*byDateMetricIDMap)
|
||||
v := byDate.get(date)
|
||||
if v.Has(metricID) {
|
||||
// Fast path.
|
||||
// The majority of calls must go here.
|
||||
return true
|
||||
}
|
||||
|
||||
// Slow path. Check mutable map.
|
||||
currentTime := time.Now()
|
||||
|
||||
dmc.mu.Lock()
|
||||
v = dmc.byDateMutable.get(date)
|
||||
ok := v.Has(metricID)
|
||||
mustSync := false
|
||||
if currentTime.Sub(dmc.lastSyncTime) > 10*time.Second {
|
||||
mustSync = true
|
||||
dmc.lastSyncTime = currentTime
|
||||
}
|
||||
dmc.mu.Unlock()
|
||||
|
||||
if mustSync {
|
||||
dmc.sync()
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func (dmc *dateMetricIDCache) Set(date, metricID uint64) {
|
||||
dmc.mu.Lock()
|
||||
v := dmc.byDateMutable.getOrCreate(date)
|
||||
v.Add(metricID)
|
||||
dmc.mu.Unlock()
|
||||
}
|
||||
|
||||
func (dmc *dateMetricIDCache) sync() {
|
||||
dmc.mu.Lock()
|
||||
byDate := dmc.byDate.Load().(*byDateMetricIDMap)
|
||||
for date, e := range dmc.byDateMutable.m {
|
||||
v := byDate.get(date)
|
||||
e.v.Union(v)
|
||||
}
|
||||
dmc.byDate.Store(dmc.byDateMutable)
|
||||
dmc.byDateMutable = newByDateMetricIDMap()
|
||||
dmc.mu.Unlock()
|
||||
|
||||
atomic.AddUint64(&dmc.syncsCount, 1)
|
||||
|
||||
if dmc.EntriesCount() > memory.Allowed()/128 {
|
||||
dmc.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
type byDateMetricIDMap struct {
|
||||
hotEntry atomic.Value
|
||||
m map[uint64]*byDateMetricIDEntry
|
||||
}
|
||||
|
||||
func newByDateMetricIDMap() *byDateMetricIDMap {
|
||||
dmm := &byDateMetricIDMap{
|
||||
m: make(map[uint64]*byDateMetricIDEntry),
|
||||
}
|
||||
dmm.hotEntry.Store(&byDateMetricIDEntry{})
|
||||
return dmm
|
||||
}
|
||||
|
||||
func (dmm *byDateMetricIDMap) get(date uint64) *uint64set.Set {
|
||||
hotEntry := dmm.hotEntry.Load().(*byDateMetricIDEntry)
|
||||
if hotEntry.date == date {
|
||||
// Fast path
|
||||
return &hotEntry.v
|
||||
}
|
||||
// Slow path
|
||||
e := dmm.m[date]
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
dmm.hotEntry.Store(e)
|
||||
return &e.v
|
||||
}
|
||||
|
||||
func (dmm *byDateMetricIDMap) getOrCreate(date uint64) *uint64set.Set {
|
||||
v := dmm.get(date)
|
||||
if v != nil {
|
||||
return v
|
||||
}
|
||||
e := &byDateMetricIDEntry{
|
||||
date: date,
|
||||
}
|
||||
dmm.m[date] = e
|
||||
return &e.v
|
||||
}
|
||||
|
||||
type byDateMetricIDEntry struct {
|
||||
date uint64
|
||||
v uint64set.Set
|
||||
}
|
||||
|
||||
func (s *Storage) updateCurrHourMetricIDs() {
|
||||
hm := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
s.pendingHourMetricIDsLock.Lock()
|
||||
newMetricIDsLen := s.pendingHourMetricIDs.Len()
|
||||
s.pendingHourMetricIDsLock.Unlock()
|
||||
s.pendingHourEntriesLock.Lock()
|
||||
newMetricIDs := s.pendingHourEntries
|
||||
s.pendingHourEntries = &uint64set.Set{}
|
||||
s.pendingHourEntriesLock.Unlock()
|
||||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
if newMetricIDsLen == 0 && hm.hour == hour {
|
||||
if newMetricIDs.Len() == 0 && hm.hour == hour {
|
||||
// Fast path: nothing to update.
|
||||
return
|
||||
}
|
||||
|
||||
// Slow path: hm.m must be updated with non-empty s.pendingHourMetricIDs.
|
||||
// Slow path: hm.m must be updated with non-empty s.pendingHourEntries.
|
||||
var m *uint64set.Set
|
||||
isFull := hm.isFull
|
||||
if hm.hour == hour {
|
||||
@@ -937,14 +1100,7 @@ func (s *Storage) updateCurrHourMetricIDs() {
|
||||
m = &uint64set.Set{}
|
||||
isFull = true
|
||||
}
|
||||
s.pendingHourMetricIDsLock.Lock()
|
||||
newMetricIDs := s.pendingHourMetricIDs.AppendTo(nil)
|
||||
s.pendingHourMetricIDs = &uint64set.Set{}
|
||||
s.pendingHourMetricIDsLock.Unlock()
|
||||
for _, metricID := range newMetricIDs {
|
||||
m.Add(metricID)
|
||||
}
|
||||
|
||||
m.Union(newMetricIDs)
|
||||
hmNew := &hourMetricIDs{
|
||||
m: m,
|
||||
hour: hour,
|
||||
@@ -1043,6 +1199,11 @@ func openIndexDBTables(path string, metricIDCache, metricNameCache *workingsetca
|
||||
return nil, nil, fmt.Errorf("cannot open prev indexdb table at %q: %s", prevPath, err)
|
||||
}
|
||||
|
||||
// Adjust startDateForPerDayInvertedIndex for the previous index.
|
||||
if prev.startDateForPerDayInvertedIndex > curr.startDateForPerDayInvertedIndex {
|
||||
prev.startDateForPerDayInvertedIndex = curr.startDateForPerDayInvertedIndex
|
||||
}
|
||||
|
||||
return curr, prev, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -13,12 +13,94 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/uint64set"
|
||||
)
|
||||
|
||||
func TestDateMetricIDCacheSerial(t *testing.T) {
|
||||
c := newDateMetricIDCache()
|
||||
if err := testDateMetricIDCache(c, false); err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDateMetricIDCacheConcurrent(t *testing.T) {
|
||||
c := newDateMetricIDCache()
|
||||
ch := make(chan error, 5)
|
||||
for i := 0; i < 5; i++ {
|
||||
go func() {
|
||||
ch <- testDateMetricIDCache(c, true)
|
||||
}()
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
select {
|
||||
case err := <-ch:
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
case <-time.After(time.Second * 5):
|
||||
t.Fatalf("timeout")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testDateMetricIDCache(c *dateMetricIDCache, concurrent bool) error {
|
||||
type dmk struct {
|
||||
date uint64
|
||||
metricID uint64
|
||||
}
|
||||
m := make(map[dmk]bool)
|
||||
for i := 0; i < 1e5; i++ {
|
||||
date := uint64(i) % 3
|
||||
metricID := uint64(i) % 1237
|
||||
if !concurrent && c.Has(date, metricID) {
|
||||
if !m[dmk{date, metricID}] {
|
||||
return fmt.Errorf("c.Has(%d, %d) must return false, but returned true", date, metricID)
|
||||
}
|
||||
continue
|
||||
}
|
||||
c.Set(date, metricID)
|
||||
m[dmk{date, metricID}] = true
|
||||
if !concurrent && !c.Has(date, metricID) {
|
||||
return fmt.Errorf("c.Has(%d, %d) must return true, but returned false", date, metricID)
|
||||
}
|
||||
if i%11234 == 0 {
|
||||
c.sync()
|
||||
}
|
||||
if i%34323 == 0 {
|
||||
c.Reset()
|
||||
m = make(map[dmk]bool)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify fast path after sync.
|
||||
for i := 0; i < 1e5; i++ {
|
||||
date := uint64(i) % 3
|
||||
metricID := uint64(i) % 123
|
||||
c.Set(date, metricID)
|
||||
}
|
||||
c.sync()
|
||||
for i := 0; i < 1e5; i++ {
|
||||
date := uint64(i) % 3
|
||||
metricID := uint64(i) % 123
|
||||
if !concurrent && !c.Has(date, metricID) {
|
||||
return fmt.Errorf("c.Has(%d, %d) must return true after sync", date, metricID)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify c.Reset
|
||||
if n := c.EntriesCount(); !concurrent && n < 123 {
|
||||
return fmt.Errorf("c.EntriesCount must return at least 123; returned %d", n)
|
||||
}
|
||||
c.Reset()
|
||||
if n := c.EntriesCount(); !concurrent && n > 0 {
|
||||
return fmt.Errorf("c.EntriesCount must return 0 after reset; returned %d", n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
newStorage := func() *Storage {
|
||||
var s Storage
|
||||
s.currHourMetricIDs.Store(&hourMetricIDs{})
|
||||
s.prevHourMetricIDs.Store(&hourMetricIDs{})
|
||||
s.pendingHourMetricIDs = &uint64set.Set{}
|
||||
s.pendingHourEntries = &uint64set.Set{}
|
||||
return &s
|
||||
}
|
||||
t.Run("empty_pedning_metric_ids_stale_curr_hour", func(t *testing.T) {
|
||||
@@ -34,7 +116,7 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
s.updateCurrHourMetricIDs()
|
||||
hmCurr := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if hmCurr.hour != hour {
|
||||
// It is possible new hour occured. Update the hour and verify it again.
|
||||
// It is possible new hour occurred. Update the hour and verify it again.
|
||||
hour = uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
if hmCurr.hour != hour {
|
||||
t.Fatalf("unexpected hmCurr.hour; got %d; want %d", hmCurr.hour, hour)
|
||||
@@ -52,8 +134,8 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
t.Fatalf("unexpected hmPrev; got %v; want %v", hmPrev, hmOrig)
|
||||
}
|
||||
|
||||
if s.pendingHourMetricIDs.Len() != 0 {
|
||||
t.Fatalf("unexpected s.pendingHourMetricIDs.Len(); got %d; want %d", s.pendingHourMetricIDs.Len(), 0)
|
||||
if s.pendingHourEntries.Len() != 0 {
|
||||
t.Fatalf("unexpected s.pendingHourEntries.Len(); got %d; want %d", s.pendingHourEntries.Len(), 0)
|
||||
}
|
||||
})
|
||||
t.Run("empty_pedning_metric_ids_valid_curr_hour", func(t *testing.T) {
|
||||
@@ -69,7 +151,7 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
s.updateCurrHourMetricIDs()
|
||||
hmCurr := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if hmCurr.hour != hour {
|
||||
// It is possible new hour occured. Update the hour and verify it again.
|
||||
// It is possible new hour occurred. Update the hour and verify it again.
|
||||
hour = uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
if hmCurr.hour != hour {
|
||||
t.Fatalf("unexpected hmCurr.hour; got %d; want %d", hmCurr.hour, hour)
|
||||
@@ -90,17 +172,17 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
t.Fatalf("unexpected hmPrev; got %v; want %v", hmPrev, hmEmpty)
|
||||
}
|
||||
|
||||
if s.pendingHourMetricIDs.Len() != 0 {
|
||||
t.Fatalf("unexpected s.pendingHourMetricIDs.Len(); got %d; want %d", s.pendingHourMetricIDs.Len(), 0)
|
||||
if s.pendingHourEntries.Len() != 0 {
|
||||
t.Fatalf("unexpected s.pendingHourEntries.Len(); got %d; want %d", s.pendingHourEntries.Len(), 0)
|
||||
}
|
||||
})
|
||||
t.Run("nonempty_pending_metric_ids_stale_curr_hour", func(t *testing.T) {
|
||||
s := newStorage()
|
||||
pendingHourMetricIDs := &uint64set.Set{}
|
||||
pendingHourMetricIDs.Add(343)
|
||||
pendingHourMetricIDs.Add(32424)
|
||||
pendingHourMetricIDs.Add(8293432)
|
||||
s.pendingHourMetricIDs = pendingHourMetricIDs
|
||||
pendingHourEntries := &uint64set.Set{}
|
||||
pendingHourEntries.Add(343)
|
||||
pendingHourEntries.Add(32424)
|
||||
pendingHourEntries.Add(8293432)
|
||||
s.pendingHourEntries = pendingHourEntries
|
||||
|
||||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
hmOrig := &hourMetricIDs{
|
||||
@@ -113,14 +195,14 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
s.updateCurrHourMetricIDs()
|
||||
hmCurr := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if hmCurr.hour != hour {
|
||||
// It is possible new hour occured. Update the hour and verify it again.
|
||||
// It is possible new hour occurred. Update the hour and verify it again.
|
||||
hour = uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
if hmCurr.hour != hour {
|
||||
t.Fatalf("unexpected hmCurr.hour; got %d; want %d", hmCurr.hour, hour)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(hmCurr.m, pendingHourMetricIDs) {
|
||||
t.Fatalf("unexpected hm.m; got %v; want %v", hmCurr.m, pendingHourMetricIDs)
|
||||
if !hmCurr.m.Equal(pendingHourEntries) {
|
||||
t.Fatalf("unexpected hmCurr.m; got %v; want %v", hmCurr.m, pendingHourEntries)
|
||||
}
|
||||
if !hmCurr.isFull {
|
||||
t.Fatalf("unexpected hmCurr.isFull; got %v; want %v", hmCurr.isFull, true)
|
||||
@@ -131,17 +213,17 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
t.Fatalf("unexpected hmPrev; got %v; want %v", hmPrev, hmOrig)
|
||||
}
|
||||
|
||||
if s.pendingHourMetricIDs.Len() != 0 {
|
||||
t.Fatalf("unexpected s.pendingHourMetricIDs.Len(); got %d; want %d", s.pendingHourMetricIDs.Len(), 0)
|
||||
if s.pendingHourEntries.Len() != 0 {
|
||||
t.Fatalf("unexpected s.pendingHourEntries.Len(); got %d; want %d", s.pendingHourEntries.Len(), 0)
|
||||
}
|
||||
})
|
||||
t.Run("nonempty_pending_metric_ids_valid_curr_hour", func(t *testing.T) {
|
||||
s := newStorage()
|
||||
pendingHourMetricIDs := &uint64set.Set{}
|
||||
pendingHourMetricIDs.Add(343)
|
||||
pendingHourMetricIDs.Add(32424)
|
||||
pendingHourMetricIDs.Add(8293432)
|
||||
s.pendingHourMetricIDs = pendingHourMetricIDs
|
||||
pendingHourEntries := &uint64set.Set{}
|
||||
pendingHourEntries.Add(343)
|
||||
pendingHourEntries.Add(32424)
|
||||
pendingHourEntries.Add(8293432)
|
||||
s.pendingHourEntries = pendingHourEntries
|
||||
|
||||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
hmOrig := &hourMetricIDs{
|
||||
@@ -154,7 +236,7 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
s.updateCurrHourMetricIDs()
|
||||
hmCurr := s.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if hmCurr.hour != hour {
|
||||
// It is possible new hour occured. Update the hour and verify it again.
|
||||
// It is possible new hour occurred. Update the hour and verify it again.
|
||||
hour = uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
if hmCurr.hour != hour {
|
||||
t.Fatalf("unexpected hmCurr.hour; got %d; want %d", hmCurr.hour, hour)
|
||||
@@ -162,12 +244,12 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
// Do not run other checks, since they may fail.
|
||||
return
|
||||
}
|
||||
m := pendingHourMetricIDs.Clone()
|
||||
m := pendingHourEntries.Clone()
|
||||
origMetricIDs := hmOrig.m.AppendTo(nil)
|
||||
for _, metricID := range origMetricIDs {
|
||||
m.Add(metricID)
|
||||
}
|
||||
if !reflect.DeepEqual(hmCurr.m, m) {
|
||||
if !hmCurr.m.Equal(m) {
|
||||
t.Fatalf("unexpected hm.m; got %v; want %v", hmCurr.m, m)
|
||||
}
|
||||
if hmCurr.isFull {
|
||||
@@ -180,8 +262,8 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
||||
t.Fatalf("unexpected hmPrev; got %v; want %v", hmPrev, hmEmpty)
|
||||
}
|
||||
|
||||
if s.pendingHourMetricIDs.Len() != 0 {
|
||||
t.Fatalf("unexpected s.pendingHourMetricIDs.Len(); got %d; want %d", s.pendingHourMetricIDs.Len(), 0)
|
||||
if s.pendingHourEntries.Len() != 0 {
|
||||
t.Fatalf("unexpected s.pendingHourEntries.Len(); got %d; want %d", s.pendingHourEntries.Len(), 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -381,7 +463,7 @@ func TestStorageDeleteMetrics(t *testing.T) {
|
||||
t.Run("serial", func(t *testing.T) {
|
||||
for i := 0; i < 3; i++ {
|
||||
if err = testStorageDeleteMetrics(s, 0); err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
t.Fatalf("unexpected error on iteration %d: %s", i, err)
|
||||
}
|
||||
|
||||
// Re-open the storage in order to check how deleted metricIDs
|
||||
@@ -389,7 +471,7 @@ func TestStorageDeleteMetrics(t *testing.T) {
|
||||
s.MustClose()
|
||||
s, err = OpenStorage(path, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open storage after closing: %s", err)
|
||||
t.Fatalf("cannot open storage after closing on iteration %d: %s", i, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -537,7 +619,7 @@ func testStorageDeleteMetrics(s *Storage, workerNum int) error {
|
||||
return fmt.Errorf("cannot delete metrics: %s", err)
|
||||
}
|
||||
if deletedCount == 0 {
|
||||
return fmt.Errorf("expecting non-zero number of deleted metrics")
|
||||
return fmt.Errorf("expecting non-zero number of deleted metrics on iteration %d", i)
|
||||
}
|
||||
if n := metricBlocksCount(tfs); n != 0 {
|
||||
return fmt.Errorf("expecting zero metric blocks after DeleteMetrics call for tfs=%s; got %d blocks", tfs, n)
|
||||
|
||||
@@ -436,27 +436,17 @@ func (tb *table) PutPartitions(ptws []*partitionWrapper) {
|
||||
}
|
||||
|
||||
func openPartitions(smallPartitionsPath, bigPartitionsPath string, getDeletedMetricIDs func() *uint64set.Set) ([]*partition, error) {
|
||||
smallD, err := os.Open(smallPartitionsPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open directory with small partitions %q: %s", smallPartitionsPath, err)
|
||||
// Certain partition directories in either `big` or `small` dir may be missing
|
||||
// after restoring from backup. So populate partition names from both dirs.
|
||||
ptNames := make(map[string]bool)
|
||||
if err := populatePartitionNames(smallPartitionsPath, ptNames); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fs.MustClose(smallD)
|
||||
|
||||
fis, err := smallD.Readdir(-1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read directory with small partitions %q: %s", smallPartitionsPath, err)
|
||||
if err := populatePartitionNames(bigPartitionsPath, ptNames); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var pts []*partition
|
||||
for _, fi := range fis {
|
||||
if !fs.IsDirOrSymlink(fi) {
|
||||
// Skip non-directories
|
||||
continue
|
||||
}
|
||||
ptName := fi.Name()
|
||||
if ptName == "snapshots" {
|
||||
// Skipe directory with snapshots
|
||||
continue
|
||||
}
|
||||
for ptName := range ptNames {
|
||||
smallPartsPath := smallPartitionsPath + "/" + ptName
|
||||
bigPartsPath := bigPartitionsPath + "/" + ptName
|
||||
pt, err := openPartition(smallPartsPath, bigPartsPath, getDeletedMetricIDs)
|
||||
@@ -469,6 +459,32 @@ func openPartitions(smallPartitionsPath, bigPartitionsPath string, getDeletedMet
|
||||
return pts, nil
|
||||
}
|
||||
|
||||
func populatePartitionNames(partitionsPath string, ptNames map[string]bool) error {
|
||||
d, err := os.Open(partitionsPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot open directory with partitions %q: %s", partitionsPath, err)
|
||||
}
|
||||
defer fs.MustClose(d)
|
||||
|
||||
fis, err := d.Readdir(-1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot read directory with partitions %q: %s", partitionsPath, err)
|
||||
}
|
||||
for _, fi := range fis {
|
||||
if !fs.IsDirOrSymlink(fi) {
|
||||
// Skip non-directories
|
||||
continue
|
||||
}
|
||||
ptName := fi.Name()
|
||||
if ptName == "snapshots" {
|
||||
// Skip directory with snapshots
|
||||
continue
|
||||
}
|
||||
ptNames[ptName] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mustClosePartitions(pts []*partition) {
|
||||
for _, pt := range pts {
|
||||
pt.MustClose()
|
||||
|
||||
@@ -31,10 +31,6 @@ func (tr *TimeRange) String() string {
|
||||
return fmt.Sprintf("[%s - %s]", minTime, maxTime)
|
||||
}
|
||||
|
||||
func (tr *TimeRange) isZero() bool {
|
||||
return tr.MinTimestamp == 0 && tr.MaxTimestamp == 0
|
||||
}
|
||||
|
||||
// timestampToPartitionName returns partition name for the given timestamp.
|
||||
func timestampToPartitionName(timestamp int64) string {
|
||||
t := timestampToTime(timestamp)
|
||||
|
||||
@@ -3,6 +3,7 @@ package uint64set
|
||||
import (
|
||||
"math/bits"
|
||||
"sort"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Set is a fast set for uint64.
|
||||
@@ -12,8 +13,10 @@ import (
|
||||
//
|
||||
// It is unsafe calling Set methods from concurrent goroutines.
|
||||
type Set struct {
|
||||
itemsCount int
|
||||
buckets bucket32Sorter
|
||||
skipSmallPool bool
|
||||
itemsCount int
|
||||
buckets bucket32Sorter
|
||||
smallPool [5]uint64
|
||||
}
|
||||
|
||||
type bucket32Sorter []*bucket32
|
||||
@@ -35,14 +38,29 @@ func (s *Set) Clone() *Set {
|
||||
return &Set{}
|
||||
}
|
||||
var dst Set
|
||||
dst.skipSmallPool = s.skipSmallPool
|
||||
dst.itemsCount = s.itemsCount
|
||||
dst.buckets = make([]*bucket32, len(s.buckets))
|
||||
dst.smallPool = s.smallPool
|
||||
for i, b32 := range s.buckets {
|
||||
dst.buckets[i] = b32.clone()
|
||||
}
|
||||
return &dst
|
||||
}
|
||||
|
||||
// SizeBytes returns an estimate size of s in RAM.
|
||||
func (s *Set) SizeBytes() uint64 {
|
||||
if s == nil {
|
||||
return 0
|
||||
}
|
||||
n := uint64(unsafe.Sizeof(*s))
|
||||
for _, b := range s.buckets {
|
||||
n += uint64(unsafe.Sizeof(b))
|
||||
n += b.sizeBytes()
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Len returns the number of distinct uint64 values in s.
|
||||
func (s *Set) Len() int {
|
||||
if s == nil {
|
||||
@@ -53,6 +71,10 @@ func (s *Set) Len() int {
|
||||
|
||||
// Add adds x to s.
|
||||
func (s *Set) Add(x uint64) {
|
||||
if !s.skipSmallPool {
|
||||
s.addToSmallPool(x)
|
||||
return
|
||||
}
|
||||
hi := uint32(x >> 32)
|
||||
lo := uint32(x)
|
||||
for _, b32 := range s.buckets {
|
||||
@@ -66,6 +88,23 @@ func (s *Set) Add(x uint64) {
|
||||
s.addAlloc(hi, lo)
|
||||
}
|
||||
|
||||
func (s *Set) addToSmallPool(x uint64) {
|
||||
if s.hasInSmallPool(x) {
|
||||
return
|
||||
}
|
||||
if s.itemsCount < len(s.smallPool) {
|
||||
s.smallPool[s.itemsCount] = x
|
||||
s.itemsCount++
|
||||
return
|
||||
}
|
||||
s.skipSmallPool = true
|
||||
s.itemsCount = 0
|
||||
for _, v := range s.smallPool[:] {
|
||||
s.Add(v)
|
||||
}
|
||||
s.Add(x)
|
||||
}
|
||||
|
||||
func (s *Set) addAlloc(hi, lo uint32) {
|
||||
var b32 bucket32
|
||||
b32.hi = hi
|
||||
@@ -76,11 +115,14 @@ func (s *Set) addAlloc(hi, lo uint32) {
|
||||
|
||||
// Has verifies whether x exists in s.
|
||||
func (s *Set) Has(x uint64) bool {
|
||||
hi := uint32(x >> 32)
|
||||
lo := uint32(x)
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
if !s.skipSmallPool {
|
||||
return s.hasInSmallPool(x)
|
||||
}
|
||||
hi := uint32(x >> 32)
|
||||
lo := uint32(x)
|
||||
for _, b32 := range s.buckets {
|
||||
if b32.hi == hi {
|
||||
return b32.has(lo)
|
||||
@@ -89,8 +131,21 @@ func (s *Set) Has(x uint64) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Set) hasInSmallPool(x uint64) bool {
|
||||
for _, v := range s.smallPool[:s.itemsCount] {
|
||||
if v == x {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Del deletes x from s.
|
||||
func (s *Set) Del(x uint64) {
|
||||
if !s.skipSmallPool {
|
||||
s.delFromSmallPool(x)
|
||||
return
|
||||
}
|
||||
hi := uint32(x >> 32)
|
||||
lo := uint32(x)
|
||||
for _, b32 := range s.buckets {
|
||||
@@ -103,13 +158,37 @@ func (s *Set) Del(x uint64) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Set) delFromSmallPool(x uint64) {
|
||||
idx := -1
|
||||
for i, v := range s.smallPool[:s.itemsCount] {
|
||||
if v == x {
|
||||
idx = i
|
||||
}
|
||||
}
|
||||
if idx < 0 {
|
||||
return
|
||||
}
|
||||
copy(s.smallPool[idx:], s.smallPool[idx+1:])
|
||||
s.itemsCount--
|
||||
}
|
||||
|
||||
// AppendTo appends all the items from the set to dst and returns the result.
|
||||
//
|
||||
// The returned items are sorted.
|
||||
//
|
||||
// AppendTo can mutate s.
|
||||
func (s *Set) AppendTo(dst []uint64) []uint64 {
|
||||
if s == nil {
|
||||
return dst
|
||||
}
|
||||
if !s.skipSmallPool {
|
||||
a := s.smallPool[:s.itemsCount]
|
||||
if len(a) > 1 {
|
||||
sort.Slice(a, func(i, j int) bool { return a[i] < a[j] })
|
||||
}
|
||||
return append(dst, a...)
|
||||
}
|
||||
|
||||
// pre-allocate memory for dst
|
||||
dstLen := len(dst)
|
||||
if n := s.Len() - cap(dst) + dstLen; n > 0 {
|
||||
@@ -126,17 +205,92 @@ func (s *Set) AppendTo(dst []uint64) []uint64 {
|
||||
return dst
|
||||
}
|
||||
|
||||
// Union adds all the items from a to s.
|
||||
func (s *Set) Union(a *Set) {
|
||||
// Clone a, since AppendTo may mutate it below.
|
||||
aCopy := a.Clone()
|
||||
if s.Len() == 0 {
|
||||
// Fast path if the initial set is empty.
|
||||
*s = *aCopy
|
||||
return
|
||||
}
|
||||
// TODO: optimize it
|
||||
for _, x := range aCopy.AppendTo(nil) {
|
||||
s.Add(x)
|
||||
}
|
||||
}
|
||||
|
||||
// Intersect removes all the items missing in a from s.
|
||||
func (s *Set) Intersect(a *Set) {
|
||||
if a.Len() == 0 {
|
||||
// Fast path
|
||||
*s = Set{}
|
||||
return
|
||||
}
|
||||
// TODO: optimize it
|
||||
for _, x := range s.AppendTo(nil) {
|
||||
if !a.Has(x) {
|
||||
s.Del(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Subtract removes from s all the shared items between s and a.
|
||||
func (s *Set) Subtract(a *Set) {
|
||||
if s.Len() == 0 {
|
||||
return
|
||||
}
|
||||
// Copy a because AppendTo below can mutate a.
|
||||
aCopy := a.Clone()
|
||||
// TODO: optimize it
|
||||
for _, x := range aCopy.AppendTo(nil) {
|
||||
s.Del(x)
|
||||
}
|
||||
}
|
||||
|
||||
// Equal returns true if s contains the same items as a.
|
||||
func (s *Set) Equal(a *Set) bool {
|
||||
if s.Len() != a.Len() {
|
||||
return false
|
||||
}
|
||||
// Copy a because AppendTo below can mutate a
|
||||
aCopy := a.Clone()
|
||||
// TODO: optimize it
|
||||
for _, x := range aCopy.AppendTo(nil) {
|
||||
if !s.Has(x) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type bucket32 struct {
|
||||
hi uint32
|
||||
b16his []uint16
|
||||
buckets []*bucket16
|
||||
skipSmallPool bool
|
||||
smallPoolLen int
|
||||
hi uint32
|
||||
b16his []uint16
|
||||
buckets []*bucket16
|
||||
smallPool [14]uint32
|
||||
}
|
||||
|
||||
func (b *bucket32) sizeBytes() uint64 {
|
||||
n := uint64(unsafe.Sizeof(*b))
|
||||
n += 2 * uint64(len(b.b16his))
|
||||
for _, b := range b.buckets {
|
||||
n += uint64(unsafe.Sizeof(b))
|
||||
n += b.sizeBytes()
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (b *bucket32) clone() *bucket32 {
|
||||
var dst bucket32
|
||||
dst.skipSmallPool = b.skipSmallPool
|
||||
dst.smallPoolLen = b.smallPoolLen
|
||||
dst.hi = b.hi
|
||||
dst.b16his = append(dst.b16his[:0], b.b16his...)
|
||||
dst.buckets = make([]*bucket16, len(b.buckets))
|
||||
dst.smallPool = b.smallPool
|
||||
for i, b16 := range b.buckets {
|
||||
dst.buckets[i] = b16.clone()
|
||||
}
|
||||
@@ -156,6 +310,9 @@ func (b *bucket32) Swap(i, j int) {
|
||||
const maxUnsortedBuckets = 32
|
||||
|
||||
func (b *bucket32) add(x uint32) bool {
|
||||
if !b.skipSmallPool {
|
||||
return b.addToSmallPool(x)
|
||||
}
|
||||
hi := uint16(x >> 16)
|
||||
lo := uint16(x)
|
||||
if len(b.buckets) > maxUnsortedBuckets {
|
||||
@@ -170,6 +327,23 @@ func (b *bucket32) add(x uint32) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *bucket32) addToSmallPool(x uint32) bool {
|
||||
if b.hasInSmallPool(x) {
|
||||
return false
|
||||
}
|
||||
if b.smallPoolLen < len(b.smallPool) {
|
||||
b.smallPool[b.smallPoolLen] = x
|
||||
b.smallPoolLen++
|
||||
return true
|
||||
}
|
||||
b.skipSmallPool = true
|
||||
b.smallPoolLen = 0
|
||||
for _, v := range b.smallPool[:] {
|
||||
b.add(v)
|
||||
}
|
||||
return b.add(x)
|
||||
}
|
||||
|
||||
func (b *bucket32) addAllocSmall(hi, lo uint16) {
|
||||
var b16 bucket16
|
||||
_ = b16.add(lo)
|
||||
@@ -191,6 +365,7 @@ func (b *bucket32) addSlow(hi, lo uint16) bool {
|
||||
|
||||
func (b *bucket32) addAllocBig(hi, lo uint16, n int) {
|
||||
if n < 0 {
|
||||
// This is a hint to Go compiler to remove automatic bounds checks below.
|
||||
return
|
||||
}
|
||||
var b16 bucket16
|
||||
@@ -207,6 +382,9 @@ func (b *bucket32) addAllocBig(hi, lo uint16, n int) {
|
||||
}
|
||||
|
||||
func (b *bucket32) has(x uint32) bool {
|
||||
if !b.skipSmallPool {
|
||||
return b.hasInSmallPool(x)
|
||||
}
|
||||
hi := uint16(x >> 16)
|
||||
lo := uint16(x)
|
||||
if len(b.buckets) > maxUnsortedBuckets {
|
||||
@@ -220,6 +398,15 @@ func (b *bucket32) has(x uint32) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *bucket32) hasInSmallPool(x uint32) bool {
|
||||
for _, v := range b.smallPool[:b.smallPoolLen] {
|
||||
if v == x {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *bucket32) hasSlow(hi, lo uint16) bool {
|
||||
n := binarySearch16(b.b16his, hi)
|
||||
if n < 0 || n >= len(b.b16his) || b.b16his[n] != hi {
|
||||
@@ -229,6 +416,9 @@ func (b *bucket32) hasSlow(hi, lo uint16) bool {
|
||||
}
|
||||
|
||||
func (b *bucket32) del(x uint32) bool {
|
||||
if !b.skipSmallPool {
|
||||
return b.delFromSmallPool(x)
|
||||
}
|
||||
hi := uint16(x >> 16)
|
||||
lo := uint16(x)
|
||||
if len(b.buckets) > maxUnsortedBuckets {
|
||||
@@ -242,6 +432,21 @@ func (b *bucket32) del(x uint32) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *bucket32) delFromSmallPool(x uint32) bool {
|
||||
idx := -1
|
||||
for i, v := range b.smallPool[:b.smallPoolLen] {
|
||||
if v == x {
|
||||
idx = i
|
||||
}
|
||||
}
|
||||
if idx < 0 {
|
||||
return false
|
||||
}
|
||||
copy(b.smallPool[idx:], b.smallPool[idx+1:])
|
||||
b.smallPoolLen--
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *bucket32) delSlow(hi, lo uint16) bool {
|
||||
n := binarySearch16(b.b16his, hi)
|
||||
if n < 0 || n >= len(b.b16his) || b.b16his[n] != hi {
|
||||
@@ -251,6 +456,18 @@ func (b *bucket32) delSlow(hi, lo uint16) bool {
|
||||
}
|
||||
|
||||
func (b *bucket32) appendTo(dst []uint64) []uint64 {
|
||||
if !b.skipSmallPool {
|
||||
a := b.smallPool[:b.smallPoolLen]
|
||||
if len(a) > 1 {
|
||||
sort.Slice(a, func(i, j int) bool { return a[i] < a[j] })
|
||||
}
|
||||
hi := uint64(b.hi) << 32
|
||||
for _, lo32 := range a {
|
||||
v := hi | uint64(lo32)
|
||||
dst = append(dst, v)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
if len(b.buckets) <= maxUnsortedBuckets && !sort.IsSorted(b) {
|
||||
sort.Sort(b)
|
||||
}
|
||||
@@ -270,6 +487,10 @@ type bucket16 struct {
|
||||
bits [wordsPerBucket]uint64
|
||||
}
|
||||
|
||||
func (b *bucket16) sizeBytes() uint64 {
|
||||
return uint64(unsafe.Sizeof(*b))
|
||||
}
|
||||
|
||||
func (b *bucket16) clone() *bucket16 {
|
||||
var dst bucket16
|
||||
copy(dst.bits[:], b.bits[:])
|
||||
|
||||
@@ -3,13 +3,14 @@ package uint64set
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSetBasicOps(t *testing.T) {
|
||||
for _, itemsCount := range []int{1e2, 1e3, 1e4, 1e5, 1e6, maxUnsortedBuckets * bitsPerBucket * 2} {
|
||||
for _, itemsCount := range []int{1, 2, 3, 4, 5, 6, 1e2, 1e3, 1e4, 1e5, 1e6, maxUnsortedBuckets * bitsPerBucket * 2} {
|
||||
t.Run(fmt.Sprintf("items_%d", itemsCount), func(t *testing.T) {
|
||||
testSetBasicOps(t, itemsCount)
|
||||
})
|
||||
@@ -21,13 +22,63 @@ func testSetBasicOps(t *testing.T, itemsCount int) {
|
||||
|
||||
offset := uint64(time.Now().UnixNano())
|
||||
|
||||
// Verify operations on nil set
|
||||
{
|
||||
var sNil *Set
|
||||
if n := sNil.SizeBytes(); n != 0 {
|
||||
t.Fatalf("sNil.SizeBytes must return 0; got %d", n)
|
||||
}
|
||||
if sNil.Has(123) {
|
||||
t.Fatalf("sNil shouldn't contain any item; found 123")
|
||||
}
|
||||
if n := sNil.Len(); n != 0 {
|
||||
t.Fatalf("unexpected sNil.Len(); got %d; want 0", n)
|
||||
}
|
||||
result := sNil.AppendTo(nil)
|
||||
if result != nil {
|
||||
t.Fatalf("sNil.AppendTo(nil) must return nil")
|
||||
}
|
||||
buf := []uint64{1, 2, 3}
|
||||
result = sNil.AppendTo(buf)
|
||||
if !reflect.DeepEqual(result, buf) {
|
||||
t.Fatalf("sNil.AppendTo(buf) must return buf")
|
||||
}
|
||||
sCopy := sNil.Clone()
|
||||
if n := sCopy.Len(); n != 0 {
|
||||
t.Fatalf("unexpected sCopy.Len() from nil set; got %d; want 0", n)
|
||||
}
|
||||
sCopy.Add(123)
|
||||
if n := sCopy.Len(); n != 1 {
|
||||
t.Fatalf("unexpected sCopy.Len() after adding an item; got %d; want 1", n)
|
||||
}
|
||||
sCopy.Add(123)
|
||||
if n := sCopy.Len(); n != 1 {
|
||||
t.Fatalf("unexpected sCopy.Len() after adding an item twice; got %d; want 1", n)
|
||||
}
|
||||
if !sCopy.Has(123) {
|
||||
t.Fatalf("sCopy must contain 123")
|
||||
}
|
||||
sCopy.Del(123)
|
||||
if n := sCopy.Len(); n != 0 {
|
||||
t.Fatalf("unexpected sCopy.Len() after deleting the item; got %d; want 0", n)
|
||||
}
|
||||
sCopy.Del(123)
|
||||
if n := sCopy.Len(); n != 0 {
|
||||
t.Fatalf("unexpected sCopy.Len() after double deleting the item; got %d; want 0", n)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify forward Add
|
||||
itemsCount = (itemsCount / 2) * 2
|
||||
for i := 0; i < itemsCount/2; i++ {
|
||||
s.Add(uint64(i) + offset)
|
||||
}
|
||||
if n := s.Len(); n != itemsCount/2 {
|
||||
t.Fatalf("unexpected s.Len() after forward Add; got %d; want %d", n, itemsCount/2)
|
||||
}
|
||||
if n := s.SizeBytes(); n == 0 {
|
||||
t.Fatalf("s.SizeBytes() must be greater than 0")
|
||||
}
|
||||
|
||||
// Verify backward Add
|
||||
for i := 0; i < itemsCount/2; i++ {
|
||||
@@ -59,7 +110,7 @@ func testSetBasicOps(t *testing.T, itemsCount int) {
|
||||
}
|
||||
}
|
||||
|
||||
// Verify Clone
|
||||
// Verify Clone and Equal
|
||||
sCopy := s.Clone()
|
||||
if n := sCopy.Len(); n != itemsCount {
|
||||
t.Fatalf("unexpected sCopy.Len(); got %d; want %d", n, itemsCount)
|
||||
@@ -69,6 +120,33 @@ func testSetBasicOps(t *testing.T, itemsCount int) {
|
||||
t.Fatalf("missing bit %d on sCopy", uint64(i)+offset)
|
||||
}
|
||||
}
|
||||
if !sCopy.Equal(&s) {
|
||||
t.Fatalf("s must equal to sCopy")
|
||||
}
|
||||
if !s.Equal(sCopy) {
|
||||
t.Fatalf("sCopy must equal to s")
|
||||
}
|
||||
if s.Len() > 0 {
|
||||
var sEmpty Set
|
||||
if s.Equal(&sEmpty) {
|
||||
t.Fatalf("s mustn't equal to sEmpty")
|
||||
}
|
||||
sNew := s.Clone()
|
||||
sNew.Del(offset)
|
||||
if sNew.Equal(&s) {
|
||||
t.Fatalf("sNew mustn't equal to s")
|
||||
}
|
||||
if s.Equal(sNew) {
|
||||
t.Fatalf("s mustn't equal to sNew")
|
||||
}
|
||||
sNew.Add(offset - 123)
|
||||
if sNew.Equal(&s) {
|
||||
t.Fatalf("sNew mustn't equal to s")
|
||||
}
|
||||
if s.Equal(sNew) {
|
||||
t.Fatalf("s mustn't equal to sNew")
|
||||
}
|
||||
}
|
||||
|
||||
// Verify AppendTo
|
||||
a := s.AppendTo(nil)
|
||||
@@ -88,16 +166,96 @@ func testSetBasicOps(t *testing.T, itemsCount int) {
|
||||
}
|
||||
}
|
||||
|
||||
// Verify union
|
||||
{
|
||||
const unionOffset = 12345
|
||||
var s1, s2 Set
|
||||
for i := 0; i < itemsCount; i++ {
|
||||
s1.Add(uint64(i) + offset)
|
||||
s2.Add(uint64(i) + offset + unionOffset)
|
||||
}
|
||||
s1.Union(&s2)
|
||||
expectedLen := 2 * itemsCount
|
||||
if itemsCount > unionOffset {
|
||||
expectedLen = itemsCount + unionOffset
|
||||
}
|
||||
if n := s1.Len(); n != expectedLen {
|
||||
t.Fatalf("unexpected s1.Len() after union; got %d; want %d", n, expectedLen)
|
||||
}
|
||||
|
||||
// Verify union on empty set.
|
||||
var s3 Set
|
||||
s3.Union(&s1)
|
||||
expectedLen = s1.Len()
|
||||
if n := s3.Len(); n != expectedLen {
|
||||
t.Fatalf("unexpected s3.Len() after union with empty set; got %d; want %d", n, expectedLen)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify intersect
|
||||
{
|
||||
const intersectOffset = 12345
|
||||
var s1, s2 Set
|
||||
for i := 0; i < itemsCount; i++ {
|
||||
s1.Add(uint64(i) + offset)
|
||||
s2.Add(uint64(i) + offset + intersectOffset)
|
||||
}
|
||||
s1.Intersect(&s2)
|
||||
expectedLen := 0
|
||||
if itemsCount > intersectOffset {
|
||||
expectedLen = itemsCount - intersectOffset
|
||||
}
|
||||
if n := s1.Len(); n != expectedLen {
|
||||
t.Fatalf("unexpected s1.Len() after intersect; got %d; want %d", n, expectedLen)
|
||||
}
|
||||
|
||||
// Verify intersect on empty set.
|
||||
var s3 Set
|
||||
s2.Intersect(&s3)
|
||||
expectedLen = 0
|
||||
if n := s2.Len(); n != 0 {
|
||||
t.Fatalf("unexpected s3.Len() after intersect with empty set; got %d; want %d", n, expectedLen)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify subtract
|
||||
{
|
||||
const subtractOffset = 12345
|
||||
var s1, s2 Set
|
||||
for i := 0; i < itemsCount; i++ {
|
||||
s1.Add(uint64(i) + offset)
|
||||
s2.Add(uint64(i) + offset + subtractOffset)
|
||||
}
|
||||
s1.Subtract(&s2)
|
||||
expectedLen := itemsCount
|
||||
if itemsCount > subtractOffset {
|
||||
expectedLen = subtractOffset
|
||||
}
|
||||
if n := s1.Len(); n != expectedLen {
|
||||
t.Fatalf("unexpected s1.Len() after subtract; got %d; want %d", n, expectedLen)
|
||||
}
|
||||
|
||||
// Verify subtract from empty set.
|
||||
var s3 Set
|
||||
s3.Subtract(&s2)
|
||||
expectedLen = 0
|
||||
if n := s3.Len(); n != 0 {
|
||||
t.Fatalf("unexpected s3.Len() after subtract from empty set; got %d; want %d", n, expectedLen)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify Del
|
||||
itemsDeleted := 0
|
||||
for i := itemsCount / 2; i < itemsCount-itemsCount/4; i++ {
|
||||
s.Del(uint64(i) + offset)
|
||||
itemsDeleted++
|
||||
}
|
||||
if n := s.Len(); n != itemsCount-itemsCount/4 {
|
||||
t.Fatalf("unexpected s.Len() after Del; got %d; want %d", n, itemsCount-itemsCount/4)
|
||||
if n := s.Len(); n != itemsCount-itemsDeleted {
|
||||
t.Fatalf("unexpected s.Len() after Del; got %d; want %d", n, itemsCount-itemsDeleted)
|
||||
}
|
||||
a = s.AppendTo(a[:0])
|
||||
if len(a) != itemsCount-itemsCount/4 {
|
||||
t.Fatalf("unexpected len of exported array; got %d; want %d", len(a), itemsCount-itemsCount/4)
|
||||
if len(a) != itemsCount-itemsDeleted {
|
||||
t.Fatalf("unexpected len of exported array; got %d; want %d", len(a), itemsCount-itemsDeleted)
|
||||
}
|
||||
m = make(map[uint64]bool)
|
||||
for _, x := range a {
|
||||
@@ -121,8 +279,8 @@ func testSetBasicOps(t *testing.T, itemsCount int) {
|
||||
s.Del(uint64(i) + offset)
|
||||
s.Del(uint64(i) + offset + uint64(itemsCount))
|
||||
}
|
||||
if n := s.Len(); n != itemsCount-itemsCount/4 {
|
||||
t.Fatalf("unexpected s.Len() after Del for non-existing items; got %d; want %d", n, itemsCount-itemsCount/4)
|
||||
if n := s.Len(); n != itemsCount-itemsDeleted {
|
||||
t.Fatalf("unexpected s.Len() after Del for non-existing items; got %d; want %d", n, itemsCount-itemsDeleted)
|
||||
}
|
||||
|
||||
// Verify sCopy has the original data
|
||||
@@ -155,6 +313,9 @@ func testSetSparseItems(t *testing.T, itemsCount int) {
|
||||
if n := s.Len(); n != len(m) {
|
||||
t.Fatalf("unexpected Len(); got %d; want %d", n, len(m))
|
||||
}
|
||||
if n := s.SizeBytes(); n == 0 {
|
||||
t.Fatalf("SizeBytes() must return value greater than 0")
|
||||
}
|
||||
|
||||
// Check Has
|
||||
for x := range m {
|
||||
|
||||
1401
vendor/cloud.google.com/go/CHANGES.md
generated
vendored
Normal file
1401
vendor/cloud.google.com/go/CHANGES.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
44
vendor/cloud.google.com/go/CODE_OF_CONDUCT.md
generated
vendored
Normal file
44
vendor/cloud.google.com/go/CODE_OF_CONDUCT.md
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
# Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project,
|
||||
and in the interest of fostering an open and welcoming community,
|
||||
we pledge to respect all people who contribute through reporting issues,
|
||||
posting feature requests, updating documentation,
|
||||
submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project
|
||||
a harassment-free experience for everyone,
|
||||
regardless of level of experience, gender, gender identity and expression,
|
||||
sexual orientation, disability, personal appearance,
|
||||
body size, race, ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information,
|
||||
such as physical or electronic
|
||||
addresses, without explicit permission
|
||||
* Other unethical or unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct.
|
||||
By adopting this Code of Conduct,
|
||||
project maintainers commit themselves to fairly and consistently
|
||||
applying these principles to every aspect of managing this project.
|
||||
Project maintainers who do not follow or enforce the Code of Conduct
|
||||
may be permanently removed from the project team.
|
||||
|
||||
This code of conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior
|
||||
may be reported by opening an issue
|
||||
or contacting one or more of the project maintainers.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
|
||||
available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
|
||||
|
||||
306
vendor/cloud.google.com/go/CONTRIBUTING.md
generated
vendored
Normal file
306
vendor/cloud.google.com/go/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,306 @@
|
||||
# Contributing
|
||||
|
||||
1. [Install Go](https://golang.org/dl/).
|
||||
1. Ensure that your `GOBIN` directory (by default `$(go env GOPATH)/bin`)
|
||||
is in your `PATH`.
|
||||
1. Check it's working by running `go version`.
|
||||
* If it doesn't work, check the install location, usually
|
||||
`/usr/local/go`, is on your `PATH`.
|
||||
|
||||
1. Sign one of the
|
||||
[contributor license agreements](#contributor-license-agreements) below.
|
||||
|
||||
1. Run `go get golang.org/x/review/git-codereview && go install golang.org/x/review/git-codereview`
|
||||
to install the code reviewing tool.
|
||||
|
||||
1. Ensure it's working by running `git codereview` (check your `PATH` if
|
||||
not).
|
||||
|
||||
1. If you would like, you may want to set up aliases for `git-codereview`,
|
||||
such that `git codereview change` becomes `git change`. See the
|
||||
[godoc](https://godoc.org/golang.org/x/review/git-codereview) for details.
|
||||
|
||||
* Should you run into issues with the `git-codereview` tool, please note
|
||||
that all error messages will assume that you have set up these aliases.
|
||||
|
||||
1. Change to a directory of your choosing and clone the repo.
|
||||
|
||||
```
|
||||
cd ~/code
|
||||
git clone https://code.googlesource.com/gocloud
|
||||
```
|
||||
|
||||
* If you have already checked out the source, make sure that the remote
|
||||
`git` `origin` is https://code.googlesource.com/gocloud:
|
||||
|
||||
```
|
||||
git remote -v
|
||||
# ...
|
||||
git remote set-url origin https://code.googlesource.com/gocloud
|
||||
```
|
||||
|
||||
* The project uses [Go Modules](https://blog.golang.org/using-go-modules)
|
||||
for dependency management See
|
||||
[`gopls`](https://github.com/golang/go/wiki/gopls) for making your editor
|
||||
work with modules.
|
||||
|
||||
1. Change to the project directory:
|
||||
|
||||
```
|
||||
cd ~/code/gocloud
|
||||
```
|
||||
|
||||
1. Make sure your `git` auth is configured correctly by visiting
|
||||
https://code.googlesource.com, clicking "Generate Password" at the top-right,
|
||||
and following the directions. Otherwise, `git codereview mail` in the next step
|
||||
will fail.
|
||||
|
||||
1. Now you are ready to make changes. Don't create a new branch or make commits in the traditional
|
||||
way. Use the following`git codereview` commands to create a commit and create a Gerrit CL:
|
||||
|
||||
```
|
||||
git codereview change <branch-name> # Use this instead of git checkout -b <branch-name>
|
||||
# Make changes.
|
||||
git add ...
|
||||
git codereview change # Use this instead of git commit
|
||||
git codereview mail # If this fails, the error message will contain instructions to fix it.
|
||||
```
|
||||
|
||||
* This will create a new `git` branch for you to develop on. Once your
|
||||
change is merged, you can delete this branch.
|
||||
|
||||
1. As you make changes for code review, ammend the commit and re-mail the
|
||||
change:
|
||||
|
||||
```
|
||||
# Make more changes.
|
||||
git add ...
|
||||
git codereview change
|
||||
git codereview mail
|
||||
```
|
||||
|
||||
* **Warning**: do not change the `Change-Id` at the bottom of the commit
|
||||
message - it's how Gerrit knows which change this is (or if it's new).
|
||||
|
||||
* When you fixes issues from code review, respond to each code review
|
||||
message then click **Reply** at the top of the page.
|
||||
|
||||
* Each new mailed amendment will create a new patch set for
|
||||
your change in Gerrit. Patch sets can be compared and reviewed.
|
||||
|
||||
* **Note**: if your change includes a breaking change, our breaking change
|
||||
detector will cause CI/CD to fail. If your breaking change is acceptable
|
||||
in some way, add a `BREAKING_CHANGE_ACCEPTABLE=<reason>` line to the commit
|
||||
message to cause the detector not to be run and to make it clear why that is
|
||||
acceptable.
|
||||
|
||||
1. Finally, add reviewers to your CL when it's ready for review. Reviewers will
|
||||
not be added automatically. If you're not sure who to add for your code review,
|
||||
add deklerk@, tbp@, cbro@, and codyoss@.
|
||||
|
||||
|
||||
## Integration Tests
|
||||
|
||||
In addition to the unit tests, you may run the integration test suite. These
|
||||
directions describe setting up your environment to run integration tests for
|
||||
_all_ packages: note that many of these instructions may be redundant if you
|
||||
intend only to run integration tests on a single package.
|
||||
|
||||
#### GCP Setup
|
||||
|
||||
To run the integrations tests, creation and configuration of two projects in
|
||||
the Google Developers Console is required: one specifically for Firestore
|
||||
integration tests, and another for all other integration tests. We'll refer to
|
||||
these projects as "general project" and "Firestore project".
|
||||
|
||||
After creating each project, you must [create a service account](https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount)
|
||||
for each project. Ensure the project-level **Owner**
|
||||
[IAM role](console.cloud.google.com/iam-admin/iam/project) role is added to
|
||||
each service account. During the creation of the service account, you should
|
||||
download the JSON credential file for use later.
|
||||
|
||||
Next, ensure the following APIs are enabled in the general project:
|
||||
|
||||
- BigQuery API
|
||||
- BigQuery Data Transfer API
|
||||
- Cloud Dataproc API
|
||||
- Cloud Dataproc Control API Private
|
||||
- Cloud Datastore API
|
||||
- Cloud Firestore API
|
||||
- Cloud Key Management Service (KMS) API
|
||||
- Cloud Natural Language API
|
||||
- Cloud OS Login API
|
||||
- Cloud Pub/Sub API
|
||||
- Cloud Resource Manager API
|
||||
- Cloud Spanner API
|
||||
- Cloud Speech API
|
||||
- Cloud Translation API
|
||||
- Cloud Video Intelligence API
|
||||
- Cloud Vision API
|
||||
- Compute Engine API
|
||||
- Compute Engine Instance Group Manager API
|
||||
- Container Registry API
|
||||
- Firebase Rules API
|
||||
- Google Cloud APIs
|
||||
- Google Cloud Deployment Manager V2 API
|
||||
- Google Cloud SQL
|
||||
- Google Cloud Storage
|
||||
- Google Cloud Storage JSON API
|
||||
- Google Compute Engine Instance Group Updater API
|
||||
- Google Compute Engine Instance Groups API
|
||||
- Kubernetes Engine API
|
||||
- Stackdriver Error Reporting API
|
||||
|
||||
Next, create a Datastore database in the general project, and a Firestore
|
||||
database in the Firestore project.
|
||||
|
||||
Finally, in the general project, create an API key for the translate API:
|
||||
|
||||
- Go to GCP Developer Console.
|
||||
- Navigate to APIs & Services > Credentials.
|
||||
- Click Create Credentials > API Key.
|
||||
- Save this key for use in `GCLOUD_TESTS_API_KEY` as described below.
|
||||
|
||||
#### Local Setup
|
||||
|
||||
Once the two projects are created and configured, set the following environment
|
||||
variables:
|
||||
|
||||
- `GCLOUD_TESTS_GOLANG_PROJECT_ID`: Developers Console project's ID (e.g.
|
||||
bamboo-shift-455) for the general project.
|
||||
- `GCLOUD_TESTS_GOLANG_KEY`: The path to the JSON key file of the general
|
||||
project's service account.
|
||||
- `GCLOUD_TESTS_GOLANG_FIRESTORE_PROJECT_ID`: Developers Console project's ID
|
||||
(e.g. doorway-cliff-677) for the Firestore project.
|
||||
- `GCLOUD_TESTS_GOLANG_FIRESTORE_KEY`: The path to the JSON key file of the
|
||||
Firestore project's service account.
|
||||
- `GCLOUD_TESTS_GOLANG_KEYRING`: The full name of the keyring for the tests,
|
||||
in the form
|
||||
"projects/P/locations/L/keyRings/R". The creation of this is described below.
|
||||
- `GCLOUD_TESTS_API_KEY`: API key for using the Translate API.
|
||||
- `GCLOUD_TESTS_GOLANG_ZONE`: Compute Engine zone.
|
||||
|
||||
Install the [gcloud command-line tool][gcloudcli] to your machine and use it to
|
||||
create some resources used in integration tests.
|
||||
|
||||
From the project's root directory:
|
||||
|
||||
``` sh
|
||||
# Sets the default project in your env.
|
||||
$ gcloud config set project $GCLOUD_TESTS_GOLANG_PROJECT_ID
|
||||
|
||||
# Authenticates the gcloud tool with your account.
|
||||
$ gcloud auth login
|
||||
|
||||
# Create the indexes used in the datastore integration tests.
|
||||
$ gcloud datastore indexes create datastore/testdata/index.yaml
|
||||
|
||||
# Creates a Google Cloud storage bucket with the same name as your test project,
|
||||
# and with the Stackdriver Logging service account as owner, for the sink
|
||||
# integration tests in logging.
|
||||
$ gsutil mb gs://$GCLOUD_TESTS_GOLANG_PROJECT_ID
|
||||
$ gsutil acl ch -g cloud-logs@google.com:O gs://$GCLOUD_TESTS_GOLANG_PROJECT_ID
|
||||
|
||||
# Creates a PubSub topic for integration tests of storage notifications.
|
||||
$ gcloud beta pubsub topics create go-storage-notification-test
|
||||
# Next, go to the Pub/Sub dashboard in GCP console. Authorize the user
|
||||
# "service-<numberic project id>@gs-project-accounts.iam.gserviceaccount.com"
|
||||
# as a publisher to that topic.
|
||||
|
||||
# Creates a Spanner instance for the spanner integration tests.
|
||||
$ gcloud beta spanner instances create go-integration-test --config regional-us-central1 --nodes 10 --description 'Instance for go client test'
|
||||
# NOTE: Spanner instances are priced by the node-hour, so you may want to
|
||||
# delete the instance after testing with 'gcloud beta spanner instances delete'.
|
||||
|
||||
$ export MY_KEYRING=some-keyring-name
|
||||
$ export MY_LOCATION=global
|
||||
# Creates a KMS keyring, in the same location as the default location for your
|
||||
# project's buckets.
|
||||
$ gcloud kms keyrings create $MY_KEYRING --location $MY_LOCATION
|
||||
# Creates two keys in the keyring, named key1 and key2.
|
||||
$ gcloud kms keys create key1 --keyring $MY_KEYRING --location $MY_LOCATION --purpose encryption
|
||||
$ gcloud kms keys create key2 --keyring $MY_KEYRING --location $MY_LOCATION --purpose encryption
|
||||
# Sets the GCLOUD_TESTS_GOLANG_KEYRING environment variable.
|
||||
$ export GCLOUD_TESTS_GOLANG_KEYRING=projects/$GCLOUD_TESTS_GOLANG_PROJECT_ID/locations/$MY_LOCATION/keyRings/$MY_KEYRING
|
||||
# Authorizes Google Cloud Storage to encrypt and decrypt using key1.
|
||||
gsutil kms authorize -p $GCLOUD_TESTS_GOLANG_PROJECT_ID -k $GCLOUD_TESTS_GOLANG_KEYRING/cryptoKeys/key1
|
||||
```
|
||||
|
||||
#### Running
|
||||
|
||||
Once you've done the necessary setup, you can run the integration tests by
|
||||
running:
|
||||
|
||||
``` sh
|
||||
$ go test -v cloud.google.com/go/...
|
||||
```
|
||||
|
||||
#### Replay
|
||||
|
||||
Some packages can record the RPCs during integration tests to a file for
|
||||
subsequent replay. To record, pass the `-record` flag to `go test`. The
|
||||
recording will be saved to the _package_`.replay` file. To replay integration
|
||||
tests from a saved recording, the replay file must be present, the `-short`
|
||||
flag must be passed to `go test`, and the `GCLOUD_TESTS_GOLANG_ENABLE_REPLAY`
|
||||
environment variable must have a non-empty value.
|
||||
|
||||
## Contributor License Agreements
|
||||
|
||||
Before we can accept your pull requests you'll need to sign a Contributor
|
||||
License Agreement (CLA):
|
||||
|
||||
- **If you are an individual writing original source code** and **you own the
|
||||
intellectual property**, then you'll need to sign an [individual CLA][indvcla].
|
||||
- **If you work for a company that wants to allow you to contribute your
|
||||
work**, then you'll need to sign a [corporate CLA][corpcla].
|
||||
|
||||
You can sign these electronically (just scroll to the bottom). After that,
|
||||
we'll be able to accept your pull requests.
|
||||
|
||||
## Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project,
|
||||
and in the interest of fostering an open and welcoming community,
|
||||
we pledge to respect all people who contribute through reporting issues,
|
||||
posting feature requests, updating documentation,
|
||||
submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project
|
||||
a harassment-free experience for everyone,
|
||||
regardless of level of experience, gender, gender identity and expression,
|
||||
sexual orientation, disability, personal appearance,
|
||||
body size, race, ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information,
|
||||
such as physical or electronic
|
||||
addresses, without explicit permission
|
||||
* Other unethical or unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct.
|
||||
By adopting this Code of Conduct,
|
||||
project maintainers commit themselves to fairly and consistently
|
||||
applying these principles to every aspect of managing this project.
|
||||
Project maintainers who do not follow or enforce the Code of Conduct
|
||||
may be permanently removed from the project team.
|
||||
|
||||
This code of conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior
|
||||
may be reported by opening an issue
|
||||
or contacting one or more of the project maintainers.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
|
||||
available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
|
||||
|
||||
[gcloudcli]: https://developers.google.com/cloud/sdk/gcloud/
|
||||
[indvcla]: https://developers.google.com/open-source/cla/individual
|
||||
[corpcla]: https://developers.google.com/open-source/cla/corporate
|
||||
202
vendor/cloud.google.com/go/LICENSE
generated
vendored
Normal file
202
vendor/cloud.google.com/go/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
179
vendor/cloud.google.com/go/README.md
generated
vendored
Normal file
179
vendor/cloud.google.com/go/README.md
generated
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
# Google Cloud Client Libraries for Go
|
||||
|
||||
[](https://godoc.org/cloud.google.com/go)
|
||||
|
||||
Go packages for [Google Cloud Platform](https://cloud.google.com) services.
|
||||
|
||||
``` go
|
||||
import "cloud.google.com/go"
|
||||
```
|
||||
|
||||
To install the packages on your system, *do not clone the repo*. Instead:
|
||||
|
||||
1. Change to your project directory:
|
||||
|
||||
```
|
||||
cd /my/cloud/project
|
||||
```
|
||||
1. Get the package you want to use. Some products have their own module, so it's
|
||||
best to `go get` the package(s) you want to use:
|
||||
|
||||
```
|
||||
$ go get cloud.google.com/go/firestore # Replace with the package you want to use.
|
||||
```
|
||||
|
||||
**NOTE:** Some of these packages are under development, and may occasionally
|
||||
make backwards-incompatible changes.
|
||||
|
||||
**NOTE:** Github repo is a mirror of [https://code.googlesource.com/gocloud](https://code.googlesource.com/gocloud).
|
||||
|
||||
## Supported APIs
|
||||
|
||||
Google API | Status | Package
|
||||
------------------------------------------------|--------------|-----------------------------------------------------------
|
||||
[Asset][cloud-asset] | alpha | [`cloud.google.com/go/asset/v1beta`](https://godoc.org/cloud.google.com/go/asset/v1beta)
|
||||
[Automl][cloud-automl] | stable | [`cloud.google.com/go/automl/apiv1`](https://godoc.org/cloud.google.com/go/automl/apiv1)
|
||||
[BigQuery][cloud-bigquery] | stable | [`cloud.google.com/go/bigquery`](https://godoc.org/cloud.google.com/go/bigquery)
|
||||
[Bigtable][cloud-bigtable] | stable | [`cloud.google.com/go/bigtable`](https://godoc.org/cloud.google.com/go/bigtable)
|
||||
[Cloudbuild][cloud-build] | alpha | [`cloud.google.com/go/cloudbuild/apiv1`](https://godoc.org/cloud.google.com/go/cloudbuild/apiv1)
|
||||
[Cloudtasks][cloud-tasks] | stable | [`cloud.google.com/go/cloudtasks/apiv2`](https://godoc.org/cloud.google.com/go/cloudtasks/apiv2)
|
||||
[Container][cloud-container] | stable | [`cloud.google.com/go/container/apiv1`](https://godoc.org/cloud.google.com/go/container/apiv1)
|
||||
[ContainerAnalysis][cloud-containeranalysis] | beta | [`cloud.google.com/go/containeranalysis/apiv1`](https://godoc.org/cloud.google.com/go/containeranalysis/apiv1)
|
||||
[Dataproc][cloud-dataproc] | stable | [`cloud.google.com/go/dataproc/apiv1`](https://godoc.org/cloud.google.com/go/dataproc/apiv1)
|
||||
[Datastore][cloud-datastore] | stable | [`cloud.google.com/go/datastore`](https://godoc.org/cloud.google.com/go/datastore)
|
||||
[Debugger][cloud-debugger] | alpha | [`cloud.google.com/go/debugger/apiv2`](https://godoc.org/cloud.google.com/go/debugger/apiv2)
|
||||
[Dialogflow][cloud-dialogflow] | alpha | [`cloud.google.com/go/dialogflow/apiv2`](https://godoc.org/cloud.google.com/go/dialogflow/apiv2)
|
||||
[Data Loss Prevention][cloud-dlp] | alpha | [`cloud.google.com/go/dlp/apiv2`](https://godoc.org/cloud.google.com/go/dlp/apiv2)
|
||||
[ErrorReporting][cloud-errors] | alpha | [`cloud.google.com/go/errorreporting`](https://godoc.org/cloud.google.com/go/errorreporting)
|
||||
[Firestore][cloud-firestore] | stable | [`cloud.google.com/go/firestore`](https://godoc.org/cloud.google.com/go/firestore)
|
||||
[IAM][cloud-iam] | stable | [`cloud.google.com/go/iam`](https://godoc.org/cloud.google.com/go/iam)
|
||||
[IoT][cloud-iot] | alpha | [`cloud.google.com/iot/apiv1`](https://godoc.org/cloud.google.com/iot/apiv1)
|
||||
[IRM][cloud-irm] | alpha | [`cloud.google.com/irm/apiv1alpha2`](https://godoc.org/cloud.google.com/irm/apiv1alpha2)
|
||||
[KMS][cloud-kms] | stable | [`cloud.google.com/go/kms`](https://godoc.org/cloud.google.com/go/kms)
|
||||
[Natural Language][cloud-natural-language] | stable | [`cloud.google.com/go/language/apiv1`](https://godoc.org/cloud.google.com/go/language/apiv1)
|
||||
[Logging][cloud-logging] | stable | [`cloud.google.com/go/logging`](https://godoc.org/cloud.google.com/go/logging)
|
||||
[Memorystore][cloud-memorystore] | alpha | [`cloud.google.com/go/redis/apiv1`](https://godoc.org/cloud.google.com/go/redis/apiv1)
|
||||
[Monitoring][cloud-monitoring] | alpha | [`cloud.google.com/go/monitoring/apiv3`](https://godoc.org/cloud.google.com/go/monitoring/apiv3)
|
||||
[OS Login][cloud-oslogin] | alpha | [`cloud.google.com/go/oslogin/apiv1`](https://godoc.org/cloud.google.com/go/oslogin/apiv1)
|
||||
[Pub/Sub][cloud-pubsub] | stable | [`cloud.google.com/go/pubsub`](https://godoc.org/cloud.google.com/go/pubsub)
|
||||
[Phishing Protection][cloud-phishingprotection] | alpha | [`cloud.google.com/go/phishingprotection/apiv1beta1`](https://godoc.org/cloud.google.com/go/phishingprotection/apiv1beta1)
|
||||
[reCAPTCHA Enterprise][cloud-recaptcha] | alpha | [`cloud.google.com/go/recaptchaenterprise/apiv1beta1`](https://godoc.org/cloud.google.com/go/recaptchaenterprise/apiv1beta1)
|
||||
[Recommender][cloud-recommender] | beta | [`cloud.google.com/go/recommender/apiv1beta1`](https://godoc.org/cloud.google.com/go/recommender/apiv1beta1)
|
||||
[Scheduler][cloud-scheduler] | stable | [`cloud.google.com/go/scheduler/apiv1`](https://godoc.org/cloud.google.com/go/scheduler/apiv1)
|
||||
[Securitycenter][cloud-securitycenter] | alpha | [`cloud.google.com/go/securitycenter/apiv1`](https://godoc.org/cloud.google.com/go/securitycenter/apiv1)
|
||||
[Spanner][cloud-spanner] | stable | [`cloud.google.com/go/spanner`](https://godoc.org/cloud.google.com/go/spanner)
|
||||
[Speech][cloud-speech] | stable | [`cloud.google.com/go/speech/apiv1`](https://godoc.org/cloud.google.com/go/speech/apiv1)
|
||||
[Storage][cloud-storage] | stable | [`cloud.google.com/go/storage`](https://godoc.org/cloud.google.com/go/storage)
|
||||
[Talent][cloud-talent] | alpha | [`cloud.google.com/go/talent/apiv4beta1`](https://godoc.org/cloud.google.com/go/talent/apiv4beta1)
|
||||
[Text To Speech][cloud-texttospeech] | alpha | [`cloud.google.com/go/texttospeech/apiv1`](https://godoc.org/cloud.google.com/go/texttospeech/apiv1)
|
||||
[Trace][cloud-trace] | alpha | [`cloud.google.com/go/trace/apiv2`](https://godoc.org/cloud.google.com/go/trace/apiv2)
|
||||
[Translate][cloud-translate] | stable | [`cloud.google.com/go/translate`](https://godoc.org/cloud.google.com/go/translate)
|
||||
[Video Intelligence][cloud-video] | alpha | [`cloud.google.com/go/videointelligence/apiv1beta1`](https://godoc.org/cloud.google.com/go/videointelligence/apiv1beta1)
|
||||
[Vision][cloud-vision] | stable | [`cloud.google.com/go/vision/apiv1`](https://godoc.org/cloud.google.com/go/vision/apiv1)
|
||||
[Webrisk][cloud-webrisk] | alpha | [`cloud.google.com/go/webrisk/apiv1beta1`](https://godoc.org/cloud.google.com/go/webrisk/apiv1beta1)
|
||||
|
||||
> **Alpha status**: the API is still being actively developed. As a
|
||||
> result, it might change in backward-incompatible ways and is not recommended
|
||||
> for production use.
|
||||
>
|
||||
> **Beta status**: the API is largely complete, but still has outstanding
|
||||
> features and bugs to be addressed. There may be minor backwards-incompatible
|
||||
> changes where necessary.
|
||||
>
|
||||
> **Stable status**: the API is mature and ready for production use. We will
|
||||
> continue addressing bugs and feature requests.
|
||||
|
||||
Documentation and examples are available at [godoc.org/cloud.google.com/go](https://godoc.org/cloud.google.com/go)
|
||||
|
||||
## Go Versions Supported
|
||||
|
||||
We support the two most recent major versions of Go. If Google App Engine uses
|
||||
an older version, we support that as well.
|
||||
|
||||
## Authorization
|
||||
|
||||
By default, each API will use [Google Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials)
|
||||
for authorization credentials used in calling the API endpoints. This will allow your
|
||||
application to run in many environments without requiring explicit configuration.
|
||||
|
||||
[snip]:# (auth)
|
||||
```go
|
||||
client, err := storage.NewClient(ctx)
|
||||
```
|
||||
|
||||
To authorize using a
|
||||
[JSON key file](https://cloud.google.com/iam/docs/managing-service-account-keys),
|
||||
pass
|
||||
[`option.WithCredentialsFile`](https://godoc.org/google.golang.org/api/option#WithCredentialsFile)
|
||||
to the `NewClient` function of the desired package. For example:
|
||||
|
||||
[snip]:# (auth-JSON)
|
||||
```go
|
||||
client, err := storage.NewClient(ctx, option.WithCredentialsFile("path/to/keyfile.json"))
|
||||
```
|
||||
|
||||
You can exert more control over authorization by using the
|
||||
[`golang.org/x/oauth2`](https://godoc.org/golang.org/x/oauth2) package to
|
||||
create an `oauth2.TokenSource`. Then pass
|
||||
[`option.WithTokenSource`](https://godoc.org/google.golang.org/api/option#WithTokenSource)
|
||||
to the `NewClient` function:
|
||||
[snip]:# (auth-ts)
|
||||
```go
|
||||
tokenSource := ...
|
||||
client, err := storage.NewClient(ctx, option.WithTokenSource(tokenSource))
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome. Please, see the
|
||||
[CONTRIBUTING](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/CONTRIBUTING.md)
|
||||
document for details. We're using Gerrit for our code reviews. Please don't open pull
|
||||
requests against this repo, new pull requests will be automatically closed.
|
||||
|
||||
Please note that this project is released with a Contributor Code of Conduct.
|
||||
By participating in this project you agree to abide by its terms.
|
||||
See [Contributor Code of Conduct](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/CONTRIBUTING.md#contributor-code-of-conduct)
|
||||
for more information.
|
||||
|
||||
[cloud-asset]: https://cloud.google.com/security-command-center/docs/how-to-asset-inventory
|
||||
[cloud-automl]: https://cloud.google.com/automl
|
||||
[cloud-build]: https://cloud.google.com/cloud-build/
|
||||
[cloud-bigquery]: https://cloud.google.com/bigquery/
|
||||
[cloud-bigtable]: https://cloud.google.com/bigtable/
|
||||
[cloud-container]: https://cloud.google.com/containers/
|
||||
[cloud-containeranalysis]: https://cloud.google.com/container-registry/docs/container-analysis
|
||||
[cloud-dataproc]: https://cloud.google.com/dataproc/
|
||||
[cloud-datastore]: https://cloud.google.com/datastore/
|
||||
[cloud-dialogflow]: https://cloud.google.com/dialogflow-enterprise/
|
||||
[cloud-debugger]: https://cloud.google.com/debugger/
|
||||
[cloud-dlp]: https://cloud.google.com/dlp/
|
||||
[cloud-errors]: https://cloud.google.com/error-reporting/
|
||||
[cloud-firestore]: https://cloud.google.com/firestore/
|
||||
[cloud-iam]: https://cloud.google.com/iam/
|
||||
[cloud-iot]: https://cloud.google.com/iot-core/
|
||||
[cloud-irm]: https://cloud.google.com/incident-response/docs/concepts
|
||||
[cloud-kms]: https://cloud.google.com/kms/
|
||||
[cloud-pubsub]: https://cloud.google.com/pubsub/
|
||||
[cloud-storage]: https://cloud.google.com/storage/
|
||||
[cloud-language]: https://cloud.google.com/natural-language
|
||||
[cloud-logging]: https://cloud.google.com/logging/
|
||||
[cloud-natural-language]: https://cloud.google.com/natural-language/
|
||||
[cloud-memorystore]: https://cloud.google.com/memorystore/
|
||||
[cloud-monitoring]: https://cloud.google.com/monitoring/
|
||||
[cloud-oslogin]: https://cloud.google.com/compute/docs/oslogin/rest
|
||||
[cloud-phishingprotection]: https://cloud.google.com/phishing-protection/
|
||||
[cloud-securitycenter]: https://cloud.google.com/security-command-center/
|
||||
[cloud-scheduler]: https://cloud.google.com/scheduler
|
||||
[cloud-spanner]: https://cloud.google.com/spanner/
|
||||
[cloud-speech]: https://cloud.google.com/speech
|
||||
[cloud-talent]: https://cloud.google.com/solutions/talent-solution/
|
||||
[cloud-tasks]: https://cloud.google.com/tasks/
|
||||
[cloud-texttospeech]: https://cloud.google.com/texttospeech/
|
||||
[cloud-talent]: https://cloud.google.com/solutions/talent-solution/
|
||||
[cloud-trace]: https://cloud.google.com/trace/
|
||||
[cloud-translate]: https://cloud.google.com/translate
|
||||
[cloud-recaptcha]: https://cloud.google.com/recaptcha-enterprise/
|
||||
[cloud-recommender]: https://cloud.google.com/recommendations/
|
||||
[cloud-video]: https://cloud.google.com/video-intelligence/
|
||||
[cloud-vision]: https://cloud.google.com/vision
|
||||
[cloud-webrisk]: https://cloud.google.com/web-risk/
|
||||
153
vendor/cloud.google.com/go/RELEASING.md
generated
vendored
Normal file
153
vendor/cloud.google.com/go/RELEASING.md
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
# Setup from scratch
|
||||
|
||||
1. [Install Go](https://golang.org/dl/).
|
||||
1. Ensure that your `GOBIN` directory (by default `$(go env GOPATH)/bin`)
|
||||
is in your `PATH`.
|
||||
1. Check it's working by running `go version`.
|
||||
* If it doesn't work, check the install location, usually
|
||||
`/usr/local/go`, is on your `PATH`.
|
||||
|
||||
1. Sign one of the
|
||||
[contributor license agreements](#contributor-license-agreements) below.
|
||||
|
||||
1. Run `go get golang.org/x/review/git-codereview && go install golang.org/x/review/git-codereview`
|
||||
to install the code reviewing tool.
|
||||
|
||||
1. Ensure it's working by running `git codereview` (check your `PATH` if
|
||||
not).
|
||||
|
||||
1. If you would like, you may want to set up aliases for `git-codereview`,
|
||||
such that `git codereview change` becomes `git change`. See the
|
||||
[godoc](https://godoc.org/golang.org/x/review/git-codereview) for details.
|
||||
|
||||
* Should you run into issues with the `git-codereview` tool, please note
|
||||
that all error messages will assume that you have set up these aliases.
|
||||
|
||||
1. Change to a directory of your choosing and clone the repo.
|
||||
|
||||
```
|
||||
cd ~/code
|
||||
git clone https://code.googlesource.com/gocloud
|
||||
```
|
||||
|
||||
* If you have already checked out the source, make sure that the remote
|
||||
`git` `origin` is https://code.googlesource.com/gocloud:
|
||||
|
||||
```
|
||||
git remote -v
|
||||
# ...
|
||||
git remote set-url origin https://code.googlesource.com/gocloud
|
||||
```
|
||||
|
||||
* The project uses [Go Modules](https://blog.golang.org/using-go-modules)
|
||||
for dependency management See
|
||||
[`gopls`](https://github.com/golang/go/wiki/gopls) for making your editor
|
||||
work with modules.
|
||||
|
||||
1. Change to the project directory and add the github remote:
|
||||
|
||||
```
|
||||
cd ~/code/gocloud
|
||||
git remote add github https://github.com/googleapis/google-cloud-go
|
||||
```
|
||||
|
||||
1. Make sure your `git` auth is configured correctly by visiting
|
||||
https://code.googlesource.com, clicking "Generate Password" at the top-right,
|
||||
and following the directions. Otherwise, `git codereview mail` in the next step
|
||||
will fail.
|
||||
|
||||
# Which module to release?
|
||||
|
||||
The Go client libraries have several modules. Each module does not strictly
|
||||
correspond to a single library - they correspond to trees of directories. If a
|
||||
file needs to be released, you must release the closest ancestor module.
|
||||
|
||||
To see all modules:
|
||||
|
||||
```
|
||||
$ cat `find . -name go.mod` | grep module
|
||||
module cloud.google.com/go
|
||||
module cloud.google.com/go/bigtable
|
||||
module cloud.google.com/go/firestore
|
||||
module cloud.google.com/go/bigquery
|
||||
module cloud.google.com/go/storage
|
||||
module cloud.google.com/go/datastore
|
||||
module cloud.google.com/go/pubsub
|
||||
module cloud.google.com/go/spanner
|
||||
module cloud.google.com/go/logging
|
||||
```
|
||||
|
||||
The `cloud.google.com/go` is the repository root module. Each other module is
|
||||
a submodule.
|
||||
|
||||
So, if you need to release a change in `bigtable/bttest/inmem.go`, the closest
|
||||
ancestor module is `cloud.google.com/go/bigtable` - so you should release a new
|
||||
version of the `cloud.google.com/go/bigtable` submodule.
|
||||
|
||||
If you need to release a change in `asset/apiv1/asset_client.go`, the closest
|
||||
ancestor module is `cloud.google.com/go` - so you should release a new version
|
||||
of the `cloud.google.com/go` repository root module. Note: releasing
|
||||
`cloud.google.com/go` has no impact on any of the submodules, and vice-versa.
|
||||
They are released entirely independently.
|
||||
|
||||
# How to release `cloud.google.com/go`
|
||||
|
||||
1. Navigate to `~/code/gocloud/` and switch to master.
|
||||
1. `git pull`
|
||||
1. Run `git tag -l | grep -v beta | grep -v alpha` to see all existing releases.
|
||||
The current latest tag `$CV` is the largest tag. It should look something
|
||||
like `vX.Y.Z` (note: ignore all `LIB/vX.Y.Z` tags - these are tags for a
|
||||
specific library, not the module root). We'll call the current version `$CV`
|
||||
and the new version `$NV`.
|
||||
1. On master, run `git log $CV...` to list all the changes since the last
|
||||
release. NOTE: You must manually visually parse out changes to submodules [1]
|
||||
(the `git log` is going to show you things in submodules, which are not going
|
||||
to be part of your release).
|
||||
1. Edit `CHANGES.md` to include a summary of the changes.
|
||||
1. `cd internal/version && go generate && cd -`
|
||||
1. Mail the CL: `git add -A && git change <branch name> && git mail`
|
||||
1. Wait for the CL to be submitted. Once it's submitted, and without submitting
|
||||
any other CLs in the meantime:
|
||||
a. Switch to master.
|
||||
b. `git pull`
|
||||
c. Tag the repo with the next version: `git tag $NV`.
|
||||
d. Push the tag to both remotes:
|
||||
`git push origin $NV`
|
||||
`git push github $NV`
|
||||
1. Update [the releases page](https://github.com/googleapis/google-cloud-go/releases)
|
||||
with the new release, copying the contents of `CHANGES.md`.
|
||||
|
||||
# How to release a submodule
|
||||
|
||||
We have several submodules, including `cloud.google.com/go/logging`,
|
||||
`cloud.google.com/go/datastore`, and so on.
|
||||
|
||||
To release a submodule:
|
||||
|
||||
(these instructions assume we're releasing `cloud.google.com/go/datastore` - adjust accordingly)
|
||||
|
||||
1. Navigate to `~/code/gocloud/` and switch to master.
|
||||
1. `git pull`
|
||||
1. Run `git tag -l | grep datastore | grep -v beta | grep -v alpha` to see all
|
||||
existing releases. The current latest tag `$CV` is the largest tag. It
|
||||
should look something like `datastore/vX.Y.Z`. We'll call the current version
|
||||
`$CV` and the new version `$NV`.
|
||||
1. On master, run `git log $CV.. -- datastore/` to list all the changes to the
|
||||
submodule directory since the last release.
|
||||
1. Edit `datastore/CHANGES.md` to include a summary of the changes.
|
||||
1. `cd internal/version && go generate && cd -`
|
||||
1. Mail the CL: `git add -A && git change <branch name> && git mail`
|
||||
1. Wait for the CL to be submitted. Once it's submitted, and without submitting
|
||||
any other CLs in the meantime:
|
||||
a. Switch to master.
|
||||
b. `git pull`
|
||||
c. Tag the repo with the next version: `git tag $NV`.
|
||||
d. Push the tag to both remotes:
|
||||
`git push origin $NV`
|
||||
`git push github $NV`
|
||||
1. Update [the releases page](https://github.com/googleapis/google-cloud-go/releases)
|
||||
with the new release, copying the contents of `datastore/CHANGES.md`.
|
||||
|
||||
# Appendix
|
||||
|
||||
1: This should get better as submodule tooling matures.
|
||||
12
vendor/cloud.google.com/go/compute/metadata/.repo-metadata.json
generated
vendored
Normal file
12
vendor/cloud.google.com/go/compute/metadata/.repo-metadata.json
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "metadata",
|
||||
"name_pretty": "Google Compute Engine Metadata API",
|
||||
"product_documentation": "https://cloud.google.com/compute/docs/storing-retrieving-metadata",
|
||||
"client_documentation": "https://godoc.org/cloud.google.com/go/compute/metadata",
|
||||
"release_level": "ga",
|
||||
"language": "go",
|
||||
"repo": "googleapis/google-cloud-go",
|
||||
"distribution_name": "cloud.google.com/go/compute/metadata",
|
||||
"api_id": "compute:metadata",
|
||||
"requires_billing": false
|
||||
}
|
||||
526
vendor/cloud.google.com/go/compute/metadata/metadata.go
generated
vendored
Normal file
526
vendor/cloud.google.com/go/compute/metadata/metadata.go
generated
vendored
Normal file
@@ -0,0 +1,526 @@
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package metadata provides access to Google Compute Engine (GCE)
|
||||
// metadata and API service accounts.
|
||||
//
|
||||
// This package is a wrapper around the GCE metadata service,
|
||||
// as documented at https://developers.google.com/compute/docs/metadata.
|
||||
package metadata // import "cloud.google.com/go/compute/metadata"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// metadataIP is the documented metadata server IP address.
|
||||
metadataIP = "169.254.169.254"
|
||||
|
||||
// metadataHostEnv is the environment variable specifying the
|
||||
// GCE metadata hostname. If empty, the default value of
|
||||
// metadataIP ("169.254.169.254") is used instead.
|
||||
// This is variable name is not defined by any spec, as far as
|
||||
// I know; it was made up for the Go package.
|
||||
metadataHostEnv = "GCE_METADATA_HOST"
|
||||
|
||||
userAgent = "gcloud-golang/0.1"
|
||||
)
|
||||
|
||||
type cachedValue struct {
|
||||
k string
|
||||
trim bool
|
||||
mu sync.Mutex
|
||||
v string
|
||||
}
|
||||
|
||||
var (
|
||||
projID = &cachedValue{k: "project/project-id", trim: true}
|
||||
projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
|
||||
instID = &cachedValue{k: "instance/id", trim: true}
|
||||
)
|
||||
|
||||
var (
|
||||
defaultClient = &Client{hc: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 2 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
ResponseHeaderTimeout: 2 * time.Second,
|
||||
},
|
||||
}}
|
||||
subscribeClient = &Client{hc: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 2 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
},
|
||||
}}
|
||||
)
|
||||
|
||||
// NotDefinedError is returned when requested metadata is not defined.
|
||||
//
|
||||
// The underlying string is the suffix after "/computeMetadata/v1/".
|
||||
//
|
||||
// This error is not returned if the value is defined to be the empty
|
||||
// string.
|
||||
type NotDefinedError string
|
||||
|
||||
func (suffix NotDefinedError) Error() string {
|
||||
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
|
||||
}
|
||||
|
||||
func (c *cachedValue) get(cl *Client) (v string, err error) {
|
||||
defer c.mu.Unlock()
|
||||
c.mu.Lock()
|
||||
if c.v != "" {
|
||||
return c.v, nil
|
||||
}
|
||||
if c.trim {
|
||||
v, err = cl.getTrimmed(c.k)
|
||||
} else {
|
||||
v, err = cl.Get(c.k)
|
||||
}
|
||||
if err == nil {
|
||||
c.v = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
onGCEOnce sync.Once
|
||||
onGCE bool
|
||||
)
|
||||
|
||||
// OnGCE reports whether this process is running on Google Compute Engine.
|
||||
func OnGCE() bool {
|
||||
onGCEOnce.Do(initOnGCE)
|
||||
return onGCE
|
||||
}
|
||||
|
||||
func initOnGCE() {
|
||||
onGCE = testOnGCE()
|
||||
}
|
||||
|
||||
func testOnGCE() bool {
|
||||
// The user explicitly said they're on GCE, so trust them.
|
||||
if os.Getenv(metadataHostEnv) != "" {
|
||||
return true
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
resc := make(chan bool, 2)
|
||||
|
||||
// Try two strategies in parallel.
|
||||
// See https://github.com/googleapis/google-cloud-go/issues/194
|
||||
go func() {
|
||||
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
res, err := defaultClient.hc.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
resc <- res.Header.Get("Metadata-Flavor") == "Google"
|
||||
}()
|
||||
|
||||
go func() {
|
||||
addrs, err := net.LookupHost("metadata.google.internal")
|
||||
if err != nil || len(addrs) == 0 {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
resc <- strsContains(addrs, metadataIP)
|
||||
}()
|
||||
|
||||
tryHarder := systemInfoSuggestsGCE()
|
||||
if tryHarder {
|
||||
res := <-resc
|
||||
if res {
|
||||
// The first strategy succeeded, so let's use it.
|
||||
return true
|
||||
}
|
||||
// Wait for either the DNS or metadata server probe to
|
||||
// contradict the other one and say we are running on
|
||||
// GCE. Give it a lot of time to do so, since the system
|
||||
// info already suggests we're running on a GCE BIOS.
|
||||
timer := time.NewTimer(5 * time.Second)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case res = <-resc:
|
||||
return res
|
||||
case <-timer.C:
|
||||
// Too slow. Who knows what this system is.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// There's no hint from the system info that we're running on
|
||||
// GCE, so use the first probe's result as truth, whether it's
|
||||
// true or false. The goal here is to optimize for speed for
|
||||
// users who are NOT running on GCE. We can't assume that
|
||||
// either a DNS lookup or an HTTP request to a blackholed IP
|
||||
// address is fast. Worst case this should return when the
|
||||
// metaClient's Transport.ResponseHeaderTimeout or
|
||||
// Transport.Dial.Timeout fires (in two seconds).
|
||||
return <-resc
|
||||
}
|
||||
|
||||
// systemInfoSuggestsGCE reports whether the local system (without
|
||||
// doing network requests) suggests that we're running on GCE. If this
|
||||
// returns true, testOnGCE tries a bit harder to reach its metadata
|
||||
// server.
|
||||
func systemInfoSuggestsGCE() bool {
|
||||
if runtime.GOOS != "linux" {
|
||||
// We don't have any non-Linux clues available, at least yet.
|
||||
return false
|
||||
}
|
||||
slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name")
|
||||
name := strings.TrimSpace(string(slurp))
|
||||
return name == "Google" || name == "Google Compute Engine"
|
||||
}
|
||||
|
||||
// Subscribe calls Client.Subscribe on a client designed for subscribing (one with no
|
||||
// ResponseHeaderTimeout).
|
||||
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
return subscribeClient.Subscribe(suffix, fn)
|
||||
}
|
||||
|
||||
// Get calls Client.Get on the default client.
|
||||
func Get(suffix string) (string, error) { return defaultClient.Get(suffix) }
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func ProjectID() (string, error) { return defaultClient.ProjectID() }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() }
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func InternalIP() (string, error) { return defaultClient.InternalIP() }
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func ExternalIP() (string, error) { return defaultClient.ExternalIP() }
|
||||
|
||||
// Email calls Client.Email on the default client.
|
||||
func Email(serviceAccount string) (string, error) { return defaultClient.Email(serviceAccount) }
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func Hostname() (string, error) { return defaultClient.Hostname() }
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() }
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func InstanceID() (string, error) { return defaultClient.InstanceID() }
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func InstanceName() (string, error) { return defaultClient.InstanceName() }
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func Zone() (string, error) { return defaultClient.Zone() }
|
||||
|
||||
// InstanceAttributes calls Client.InstanceAttributes on the default client.
|
||||
func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() }
|
||||
|
||||
// ProjectAttributes calls Client.ProjectAttributes on the default client.
|
||||
func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() }
|
||||
|
||||
// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client.
|
||||
func InstanceAttributeValue(attr string) (string, error) {
|
||||
return defaultClient.InstanceAttributeValue(attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client.
|
||||
func ProjectAttributeValue(attr string) (string, error) {
|
||||
return defaultClient.ProjectAttributeValue(attr)
|
||||
}
|
||||
|
||||
// Scopes calls Client.Scopes on the default client.
|
||||
func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) }
|
||||
|
||||
func strsContains(ss []string, s string) bool {
|
||||
for _, v := range ss {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// A Client provides metadata.
|
||||
type Client struct {
|
||||
hc *http.Client
|
||||
}
|
||||
|
||||
// NewClient returns a Client that can be used to fetch metadata. All HTTP requests
|
||||
// will use the given http.Client instead of the default client.
|
||||
func NewClient(c *http.Client) *Client {
|
||||
return &Client{hc: c}
|
||||
}
|
||||
|
||||
// getETag returns a value from the metadata service as well as the associated ETag.
|
||||
// This func is otherwise equivalent to Get.
|
||||
func (c *Client) getETag(suffix string) (value, etag string, err error) {
|
||||
// Using a fixed IP makes it very difficult to spoof the metadata service in
|
||||
// a container, which is an important use-case for local testing of cloud
|
||||
// deployments. To enable spoofing of the metadata service, the environment
|
||||
// variable GCE_METADATA_HOST is first inspected to decide where metadata
|
||||
// requests shall go.
|
||||
host := os.Getenv(metadataHostEnv)
|
||||
if host == "" {
|
||||
// Using 169.254.169.254 instead of "metadata" here because Go
|
||||
// binaries built with the "netgo" tag and without cgo won't
|
||||
// know the search suffix for "metadata" is
|
||||
// ".google.internal", and this IP address is documented as
|
||||
// being stable anyway.
|
||||
host = metadataIP
|
||||
}
|
||||
u := "http://" + host + "/computeMetadata/v1/" + suffix
|
||||
req, _ := http.NewRequest("GET", u, nil)
|
||||
req.Header.Set("Metadata-Flavor", "Google")
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
res, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return "", "", NotDefinedError(suffix)
|
||||
}
|
||||
all, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return "", "", &Error{Code: res.StatusCode, Message: string(all)}
|
||||
}
|
||||
return string(all), res.Header.Get("Etag"), nil
|
||||
}
|
||||
|
||||
// Get returns a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
//
|
||||
// If the GCE_METADATA_HOST environment variable is not defined, a default of
|
||||
// 169.254.169.254 will be used instead.
|
||||
//
|
||||
// If the requested metadata is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
func (c *Client) Get(suffix string) (string, error) {
|
||||
val, _, err := c.getETag(suffix)
|
||||
return val, err
|
||||
}
|
||||
|
||||
func (c *Client) getTrimmed(suffix string) (s string, err error) {
|
||||
s, err = c.Get(suffix)
|
||||
s = strings.TrimSpace(s)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) lines(suffix string) ([]string, error) {
|
||||
j, err := c.Get(suffix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := strings.Split(strings.TrimSpace(j), "\n")
|
||||
for i := range s {
|
||||
s[i] = strings.TrimSpace(s[i])
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func (c *Client) ProjectID() (string, error) { return projID.get(c) }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) }
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func (c *Client) InstanceID() (string, error) { return instID.get(c) }
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func (c *Client) InternalIP() (string, error) {
|
||||
return c.getTrimmed("instance/network-interfaces/0/ip")
|
||||
}
|
||||
|
||||
// Email returns the email address associated with the service account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func (c *Client) Email(serviceAccount string) (string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return c.getTrimmed("instance/service-accounts/" + serviceAccount + "/email")
|
||||
}
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func (c *Client) ExternalIP() (string, error) {
|
||||
return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
|
||||
}
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func (c *Client) Hostname() (string, error) {
|
||||
return c.getTrimmed("instance/hostname")
|
||||
}
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func (c *Client) InstanceTags() ([]string, error) {
|
||||
var s []string
|
||||
j, err := c.Get("instance/tags")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func (c *Client) InstanceName() (string, error) {
|
||||
host, err := c.Hostname()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Split(host, ".")[0], nil
|
||||
}
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func (c *Client) Zone() (string, error) {
|
||||
zone, err := c.getTrimmed("instance/zone")
|
||||
// zone is of the form "projects/<projNum>/zones/<zoneName>".
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return zone[strings.LastIndex(zone, "/")+1:], nil
|
||||
}
|
||||
|
||||
// InstanceAttributes returns the list of user-defined attributes,
|
||||
// assigned when initially creating a GCE VM instance. The value of an
|
||||
// attribute can be obtained with InstanceAttributeValue.
|
||||
func (c *Client) InstanceAttributes() ([]string, error) { return c.lines("instance/attributes/") }
|
||||
|
||||
// ProjectAttributes returns the list of user-defined attributes
|
||||
// applying to the project as a whole, not just this VM. The value of
|
||||
// an attribute can be obtained with ProjectAttributeValue.
|
||||
func (c *Client) ProjectAttributes() ([]string, error) { return c.lines("project/attributes/") }
|
||||
|
||||
// InstanceAttributeValue returns the value of the provided VM
|
||||
// instance attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// InstanceAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) InstanceAttributeValue(attr string) (string, error) {
|
||||
return c.Get("instance/attributes/" + attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue returns the value of the provided
|
||||
// project attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// ProjectAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) ProjectAttributeValue(attr string) (string, error) {
|
||||
return c.Get("project/attributes/" + attr)
|
||||
}
|
||||
|
||||
// Scopes returns the service account scopes for the given account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func (c *Client) Scopes(serviceAccount string) ([]string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return c.lines("instance/service-accounts/" + serviceAccount + "/scopes")
|
||||
}
|
||||
|
||||
// Subscribe subscribes to a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
// The suffix may contain query parameters.
|
||||
//
|
||||
// Subscribe calls fn with the latest metadata value indicated by the provided
|
||||
// suffix. If the metadata value is deleted, fn is called with the empty string
|
||||
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
|
||||
// is deleted. Subscribe returns the error value returned from the last call to
|
||||
// fn, which may be nil when ok == false.
|
||||
func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
const failedSubscribeSleep = time.Second * 5
|
||||
|
||||
// First check to see if the metadata value exists at all.
|
||||
val, lastETag, err := c.getETag(suffix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fn(val, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok := true
|
||||
if strings.ContainsRune(suffix, '?') {
|
||||
suffix += "&wait_for_change=true&last_etag="
|
||||
} else {
|
||||
suffix += "?wait_for_change=true&last_etag="
|
||||
}
|
||||
for {
|
||||
val, etag, err := c.getETag(suffix + url.QueryEscape(lastETag))
|
||||
if err != nil {
|
||||
if _, deleted := err.(NotDefinedError); !deleted {
|
||||
time.Sleep(failedSubscribeSleep)
|
||||
continue // Retry on other errors.
|
||||
}
|
||||
ok = false
|
||||
}
|
||||
lastETag = etag
|
||||
|
||||
if err := fn(val, ok); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error contains an error response from the server.
|
||||
type Error struct {
|
||||
// Code is the HTTP response status code.
|
||||
Code int
|
||||
// Message is the server response message.
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message)
|
||||
}
|
||||
100
vendor/cloud.google.com/go/doc.go
generated
vendored
Normal file
100
vendor/cloud.google.com/go/doc.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package cloud is the root of the packages used to access Google Cloud
|
||||
Services. See https://godoc.org/cloud.google.com/go for a full list
|
||||
of sub-packages.
|
||||
|
||||
|
||||
Client Options
|
||||
|
||||
All clients in sub-packages are configurable via client options. These options are
|
||||
described here: https://godoc.org/google.golang.org/api/option.
|
||||
|
||||
|
||||
Authentication and Authorization
|
||||
|
||||
All the clients in sub-packages support authentication via Google Application Default
|
||||
Credentials (see https://cloud.google.com/docs/authentication/production), or
|
||||
by providing a JSON key file for a Service Account. See the authentication examples
|
||||
in this package for details.
|
||||
|
||||
|
||||
Timeouts and Cancellation
|
||||
|
||||
By default, all requests in sub-packages will run indefinitely, retrying on transient
|
||||
errors when correctness allows. To set timeouts or arrange for cancellation, use
|
||||
contexts. See the examples for details.
|
||||
|
||||
Do not attempt to control the initial connection (dialing) of a service by setting a
|
||||
timeout on the context passed to NewClient. Dialing is non-blocking, so timeouts
|
||||
would be ineffective and would only interfere with credential refreshing, which uses
|
||||
the same context.
|
||||
|
||||
|
||||
Connection Pooling
|
||||
|
||||
Connection pooling differs in clients based on their transport. Cloud
|
||||
clients either rely on HTTP or gRPC transports to communicate
|
||||
with Google Cloud.
|
||||
|
||||
Cloud clients that use HTTP (bigquery, compute, storage, and translate) rely on the
|
||||
underlying HTTP transport to cache connections for later re-use. These are cached to
|
||||
the default http.MaxIdleConns and http.MaxIdleConnsPerHost settings in
|
||||
http.DefaultTransport.
|
||||
|
||||
For gRPC clients (all others in this repo), connection pooling is configurable. Users
|
||||
of cloud client libraries may specify option.WithGRPCConnectionPool(n) as a client
|
||||
option to NewClient calls. This configures the underlying gRPC connections to be
|
||||
pooled and addressed in a round robin fashion.
|
||||
|
||||
|
||||
Using the Libraries with Docker
|
||||
|
||||
Minimal docker images like Alpine lack CA certificates. This causes RPCs to appear to
|
||||
hang, because gRPC retries indefinitely. See https://github.com/googleapis/google-cloud-go/issues/928
|
||||
for more information.
|
||||
|
||||
|
||||
Debugging
|
||||
|
||||
To see gRPC logs, set the environment variable GRPC_GO_LOG_SEVERITY_LEVEL. See
|
||||
https://godoc.org/google.golang.org/grpc/grpclog for more information.
|
||||
|
||||
For HTTP logging, set the GODEBUG environment variable to "http2debug=1" or "http2debug=2".
|
||||
|
||||
|
||||
Client Stability
|
||||
|
||||
Clients in this repository are considered alpha or beta unless otherwise
|
||||
marked as stable in the README.md. Semver is not used to communicate stability
|
||||
of clients.
|
||||
|
||||
Alpha and beta clients may change or go away without notice.
|
||||
|
||||
Clients marked stable will maintain compatibility with future versions for as
|
||||
long as we can reasonably sustain. Incompatible changes might be made in some
|
||||
situations, including:
|
||||
|
||||
- Security bugs may prompt backwards-incompatible changes.
|
||||
|
||||
- Situations in which components are no longer feasible to maintain without
|
||||
making breaking changes, including removal.
|
||||
|
||||
- Parts of the client surface may be outright unstable and subject to change.
|
||||
These parts of the surface will be labeled with the note, "It is EXPERIMENTAL
|
||||
and subject to change or removal without notice."
|
||||
*/
|
||||
package cloud // import "cloud.google.com/go"
|
||||
48
vendor/cloud.google.com/go/gapics.txt
generated
vendored
Normal file
48
vendor/cloud.google.com/go/gapics.txt
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
google/api/expr/artman_cel.yaml
|
||||
google/cloud/asset/artman_cloudasset_v1beta1.yaml
|
||||
google/cloud/asset/artman_cloudasset_v1p2beta1.yaml
|
||||
google/iam/credentials/artman_iamcredentials_v1.yaml
|
||||
google/cloud/automl/artman_automl_v1.yaml
|
||||
google/cloud/automl/artman_automl_v1beta1.yaml
|
||||
google/cloud/bigquery/datatransfer/artman_bigquerydatatransfer.yaml
|
||||
google/cloud/bigquery/storage/artman_bigquerystorage_v1beta1.yaml
|
||||
google/cloud/dataproc/artman_dataproc_v1.yaml
|
||||
google/cloud/dataproc/artman_dataproc_v1beta2.yaml
|
||||
google/cloud/dialogflow/v2/artman_dialogflow_v2.yaml
|
||||
google/cloud/iot/artman_cloudiot.yaml
|
||||
google/cloud/irm/artman_irm_v1alpha2.yaml
|
||||
google/cloud/kms/artman_cloudkms.yaml
|
||||
google/cloud/language/artman_language_v1beta2.yaml
|
||||
google/cloud/oslogin/artman_oslogin_v1.yaml
|
||||
google/cloud/oslogin/artman_oslogin_v1beta.yaml
|
||||
google/cloud/recaptchaenterprise/artman_recaptchaenterprise_v1beta1.yaml
|
||||
google/cloud/recommender/artman_recommender_v1beta1.yaml
|
||||
google/cloud/redis/artman_redis_v1beta1.yaml
|
||||
google/cloud/redis/artman_redis_v1.yaml
|
||||
google/cloud/securitycenter/artman_securitycenter_v1beta1.yaml
|
||||
google/cloud/securitycenter/artman_securitycenter_v1.yaml
|
||||
google/cloud/talent/artman_talent_v4beta1.yaml
|
||||
google/cloud/tasks/artman_cloudtasks_v2beta2.yaml
|
||||
google/cloud/tasks/artman_cloudtasks_v2beta3.yaml
|
||||
google/cloud/tasks/artman_cloudtasks_v2.yaml
|
||||
google/cloud/videointelligence/artman_videointelligence_v1.yaml
|
||||
google/cloud/videointelligence/artman_videointelligence_v1beta2.yaml
|
||||
google/cloud/vision/artman_vision_v1.yaml
|
||||
google/cloud/vision/artman_vision_v1p1beta1.yaml
|
||||
google/cloud/webrisk/artman_webrisk_v1beta1.yaml
|
||||
google/devtools/artman_clouddebugger.yaml
|
||||
google/devtools/cloudbuild/artman_cloudbuild.yaml
|
||||
google/devtools/clouderrorreporting/artman_errorreporting.yaml
|
||||
google/devtools/cloudtrace/artman_cloudtrace_v1.yaml
|
||||
google/devtools/cloudtrace/artman_cloudtrace_v2.yaml
|
||||
google/devtools/containeranalysis/artman_containeranalysis_v1beta1.yaml
|
||||
google/firestore/artman_firestore.yaml
|
||||
google/firestore/admin/artman_firestore_v1.yaml
|
||||
google/logging/artman_logging.yaml
|
||||
google/longrunning/artman_longrunning.yaml
|
||||
google/monitoring/artman_monitoring.yaml
|
||||
google/privacy/dlp/artman_dlp_v2.yaml
|
||||
google/pubsub/artman_pubsub.yaml
|
||||
google/spanner/admin/database/artman_spanner_admin_database.yaml
|
||||
google/spanner/admin/instance/artman_spanner_admin_instance.yaml
|
||||
google/spanner/artman_spanner.yaml
|
||||
28
vendor/cloud.google.com/go/go.mod
generated
vendored
Normal file
28
vendor/cloud.google.com/go/go.mod
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
module cloud.google.com/go
|
||||
|
||||
go 1.11
|
||||
|
||||
require (
|
||||
cloud.google.com/go/bigquery v1.0.1
|
||||
cloud.google.com/go/datastore v1.0.0
|
||||
cloud.google.com/go/pubsub v1.0.1
|
||||
cloud.google.com/go/storage v1.0.0
|
||||
github.com/golang/mock v1.3.1
|
||||
github.com/golang/protobuf v1.3.2
|
||||
github.com/google/go-cmp v0.3.0
|
||||
github.com/google/martian v2.1.0+incompatible
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f
|
||||
github.com/googleapis/gax-go/v2 v2.0.5
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024
|
||||
go.opencensus.io v0.22.0
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||
golang.org/x/text v0.3.2
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2
|
||||
google.golang.org/api v0.14.0
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9
|
||||
google.golang.org/grpc v1.21.1
|
||||
honnef.co/go/tools v0.0.1-2019.2.3
|
||||
)
|
||||
209
vendor/cloud.google.com/go/go.sum
generated
vendored
Normal file
209
vendor/cloud.google.com/go/go.sum
generated
vendored
Normal file
@@ -0,0 +1,209 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go/bigquery v1.0.1 h1:hL+ycaJpVE9M7nLoiXb/Pn10ENE2u+oddxbD8uu0ZVU=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/pubsub v1.0.1 h1:W9tAK3E57P75u0XLLR82LZyw8VpAnhmyTOxW9qzmyj8=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/storage v1.0.0 h1:VV2nUM3wwLLGh9lSABFgZMjInyUbJeaRSE64WuAIQ+4=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57 h1:eqyIo2HjKhKe/mJzTG8n4VqvLXIOEG+SLdDqX7xGtkY=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f h1:Jnx61latede7zDD3DiiP4gmNz33uK0U5HDUaF0a/HVQ=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522 h1:OeRHuibLsmZkFj773W4LcfAGsSxJgfPONhr8cmO+eLA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979 h1:Agxu5KLo8o7Bb634SVDnhIfpTvxmzUwhbYAzBvXt6h4=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f h1:hX65Cu3JDlGH3uEdK7I99Ii+9kjD6mvnnpfLdEAH0x4=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac h1:8R1esu+8QioDxo4E4mX6bFztO+dMTM49DNAaWfO5OeY=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138 h1:H3uGjxCR/6Ds0Mjgyp7LMK81+LvmbvWWEnJhzk1Pi9E=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c h1:97SnQk1GYRXJgvwZ8fadnxDOWfKvkNQHH3CtZntPSrM=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0 h1:Dh6fw+p6FyRl5x/FvNswO1ji0lIGzm3KP8Y9VkS9PTE=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff h1:On1qIo75ByTwFJ4/W2bIqHcwJ9XAqtSWUs8GwRrIhtc=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2 h1:EtTFh6h4SAKemS+CURDMTDIANuduG5zKEXShyy18bGA=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0 h1:VGGbLNyPF7dvYHhcUGYBBGCRDDK0RRJAI6KCvo0CL+E=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0 h1:jbyannxz0XFD3zdjgrSUsaJbgpH4eTrkdhRChkHPfO8=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.14.0 h1:uMf5uLi4eQMRrMKhCplNik4U4H8Z6C1br3zOtAa/aDE=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19 h1:Lj2SnHtxkRGJDqnGaSjo+CCdIieEnwVazbOXILwQemk=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 h1:iKtrH9Y8mcbADOP0YFaEMth7OfuHY9xHOwNj4znpM1A=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51 h1:Ex1mq5jaJof+kRnYi3SlYJ8KKa9Ao3NHyIT5XJ1gF6U=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9 h1:6XzpBoANz1NqMNfDXzc2QmHmbb1vyMsvRfoP5rM+K1I=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a h1:/8zB6iBfHCl1qAnEAWwGPNrUvapuy6CPla1VM0k8hQw=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a h1:LJwr7TCTghdatWv40WobzlKXc9c4s8oGa7QKJUtHhWA=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
12
vendor/cloud.google.com/go/iam/.repo-metadata.json
generated
vendored
Normal file
12
vendor/cloud.google.com/go/iam/.repo-metadata.json
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "iam",
|
||||
"name_pretty": "Cloud Identify and Access Management API",
|
||||
"product_documentation": "https://cloud.google.com/iam",
|
||||
"client_documentation": "https://godoc.org/cloud.google.com/go/iam",
|
||||
"release_level": "ga",
|
||||
"language": "go",
|
||||
"repo": "googleapis/google-cloud-go",
|
||||
"distribution_name": "cloud.google.com/go/iam",
|
||||
"api_id": "iam.googleapis.com",
|
||||
"requires_billing": true
|
||||
}
|
||||
315
vendor/cloud.google.com/go/iam/iam.go
generated
vendored
Normal file
315
vendor/cloud.google.com/go/iam/iam.go
generated
vendored
Normal file
@@ -0,0 +1,315 @@
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package iam supports the resource-specific operations of Google Cloud
|
||||
// IAM (Identity and Access Management) for the Google Cloud Libraries.
|
||||
// See https://cloud.google.com/iam for more about IAM.
|
||||
//
|
||||
// Users of the Google Cloud Libraries will typically not use this package
|
||||
// directly. Instead they will begin with some resource that supports IAM, like
|
||||
// a pubsub topic, and call its IAM method to get a Handle for that resource.
|
||||
package iam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
pb "google.golang.org/genproto/googleapis/iam/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// client abstracts the IAMPolicy API to allow multiple implementations.
|
||||
type client interface {
|
||||
Get(ctx context.Context, resource string) (*pb.Policy, error)
|
||||
Set(ctx context.Context, resource string, p *pb.Policy) error
|
||||
Test(ctx context.Context, resource string, perms []string) ([]string, error)
|
||||
}
|
||||
|
||||
// grpcClient implements client for the standard gRPC-based IAMPolicy service.
|
||||
type grpcClient struct {
|
||||
c pb.IAMPolicyClient
|
||||
}
|
||||
|
||||
var withRetry = gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.DeadlineExceeded,
|
||||
codes.Unavailable,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60 * time.Second,
|
||||
Multiplier: 1.3,
|
||||
})
|
||||
})
|
||||
|
||||
func (g *grpcClient) Get(ctx context.Context, resource string) (*pb.Policy, error) {
|
||||
var proto *pb.Policy
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
|
||||
ctx = insertMetadata(ctx, md)
|
||||
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
|
||||
var err error
|
||||
proto, err = g.c.GetIamPolicy(ctx, &pb.GetIamPolicyRequest{Resource: resource})
|
||||
return err
|
||||
}, withRetry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proto, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) Set(ctx context.Context, resource string, p *pb.Policy) error {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
|
||||
ctx = insertMetadata(ctx, md)
|
||||
|
||||
return gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
|
||||
_, err := g.c.SetIamPolicy(ctx, &pb.SetIamPolicyRequest{
|
||||
Resource: resource,
|
||||
Policy: p,
|
||||
})
|
||||
return err
|
||||
}, withRetry)
|
||||
}
|
||||
|
||||
func (g *grpcClient) Test(ctx context.Context, resource string, perms []string) ([]string, error) {
|
||||
var res *pb.TestIamPermissionsResponse
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
|
||||
ctx = insertMetadata(ctx, md)
|
||||
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
|
||||
var err error
|
||||
res, err = g.c.TestIamPermissions(ctx, &pb.TestIamPermissionsRequest{
|
||||
Resource: resource,
|
||||
Permissions: perms,
|
||||
})
|
||||
return err
|
||||
}, withRetry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.Permissions, nil
|
||||
}
|
||||
|
||||
// A Handle provides IAM operations for a resource.
|
||||
type Handle struct {
|
||||
c client
|
||||
resource string
|
||||
}
|
||||
|
||||
// InternalNewHandle is for use by the Google Cloud Libraries only.
|
||||
//
|
||||
// InternalNewHandle returns a Handle for resource.
|
||||
// The conn parameter refers to a server that must support the IAMPolicy service.
|
||||
func InternalNewHandle(conn *grpc.ClientConn, resource string) *Handle {
|
||||
return InternalNewHandleGRPCClient(pb.NewIAMPolicyClient(conn), resource)
|
||||
}
|
||||
|
||||
// InternalNewHandleGRPCClient is for use by the Google Cloud Libraries only.
|
||||
//
|
||||
// InternalNewHandleClient returns a Handle for resource using the given
|
||||
// grpc service that implements IAM as a mixin
|
||||
func InternalNewHandleGRPCClient(c pb.IAMPolicyClient, resource string) *Handle {
|
||||
return InternalNewHandleClient(&grpcClient{c: c}, resource)
|
||||
}
|
||||
|
||||
// InternalNewHandleClient is for use by the Google Cloud Libraries only.
|
||||
//
|
||||
// InternalNewHandleClient returns a Handle for resource using the given
|
||||
// client implementation.
|
||||
func InternalNewHandleClient(c client, resource string) *Handle {
|
||||
return &Handle{
|
||||
c: c,
|
||||
resource: resource,
|
||||
}
|
||||
}
|
||||
|
||||
// Policy retrieves the IAM policy for the resource.
|
||||
func (h *Handle) Policy(ctx context.Context) (*Policy, error) {
|
||||
proto, err := h.c.Get(ctx, h.resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Policy{InternalProto: proto}, nil
|
||||
}
|
||||
|
||||
// SetPolicy replaces the resource's current policy with the supplied Policy.
|
||||
//
|
||||
// If policy was created from a prior call to Get, then the modification will
|
||||
// only succeed if the policy has not changed since the Get.
|
||||
func (h *Handle) SetPolicy(ctx context.Context, policy *Policy) error {
|
||||
return h.c.Set(ctx, h.resource, policy.InternalProto)
|
||||
}
|
||||
|
||||
// TestPermissions returns the subset of permissions that the caller has on the resource.
|
||||
func (h *Handle) TestPermissions(ctx context.Context, permissions []string) ([]string, error) {
|
||||
return h.c.Test(ctx, h.resource, permissions)
|
||||
}
|
||||
|
||||
// A RoleName is a name representing a collection of permissions.
|
||||
type RoleName string
|
||||
|
||||
// Common role names.
|
||||
const (
|
||||
Owner RoleName = "roles/owner"
|
||||
Editor RoleName = "roles/editor"
|
||||
Viewer RoleName = "roles/viewer"
|
||||
)
|
||||
|
||||
const (
|
||||
// AllUsers is a special member that denotes all users, even unauthenticated ones.
|
||||
AllUsers = "allUsers"
|
||||
|
||||
// AllAuthenticatedUsers is a special member that denotes all authenticated users.
|
||||
AllAuthenticatedUsers = "allAuthenticatedUsers"
|
||||
)
|
||||
|
||||
// A Policy is a list of Bindings representing roles
|
||||
// granted to members.
|
||||
//
|
||||
// The zero Policy is a valid policy with no bindings.
|
||||
type Policy struct {
|
||||
// TODO(jba): when type aliases are available, put Policy into an internal package
|
||||
// and provide an exported alias here.
|
||||
|
||||
// This field is exported for use by the Google Cloud Libraries only.
|
||||
// It may become unexported in a future release.
|
||||
InternalProto *pb.Policy
|
||||
}
|
||||
|
||||
// Members returns the list of members with the supplied role.
|
||||
// The return value should not be modified. Use Add and Remove
|
||||
// to modify the members of a role.
|
||||
func (p *Policy) Members(r RoleName) []string {
|
||||
b := p.binding(r)
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
return b.Members
|
||||
}
|
||||
|
||||
// HasRole reports whether member has role r.
|
||||
func (p *Policy) HasRole(member string, r RoleName) bool {
|
||||
return memberIndex(member, p.binding(r)) >= 0
|
||||
}
|
||||
|
||||
// Add adds member member to role r if it is not already present.
|
||||
// A new binding is created if there is no binding for the role.
|
||||
func (p *Policy) Add(member string, r RoleName) {
|
||||
b := p.binding(r)
|
||||
if b == nil {
|
||||
if p.InternalProto == nil {
|
||||
p.InternalProto = &pb.Policy{}
|
||||
}
|
||||
p.InternalProto.Bindings = append(p.InternalProto.Bindings, &pb.Binding{
|
||||
Role: string(r),
|
||||
Members: []string{member},
|
||||
})
|
||||
return
|
||||
}
|
||||
if memberIndex(member, b) < 0 {
|
||||
b.Members = append(b.Members, member)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes member from role r if it is present.
|
||||
func (p *Policy) Remove(member string, r RoleName) {
|
||||
bi := p.bindingIndex(r)
|
||||
if bi < 0 {
|
||||
return
|
||||
}
|
||||
bindings := p.InternalProto.Bindings
|
||||
b := bindings[bi]
|
||||
mi := memberIndex(member, b)
|
||||
if mi < 0 {
|
||||
return
|
||||
}
|
||||
// Order doesn't matter for bindings or members, so to remove, move the last item
|
||||
// into the removed spot and shrink the slice.
|
||||
if len(b.Members) == 1 {
|
||||
// Remove binding.
|
||||
last := len(bindings) - 1
|
||||
bindings[bi] = bindings[last]
|
||||
bindings[last] = nil
|
||||
p.InternalProto.Bindings = bindings[:last]
|
||||
return
|
||||
}
|
||||
// Remove member.
|
||||
// TODO(jba): worry about multiple copies of m?
|
||||
last := len(b.Members) - 1
|
||||
b.Members[mi] = b.Members[last]
|
||||
b.Members[last] = ""
|
||||
b.Members = b.Members[:last]
|
||||
}
|
||||
|
||||
// Roles returns the names of all the roles that appear in the Policy.
|
||||
func (p *Policy) Roles() []RoleName {
|
||||
if p.InternalProto == nil {
|
||||
return nil
|
||||
}
|
||||
var rns []RoleName
|
||||
for _, b := range p.InternalProto.Bindings {
|
||||
rns = append(rns, RoleName(b.Role))
|
||||
}
|
||||
return rns
|
||||
}
|
||||
|
||||
// binding returns the Binding for the suppied role, or nil if there isn't one.
|
||||
func (p *Policy) binding(r RoleName) *pb.Binding {
|
||||
i := p.bindingIndex(r)
|
||||
if i < 0 {
|
||||
return nil
|
||||
}
|
||||
return p.InternalProto.Bindings[i]
|
||||
}
|
||||
|
||||
func (p *Policy) bindingIndex(r RoleName) int {
|
||||
if p.InternalProto == nil {
|
||||
return -1
|
||||
}
|
||||
for i, b := range p.InternalProto.Bindings {
|
||||
if b.Role == string(r) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// memberIndex returns the index of m in b's Members, or -1 if not found.
|
||||
func memberIndex(m string, b *pb.Binding) int {
|
||||
if b == nil {
|
||||
return -1
|
||||
}
|
||||
for i, mm := range b.Members {
|
||||
if mm == m {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// insertMetadata inserts metadata into the given context
|
||||
func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context {
|
||||
out, _ := metadata.FromOutgoingContext(ctx)
|
||||
out = out.Copy()
|
||||
for _, md := range mds {
|
||||
for k, v := range md {
|
||||
out[k] = append(out[k], v...)
|
||||
}
|
||||
}
|
||||
return metadata.NewOutgoingContext(ctx, out)
|
||||
}
|
||||
54
vendor/cloud.google.com/go/internal/annotate.go
generated
vendored
Normal file
54
vendor/cloud.google.com/go/internal/annotate.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Annotate prepends msg to the error message in err, attempting
|
||||
// to preserve other information in err, like an error code.
|
||||
//
|
||||
// Annotate panics if err is nil.
|
||||
//
|
||||
// Annotate knows about these error types:
|
||||
// - "google.golang.org/grpc/status".Status
|
||||
// - "google.golang.org/api/googleapi".Error
|
||||
// If the error is not one of these types, Annotate behaves
|
||||
// like
|
||||
// fmt.Errorf("%s: %v", msg, err)
|
||||
func Annotate(err error, msg string) error {
|
||||
if err == nil {
|
||||
panic("Annotate called with nil")
|
||||
}
|
||||
if s, ok := status.FromError(err); ok {
|
||||
p := s.Proto()
|
||||
p.Message = msg + ": " + p.Message
|
||||
return status.ErrorProto(p)
|
||||
}
|
||||
if g, ok := err.(*googleapi.Error); ok {
|
||||
g.Message = msg + ": " + g.Message
|
||||
return g
|
||||
}
|
||||
return fmt.Errorf("%s: %v", msg, err)
|
||||
}
|
||||
|
||||
// Annotatef uses format and args to format a string, then calls Annotate.
|
||||
func Annotatef(err error, format string, args ...interface{}) error {
|
||||
return Annotate(err, fmt.Sprintf(format, args...))
|
||||
}
|
||||
108
vendor/cloud.google.com/go/internal/optional/optional.go
generated
vendored
Normal file
108
vendor/cloud.google.com/go/internal/optional/optional.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package optional provides versions of primitive types that can
|
||||
// be nil. These are useful in methods that update some of an API object's
|
||||
// fields.
|
||||
package optional
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
// Bool is either a bool or nil.
|
||||
Bool interface{}
|
||||
|
||||
// String is either a string or nil.
|
||||
String interface{}
|
||||
|
||||
// Int is either an int or nil.
|
||||
Int interface{}
|
||||
|
||||
// Uint is either a uint or nil.
|
||||
Uint interface{}
|
||||
|
||||
// Float64 is either a float64 or nil.
|
||||
Float64 interface{}
|
||||
|
||||
// Duration is either a time.Duration or nil.
|
||||
Duration interface{}
|
||||
)
|
||||
|
||||
// ToBool returns its argument as a bool.
|
||||
// It panics if its argument is nil or not a bool.
|
||||
func ToBool(v Bool) bool {
|
||||
x, ok := v.(bool)
|
||||
if !ok {
|
||||
doPanic("Bool", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToString returns its argument as a string.
|
||||
// It panics if its argument is nil or not a string.
|
||||
func ToString(v String) string {
|
||||
x, ok := v.(string)
|
||||
if !ok {
|
||||
doPanic("String", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToInt returns its argument as an int.
|
||||
// It panics if its argument is nil or not an int.
|
||||
func ToInt(v Int) int {
|
||||
x, ok := v.(int)
|
||||
if !ok {
|
||||
doPanic("Int", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToUint returns its argument as a uint.
|
||||
// It panics if its argument is nil or not a uint.
|
||||
func ToUint(v Uint) uint {
|
||||
x, ok := v.(uint)
|
||||
if !ok {
|
||||
doPanic("Uint", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToFloat64 returns its argument as a float64.
|
||||
// It panics if its argument is nil or not a float64.
|
||||
func ToFloat64(v Float64) float64 {
|
||||
x, ok := v.(float64)
|
||||
if !ok {
|
||||
doPanic("Float64", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToDuration returns its argument as a time.Duration.
|
||||
// It panics if its argument is nil or not a time.Duration.
|
||||
func ToDuration(v Duration) time.Duration {
|
||||
x, ok := v.(time.Duration)
|
||||
if !ok {
|
||||
doPanic("Duration", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func doPanic(capType string, v interface{}) {
|
||||
panic(fmt.Sprintf("optional.%s value should be %s, got %T", capType, strings.ToLower(capType), v))
|
||||
}
|
||||
54
vendor/cloud.google.com/go/internal/retry.go
generated
vendored
Normal file
54
vendor/cloud.google.com/go/internal/retry.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
)
|
||||
|
||||
// Retry calls the supplied function f repeatedly according to the provided
|
||||
// backoff parameters. It returns when one of the following occurs:
|
||||
// When f's first return value is true, Retry immediately returns with f's second
|
||||
// return value.
|
||||
// When the provided context is done, Retry returns with an error that
|
||||
// includes both ctx.Error() and the last error returned by f.
|
||||
func Retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error)) error {
|
||||
return retry(ctx, bo, f, gax.Sleep)
|
||||
}
|
||||
|
||||
func retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error),
|
||||
sleep func(context.Context, time.Duration) error) error {
|
||||
var lastErr error
|
||||
for {
|
||||
stop, err := f()
|
||||
if stop {
|
||||
return err
|
||||
}
|
||||
// Remember the last "real" error from f.
|
||||
if err != nil && err != context.Canceled && err != context.DeadlineExceeded {
|
||||
lastErr = err
|
||||
}
|
||||
p := bo.Pause()
|
||||
if cerr := sleep(ctx, p); cerr != nil {
|
||||
if lastErr != nil {
|
||||
return Annotatef(lastErr, "retry failed with %v; last error", cerr)
|
||||
}
|
||||
return cerr
|
||||
}
|
||||
}
|
||||
}
|
||||
109
vendor/cloud.google.com/go/internal/trace/trace.go
generated
vendored
Normal file
109
vendor/cloud.google.com/go/internal/trace/trace.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.opencensus.io/trace"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/genproto/googleapis/rpc/code"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// StartSpan adds a span to the trace with the given name.
|
||||
func StartSpan(ctx context.Context, name string) context.Context {
|
||||
ctx, _ = trace.StartSpan(ctx, name)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// EndSpan ends a span with the given error.
|
||||
func EndSpan(ctx context.Context, err error) {
|
||||
span := trace.FromContext(ctx)
|
||||
if err != nil {
|
||||
span.SetStatus(toStatus(err))
|
||||
}
|
||||
span.End()
|
||||
}
|
||||
|
||||
// toStatus interrogates an error and converts it to an appropriate
|
||||
// OpenCensus status.
|
||||
func toStatus(err error) trace.Status {
|
||||
if err2, ok := err.(*googleapi.Error); ok {
|
||||
return trace.Status{Code: httpStatusCodeToOCCode(err2.Code), Message: err2.Message}
|
||||
} else if s, ok := status.FromError(err); ok {
|
||||
return trace.Status{Code: int32(s.Code()), Message: s.Message()}
|
||||
} else {
|
||||
return trace.Status{Code: int32(code.Code_UNKNOWN), Message: err.Error()}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(deklerk): switch to using OpenCensus function when it becomes available.
|
||||
// Reference: https://github.com/googleapis/googleapis/blob/26b634d2724ac5dd30ae0b0cbfb01f07f2e4050e/google/rpc/code.proto
|
||||
func httpStatusCodeToOCCode(httpStatusCode int) int32 {
|
||||
switch httpStatusCode {
|
||||
case 200:
|
||||
return int32(code.Code_OK)
|
||||
case 499:
|
||||
return int32(code.Code_CANCELLED)
|
||||
case 500:
|
||||
return int32(code.Code_UNKNOWN) // Could also be Code_INTERNAL, Code_DATA_LOSS
|
||||
case 400:
|
||||
return int32(code.Code_INVALID_ARGUMENT) // Could also be Code_OUT_OF_RANGE
|
||||
case 504:
|
||||
return int32(code.Code_DEADLINE_EXCEEDED)
|
||||
case 404:
|
||||
return int32(code.Code_NOT_FOUND)
|
||||
case 409:
|
||||
return int32(code.Code_ALREADY_EXISTS) // Could also be Code_ABORTED
|
||||
case 403:
|
||||
return int32(code.Code_PERMISSION_DENIED)
|
||||
case 401:
|
||||
return int32(code.Code_UNAUTHENTICATED)
|
||||
case 429:
|
||||
return int32(code.Code_RESOURCE_EXHAUSTED)
|
||||
case 501:
|
||||
return int32(code.Code_UNIMPLEMENTED)
|
||||
case 503:
|
||||
return int32(code.Code_UNAVAILABLE)
|
||||
default:
|
||||
return int32(code.Code_UNKNOWN)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: (odeke-em): perhaps just pass around spans due to the cost
|
||||
// incurred from using trace.FromContext(ctx) yet we could avoid
|
||||
// throwing away the work done by ctx, span := trace.StartSpan.
|
||||
func TracePrintf(ctx context.Context, attrMap map[string]interface{}, format string, args ...interface{}) {
|
||||
var attrs []trace.Attribute
|
||||
for k, v := range attrMap {
|
||||
var a trace.Attribute
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
a = trace.StringAttribute(k, v)
|
||||
case bool:
|
||||
a = trace.BoolAttribute(k, v)
|
||||
case int:
|
||||
a = trace.Int64Attribute(k, int64(v))
|
||||
case int64:
|
||||
a = trace.Int64Attribute(k, v)
|
||||
default:
|
||||
a = trace.StringAttribute(k, fmt.Sprintf("%#v", v))
|
||||
}
|
||||
attrs = append(attrs, a)
|
||||
}
|
||||
trace.FromContext(ctx).Annotatef(attrs, format, args...)
|
||||
}
|
||||
19
vendor/cloud.google.com/go/internal/version/update_version.sh
generated
vendored
Normal file
19
vendor/cloud.google.com/go/internal/version/update_version.sh
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
# Copyright 2019 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
today=$(date +%Y%m%d)
|
||||
|
||||
sed -i -r -e 's/const Repo = "([0-9]{8})"/const Repo = "'$today'"/' $GOFILE
|
||||
|
||||
71
vendor/cloud.google.com/go/internal/version/version.go
generated
vendored
Normal file
71
vendor/cloud.google.com/go/internal/version/version.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:generate ./update_version.sh
|
||||
|
||||
// Package version contains version information for Google Cloud Client
|
||||
// Libraries for Go, as reported in request headers.
|
||||
package version
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Repo is the current version of the client libraries in this
|
||||
// repo. It should be a date in YYYYMMDD format.
|
||||
const Repo = "20191119"
|
||||
|
||||
// Go returns the Go runtime version. The returned string
|
||||
// has no whitespace.
|
||||
func Go() string {
|
||||
return goVersion
|
||||
}
|
||||
|
||||
var goVersion = goVer(runtime.Version())
|
||||
|
||||
const develPrefix = "devel +"
|
||||
|
||||
func goVer(s string) string {
|
||||
if strings.HasPrefix(s, develPrefix) {
|
||||
s = s[len(develPrefix):]
|
||||
if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
|
||||
s = s[:p]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
if strings.HasPrefix(s, "go1") {
|
||||
s = s[2:]
|
||||
var prerelease string
|
||||
if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
|
||||
s, prerelease = s[:p], s[p:]
|
||||
}
|
||||
if strings.HasSuffix(s, ".") {
|
||||
s += "0"
|
||||
} else if strings.Count(s, ".") < 2 {
|
||||
s += ".0"
|
||||
}
|
||||
if prerelease != "" {
|
||||
s += "-" + prerelease
|
||||
}
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func notSemverRune(r rune) bool {
|
||||
return !strings.ContainsRune("0123456789.", r)
|
||||
}
|
||||
17
vendor/cloud.google.com/go/issue_template.md
generated
vendored
Normal file
17
vendor/cloud.google.com/go/issue_template.md
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
(delete this for feature requests)
|
||||
|
||||
## Client
|
||||
|
||||
e.g. PubSub
|
||||
|
||||
## Describe Your Environment
|
||||
|
||||
e.g. Alpine Docker on GKE
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
e.g. Messages arrive really fast.
|
||||
|
||||
## Actual Behavior
|
||||
|
||||
e.g. Messages arrive really slowly.
|
||||
8
vendor/cloud.google.com/go/manuals.txt
generated
vendored
Normal file
8
vendor/cloud.google.com/go/manuals.txt
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
errorreporting/apiv1beta1
|
||||
firestore/apiv1beta1
|
||||
firestore/apiv1
|
||||
logging/apiv2
|
||||
longrunning/autogen
|
||||
pubsub/apiv1
|
||||
spanner/apiv1
|
||||
trace/apiv1
|
||||
10
vendor/cloud.google.com/go/microgens.csv
generated
vendored
Normal file
10
vendor/cloud.google.com/go/microgens.csv
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
input directory path, go module;package flag, gRPC ServiceConfig path flag, API service config path flag, release level
|
||||
google/cloud/texttospeech/v1, --go-gapic-package cloud.google.com/go/texttospeech/apiv1;texttospeech, --grpc-service-config google/cloud/texttospeech/v1/texttospeech_grpc_service_config.json, --gapic-service-config google/cloud/texttospeech/v1/texttospeech_v1.yaml, --release-level alpha
|
||||
google/cloud/asset/v1, --go-gapic-package cloud.google.com/go/asset/apiv1;asset, --grpc-service-config google/cloud/asset/v1/cloudasset_grpc_service_config.json, --gapic-service-config google/cloud/asset/v1/cloudasset_v1.yaml, --release-level alpha
|
||||
google/cloud/language/v1, --go-gapic-package cloud.google.com/go/language/apiv1;language, --grpc-service-config google/cloud/language/v1/language_grpc_service_config.json, --gapic-service-config google/cloud/language/language_v1.yaml, --release-level alpha
|
||||
google/cloud/phishingprotection/v1beta1, --go-gapic-package cloud.google.com/go/phishingprotection/apiv1beta1;phishingprotection, --grpc-service-config google/cloud/phishingprotection/v1beta1/phishingprotection_grpc_service_config.json, --gapic-service-config google/cloud/phishingprotection/v1beta1/phishingprotection_v1beta1.yaml, --release-level beta
|
||||
google/cloud/translate/v3, --go-gapic-package cloud.google.com/go/translate/apiv3;translate, --grpc-service-config google/cloud/translate/v3/translate_grpc_service_config.json, --gapic-service-config google/cloud/translate/v3/translate_v3.yaml,
|
||||
google/cloud/scheduler/v1, --go-gapic-package cloud.google.com/go/scheduler/apiv1;scheduler, --grpc-service-config google/cloud/scheduler/v1/cloudscheduler_grpc_service_config.json, --gapic-service-config google/cloud/scheduler/v1/cloudscheduler_v1.yaml,
|
||||
google/cloud/scheduler/v1beta1, --go-gapic-package cloud.google.com/go/scheduler/apiv1beta1;scheduler, --grpc-service-config google/cloud/scheduler/v1beta1/cloudscheduler_grpc_service_config.json, --gapic-service-config google/cloud/scheduler/v1beta1/cloudscheduler_v1beta1.yaml, --release-level beta
|
||||
google/cloud/speech/v1, --go-gapic-package cloud.google.com/go/speech/apiv1;speech, --grpc-service-config google/cloud/speech/v1/speech_grpc_service_config.json, --gapic-service-config google/cloud/speech/v1/speech_v1.yaml,
|
||||
google/cloud/speech/v1p1beta1, --go-gapic-package cloud.google.com/go/speech/apiv1p1beta1;speech, --grpc-service-config google/cloud/speech/v1p1beta1/speech_grpc_service_config.json, --gapic-service-config google/cloud/speech/v1p1beta1/speech_v1p1beta1.yaml, --release-level beta
|
||||
|
81
vendor/cloud.google.com/go/regen-gapic.sh
generated
vendored
Normal file
81
vendor/cloud.google.com/go/regen-gapic.sh
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/bin/bash
|
||||
# Copyright 2019 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# This script generates all GAPIC clients in this repo.
|
||||
# See instructions at go/yoshi-site.
|
||||
|
||||
set -ex
|
||||
|
||||
GOCLOUD_DIR="$(dirname "$0")"
|
||||
HOST_MOUNT="$PWD"
|
||||
|
||||
# need to mount the /var/folders properly for macos
|
||||
# https://stackoverflow.com/questions/45122459/docker-mounts-denied-the-paths-are-not-shared-from-os-x-and-are-not-known/45123074
|
||||
if [[ "$OSTYPE" == "darwin"* ]] && [[ "$HOST_MOUNT" == "/var/folders"* ]]; then
|
||||
HOST_MOUNT=/private$HOST_MOUNT
|
||||
fi
|
||||
|
||||
microgen() {
|
||||
input=$1
|
||||
options="${@:2}"
|
||||
|
||||
# see https://github.com/googleapis/gapic-generator-go/blob/master/README.md#docker-wrapper for details
|
||||
docker run \
|
||||
--user $UID \
|
||||
--mount type=bind,source=$HOST_MOUNT,destination=/conf,readonly \
|
||||
--mount type=bind,source=$HOST_MOUNT/$input,destination=/in/$input,readonly \
|
||||
--mount type=bind,source=/tmp,destination=/out \
|
||||
--rm \
|
||||
gcr.io/gapic-images/gapic-generator-go:0.9.1 \
|
||||
$options
|
||||
}
|
||||
|
||||
for gencfg in $(cat $GOCLOUD_DIR/gapics.txt); do
|
||||
rm -rf artman-genfiles/*
|
||||
artman --config "$gencfg" generate go_gapic
|
||||
cp -r artman-genfiles/gapi-*/cloud.google.com/go/* $GOCLOUD_DIR
|
||||
done
|
||||
|
||||
rm -rf /tmp/cloud.google.com
|
||||
{
|
||||
# skip the first line with column titles
|
||||
read -r
|
||||
while IFS=, read -r input mod retrycfg apicfg release
|
||||
do
|
||||
microgen $input "$mod" "$retrycfg" "$apicfg" "$release"
|
||||
done
|
||||
} < $GOCLOUD_DIR/microgens.csv
|
||||
|
||||
# copy generated code if any was created
|
||||
[ -d "/tmp/cloud.google.com/go" ] && cp -r /tmp/cloud.google.com/go/* $GOCLOUD_DIR
|
||||
|
||||
pushd $GOCLOUD_DIR
|
||||
gofmt -s -d -l -w . && goimports -w .
|
||||
|
||||
# NOTE(pongad): `sed -i` doesn't work on Macs, because -i option needs an argument.
|
||||
# `-i ''` doesn't work on GNU, since the empty string is treated as a file name.
|
||||
# So we just create the backup and delete it after.
|
||||
ver=$(date +%Y%m%d)
|
||||
git ls-files -mo | while read modified; do
|
||||
dir=${modified%/*.*}
|
||||
find . -path "*/$dir/doc.go" -exec sed -i.backup -e "s/^const versionClient.*/const versionClient = \"$ver\"/" '{}' +
|
||||
done
|
||||
popd
|
||||
|
||||
for manualdir in $(cat $GOCLOUD_DIR/manuals.txt); do
|
||||
find "$GOCLOUD_DIR/$manualdir" -name '*.go' -exec sed -i.backup -e 's/setGoogleClientInfo/SetGoogleClientInfo/g' '{}' '+'
|
||||
done
|
||||
|
||||
find $GOCLOUD_DIR -name '*.backup' -delete
|
||||
12
vendor/cloud.google.com/go/storage/.repo-metadata.json
generated
vendored
Normal file
12
vendor/cloud.google.com/go/storage/.repo-metadata.json
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "storage",
|
||||
"name_pretty": "storage",
|
||||
"product_documentation": "https://cloud.google.com/storage",
|
||||
"client_documentation": "https://godoc.org/cloud.google.com/go/storage",
|
||||
"release_level": "ga",
|
||||
"language": "go",
|
||||
"repo": "googleapis/google-cloud-go",
|
||||
"distribution_name": "cloud.google.com/go/storage",
|
||||
"api_id": "storage:v2",
|
||||
"requires_billing": true
|
||||
}
|
||||
43
vendor/cloud.google.com/go/storage/CHANGES.md
generated
vendored
Normal file
43
vendor/cloud.google.com/go/storage/CHANGES.md
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# Changes
|
||||
|
||||
## v1.4.0
|
||||
|
||||
- When listing objects in a bucket, allow callers to specify which attributes
|
||||
are queried. This allows for performance optimization.
|
||||
|
||||
## v1.3.0
|
||||
|
||||
- Use `storage.googleapis.com/storage/v1` by default for GCS requests
|
||||
instead of `www.googleapis.com/storage/v1`.
|
||||
|
||||
## v1.2.1
|
||||
|
||||
- Fixed a bug where UniformBucketLevelAccess and BucketPolicyOnly were not
|
||||
being sent in all cases.
|
||||
|
||||
## v1.2.0
|
||||
|
||||
- Add support for UniformBucketLevelAccess. This configures access checks
|
||||
to use only bucket-level IAM policies.
|
||||
See: https://godoc.org/cloud.google.com/go/storage#UniformBucketLevelAccess.
|
||||
- Fix userAgent to use correct version.
|
||||
|
||||
## v1.1.2
|
||||
|
||||
- Fix memory leak in BucketIterator and ObjectIterator.
|
||||
|
||||
## v1.1.1
|
||||
|
||||
- Send BucketPolicyOnly even when it's disabled.
|
||||
|
||||
## v1.1.0
|
||||
|
||||
- Performance improvements for ObjectIterator and BucketIterator.
|
||||
- Fix Bucket.ObjectIterator size calculation checks.
|
||||
- Added HMACKeyOptions to all the methods which allows for options such as
|
||||
UserProject to be set per invocation and optionally be used.
|
||||
|
||||
## v1.0.0
|
||||
|
||||
This is the first tag to carve out storage as its own module. See:
|
||||
https://github.com/golang/go/wiki/Modules#is-it-possible-to-add-a-module-to-a-multi-module-repository.
|
||||
202
vendor/cloud.google.com/go/storage/LICENSE
generated
vendored
Normal file
202
vendor/cloud.google.com/go/storage/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
32
vendor/cloud.google.com/go/storage/README.md
generated
vendored
Normal file
32
vendor/cloud.google.com/go/storage/README.md
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
## Cloud Storage [](https://godoc.org/cloud.google.com/go/storage)
|
||||
|
||||
- [About Cloud Storage](https://cloud.google.com/storage/)
|
||||
- [API documentation](https://cloud.google.com/storage/docs)
|
||||
- [Go client documentation](https://godoc.org/cloud.google.com/go/storage)
|
||||
- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/storage)
|
||||
|
||||
### Example Usage
|
||||
|
||||
First create a `storage.Client` to use throughout your application:
|
||||
|
||||
[snip]:# (storage-1)
|
||||
```go
|
||||
client, err := storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
[snip]:# (storage-2)
|
||||
```go
|
||||
// Read the object1 from bucket.
|
||||
rc, err := client.Bucket("bucket").Object("object1").NewReader(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer rc.Close()
|
||||
body, err := ioutil.ReadAll(rc)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
335
vendor/cloud.google.com/go/storage/acl.go
generated
vendored
Normal file
335
vendor/cloud.google.com/go/storage/acl.go
generated
vendored
Normal file
@@ -0,0 +1,335 @@
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"cloud.google.com/go/internal/trace"
|
||||
"google.golang.org/api/googleapi"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
)
|
||||
|
||||
// ACLRole is the level of access to grant.
|
||||
type ACLRole string
|
||||
|
||||
const (
|
||||
RoleOwner ACLRole = "OWNER"
|
||||
RoleReader ACLRole = "READER"
|
||||
RoleWriter ACLRole = "WRITER"
|
||||
)
|
||||
|
||||
// ACLEntity refers to a user or group.
|
||||
// They are sometimes referred to as grantees.
|
||||
//
|
||||
// It could be in the form of:
|
||||
// "user-<userId>", "user-<email>", "group-<groupId>", "group-<email>",
|
||||
// "domain-<domain>" and "project-team-<projectId>".
|
||||
//
|
||||
// Or one of the predefined constants: AllUsers, AllAuthenticatedUsers.
|
||||
type ACLEntity string
|
||||
|
||||
const (
|
||||
AllUsers ACLEntity = "allUsers"
|
||||
AllAuthenticatedUsers ACLEntity = "allAuthenticatedUsers"
|
||||
)
|
||||
|
||||
// ACLRule represents a grant for a role to an entity (user, group or team) for a
|
||||
// Google Cloud Storage object or bucket.
|
||||
type ACLRule struct {
|
||||
Entity ACLEntity
|
||||
EntityID string
|
||||
Role ACLRole
|
||||
Domain string
|
||||
Email string
|
||||
ProjectTeam *ProjectTeam
|
||||
}
|
||||
|
||||
// ProjectTeam is the project team associated with the entity, if any.
|
||||
type ProjectTeam struct {
|
||||
ProjectNumber string
|
||||
Team string
|
||||
}
|
||||
|
||||
// ACLHandle provides operations on an access control list for a Google Cloud Storage bucket or object.
|
||||
type ACLHandle struct {
|
||||
c *Client
|
||||
bucket string
|
||||
object string
|
||||
isDefault bool
|
||||
userProject string // for requester-pays buckets
|
||||
}
|
||||
|
||||
// Delete permanently deletes the ACL entry for the given entity.
|
||||
func (a *ACLHandle) Delete(ctx context.Context, entity ACLEntity) (err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.ACL.Delete")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if a.object != "" {
|
||||
return a.objectDelete(ctx, entity)
|
||||
}
|
||||
if a.isDefault {
|
||||
return a.bucketDefaultDelete(ctx, entity)
|
||||
}
|
||||
return a.bucketDelete(ctx, entity)
|
||||
}
|
||||
|
||||
// Set sets the role for the given entity.
|
||||
func (a *ACLHandle) Set(ctx context.Context, entity ACLEntity, role ACLRole) (err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.ACL.Set")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if a.object != "" {
|
||||
return a.objectSet(ctx, entity, role, false)
|
||||
}
|
||||
if a.isDefault {
|
||||
return a.objectSet(ctx, entity, role, true)
|
||||
}
|
||||
return a.bucketSet(ctx, entity, role)
|
||||
}
|
||||
|
||||
// List retrieves ACL entries.
|
||||
func (a *ACLHandle) List(ctx context.Context) (rules []ACLRule, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.ACL.List")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if a.object != "" {
|
||||
return a.objectList(ctx)
|
||||
}
|
||||
if a.isDefault {
|
||||
return a.bucketDefaultList(ctx)
|
||||
}
|
||||
return a.bucketList(ctx)
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketDefaultList(ctx context.Context) ([]ACLRule, error) {
|
||||
var acls *raw.ObjectAccessControls
|
||||
var err error
|
||||
err = runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.DefaultObjectAccessControls.List(a.bucket)
|
||||
a.configureCall(ctx, req)
|
||||
acls, err = req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toObjectACLRules(acls.Items), nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketDefaultDelete(ctx context.Context, entity ACLEntity) error {
|
||||
return runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.DefaultObjectAccessControls.Delete(a.bucket, string(entity))
|
||||
a.configureCall(ctx, req)
|
||||
return req.Do()
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketList(ctx context.Context) ([]ACLRule, error) {
|
||||
var acls *raw.BucketAccessControls
|
||||
var err error
|
||||
err = runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.BucketAccessControls.List(a.bucket)
|
||||
a.configureCall(ctx, req)
|
||||
acls, err = req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toBucketACLRules(acls.Items), nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketSet(ctx context.Context, entity ACLEntity, role ACLRole) error {
|
||||
acl := &raw.BucketAccessControl{
|
||||
Bucket: a.bucket,
|
||||
Entity: string(entity),
|
||||
Role: string(role),
|
||||
}
|
||||
err := runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.BucketAccessControls.Update(a.bucket, string(entity), acl)
|
||||
a.configureCall(ctx, req)
|
||||
_, err := req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketDelete(ctx context.Context, entity ACLEntity) error {
|
||||
return runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.BucketAccessControls.Delete(a.bucket, string(entity))
|
||||
a.configureCall(ctx, req)
|
||||
return req.Do()
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACLHandle) objectList(ctx context.Context) ([]ACLRule, error) {
|
||||
var acls *raw.ObjectAccessControls
|
||||
var err error
|
||||
err = runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.ObjectAccessControls.List(a.bucket, a.object)
|
||||
a.configureCall(ctx, req)
|
||||
acls, err = req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toObjectACLRules(acls.Items), nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) objectSet(ctx context.Context, entity ACLEntity, role ACLRole, isBucketDefault bool) error {
|
||||
type setRequest interface {
|
||||
Do(opts ...googleapi.CallOption) (*raw.ObjectAccessControl, error)
|
||||
Header() http.Header
|
||||
}
|
||||
|
||||
acl := &raw.ObjectAccessControl{
|
||||
Bucket: a.bucket,
|
||||
Entity: string(entity),
|
||||
Role: string(role),
|
||||
}
|
||||
var req setRequest
|
||||
if isBucketDefault {
|
||||
req = a.c.raw.DefaultObjectAccessControls.Update(a.bucket, string(entity), acl)
|
||||
} else {
|
||||
req = a.c.raw.ObjectAccessControls.Update(a.bucket, a.object, string(entity), acl)
|
||||
}
|
||||
a.configureCall(ctx, req)
|
||||
return runWithRetry(ctx, func() error {
|
||||
_, err := req.Do()
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACLHandle) objectDelete(ctx context.Context, entity ACLEntity) error {
|
||||
return runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.ObjectAccessControls.Delete(a.bucket, a.object, string(entity))
|
||||
a.configureCall(ctx, req)
|
||||
return req.Do()
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACLHandle) configureCall(ctx context.Context, call interface{ Header() http.Header }) {
|
||||
vc := reflect.ValueOf(call)
|
||||
vc.MethodByName("Context").Call([]reflect.Value{reflect.ValueOf(ctx)})
|
||||
if a.userProject != "" {
|
||||
vc.MethodByName("UserProject").Call([]reflect.Value{reflect.ValueOf(a.userProject)})
|
||||
}
|
||||
setClientHeader(call.Header())
|
||||
}
|
||||
|
||||
func toObjectACLRules(items []*raw.ObjectAccessControl) []ACLRule {
|
||||
var rs []ACLRule
|
||||
for _, item := range items {
|
||||
rs = append(rs, toObjectACLRule(item))
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
func toBucketACLRules(items []*raw.BucketAccessControl) []ACLRule {
|
||||
var rs []ACLRule
|
||||
for _, item := range items {
|
||||
rs = append(rs, toBucketACLRule(item))
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
func toObjectACLRule(a *raw.ObjectAccessControl) ACLRule {
|
||||
return ACLRule{
|
||||
Entity: ACLEntity(a.Entity),
|
||||
EntityID: a.EntityId,
|
||||
Role: ACLRole(a.Role),
|
||||
Domain: a.Domain,
|
||||
Email: a.Email,
|
||||
ProjectTeam: toObjectProjectTeam(a.ProjectTeam),
|
||||
}
|
||||
}
|
||||
|
||||
func toBucketACLRule(a *raw.BucketAccessControl) ACLRule {
|
||||
return ACLRule{
|
||||
Entity: ACLEntity(a.Entity),
|
||||
EntityID: a.EntityId,
|
||||
Role: ACLRole(a.Role),
|
||||
Domain: a.Domain,
|
||||
Email: a.Email,
|
||||
ProjectTeam: toBucketProjectTeam(a.ProjectTeam),
|
||||
}
|
||||
}
|
||||
|
||||
func toRawObjectACL(rules []ACLRule) []*raw.ObjectAccessControl {
|
||||
if len(rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
r := make([]*raw.ObjectAccessControl, 0, len(rules))
|
||||
for _, rule := range rules {
|
||||
r = append(r, rule.toRawObjectAccessControl("")) // bucket name unnecessary
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func toRawBucketACL(rules []ACLRule) []*raw.BucketAccessControl {
|
||||
if len(rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
r := make([]*raw.BucketAccessControl, 0, len(rules))
|
||||
for _, rule := range rules {
|
||||
r = append(r, rule.toRawBucketAccessControl("")) // bucket name unnecessary
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r ACLRule) toRawBucketAccessControl(bucket string) *raw.BucketAccessControl {
|
||||
return &raw.BucketAccessControl{
|
||||
Bucket: bucket,
|
||||
Entity: string(r.Entity),
|
||||
Role: string(r.Role),
|
||||
// The other fields are not settable.
|
||||
}
|
||||
}
|
||||
|
||||
func (r ACLRule) toRawObjectAccessControl(bucket string) *raw.ObjectAccessControl {
|
||||
return &raw.ObjectAccessControl{
|
||||
Bucket: bucket,
|
||||
Entity: string(r.Entity),
|
||||
Role: string(r.Role),
|
||||
// The other fields are not settable.
|
||||
}
|
||||
}
|
||||
|
||||
func toBucketProjectTeam(p *raw.BucketAccessControlProjectTeam) *ProjectTeam {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return &ProjectTeam{
|
||||
ProjectNumber: p.ProjectNumber,
|
||||
Team: p.Team,
|
||||
}
|
||||
}
|
||||
|
||||
func toObjectProjectTeam(p *raw.ObjectAccessControlProjectTeam) *ProjectTeam {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return &ProjectTeam{
|
||||
ProjectNumber: p.ProjectNumber,
|
||||
Team: p.Team,
|
||||
}
|
||||
}
|
||||
1263
vendor/cloud.google.com/go/storage/bucket.go
generated
vendored
Normal file
1263
vendor/cloud.google.com/go/storage/bucket.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
228
vendor/cloud.google.com/go/storage/copy.go
generated
vendored
Normal file
228
vendor/cloud.google.com/go/storage/copy.go
generated
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"cloud.google.com/go/internal/trace"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
)
|
||||
|
||||
// CopierFrom creates a Copier that can copy src to dst.
|
||||
// You can immediately call Run on the returned Copier, or
|
||||
// you can configure it first.
|
||||
//
|
||||
// For Requester Pays buckets, the user project of dst is billed, unless it is empty,
|
||||
// in which case the user project of src is billed.
|
||||
func (dst *ObjectHandle) CopierFrom(src *ObjectHandle) *Copier {
|
||||
return &Copier{dst: dst, src: src}
|
||||
}
|
||||
|
||||
// A Copier copies a source object to a destination.
|
||||
type Copier struct {
|
||||
// ObjectAttrs are optional attributes to set on the destination object.
|
||||
// Any attributes must be initialized before any calls on the Copier. Nil
|
||||
// or zero-valued attributes are ignored.
|
||||
ObjectAttrs
|
||||
|
||||
// RewriteToken can be set before calling Run to resume a copy
|
||||
// operation. After Run returns a non-nil error, RewriteToken will
|
||||
// have been updated to contain the value needed to resume the copy.
|
||||
RewriteToken string
|
||||
|
||||
// ProgressFunc can be used to monitor the progress of a multi-RPC copy
|
||||
// operation. If ProgressFunc is not nil and copying requires multiple
|
||||
// calls to the underlying service (see
|
||||
// https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite), then
|
||||
// ProgressFunc will be invoked after each call with the number of bytes of
|
||||
// content copied so far and the total size in bytes of the source object.
|
||||
//
|
||||
// ProgressFunc is intended to make upload progress available to the
|
||||
// application. For example, the implementation of ProgressFunc may update
|
||||
// a progress bar in the application's UI, or log the result of
|
||||
// float64(copiedBytes)/float64(totalBytes).
|
||||
//
|
||||
// ProgressFunc should return quickly without blocking.
|
||||
ProgressFunc func(copiedBytes, totalBytes uint64)
|
||||
|
||||
// The Cloud KMS key, in the form projects/P/locations/L/keyRings/R/cryptoKeys/K,
|
||||
// that will be used to encrypt the object. Overrides the object's KMSKeyName, if
|
||||
// any.
|
||||
//
|
||||
// Providing both a DestinationKMSKeyName and a customer-supplied encryption key
|
||||
// (via ObjectHandle.Key) on the destination object will result in an error when
|
||||
// Run is called.
|
||||
DestinationKMSKeyName string
|
||||
|
||||
dst, src *ObjectHandle
|
||||
}
|
||||
|
||||
// Run performs the copy.
|
||||
func (c *Copier) Run(ctx context.Context) (attrs *ObjectAttrs, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Copier.Run")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if err := c.src.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.dst.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.DestinationKMSKeyName != "" && c.dst.encryptionKey != nil {
|
||||
return nil, errors.New("storage: cannot use DestinationKMSKeyName with a customer-supplied encryption key")
|
||||
}
|
||||
// Convert destination attributes to raw form, omitting the bucket.
|
||||
// If the bucket is included but name or content-type aren't, the service
|
||||
// returns a 400 with "Required" as the only message. Omitting the bucket
|
||||
// does not cause any problems.
|
||||
rawObject := c.ObjectAttrs.toRawObject("")
|
||||
for {
|
||||
res, err := c.callRewrite(ctx, rawObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.ProgressFunc != nil {
|
||||
c.ProgressFunc(uint64(res.TotalBytesRewritten), uint64(res.ObjectSize))
|
||||
}
|
||||
if res.Done { // Finished successfully.
|
||||
return newObject(res.Resource), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Copier) callRewrite(ctx context.Context, rawObj *raw.Object) (*raw.RewriteResponse, error) {
|
||||
call := c.dst.c.raw.Objects.Rewrite(c.src.bucket, c.src.object, c.dst.bucket, c.dst.object, rawObj)
|
||||
|
||||
call.Context(ctx).Projection("full")
|
||||
if c.RewriteToken != "" {
|
||||
call.RewriteToken(c.RewriteToken)
|
||||
}
|
||||
if c.DestinationKMSKeyName != "" {
|
||||
call.DestinationKmsKeyName(c.DestinationKMSKeyName)
|
||||
}
|
||||
if c.PredefinedACL != "" {
|
||||
call.DestinationPredefinedAcl(c.PredefinedACL)
|
||||
}
|
||||
if err := applyConds("Copy destination", c.dst.gen, c.dst.conds, call); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.dst.userProject != "" {
|
||||
call.UserProject(c.dst.userProject)
|
||||
} else if c.src.userProject != "" {
|
||||
call.UserProject(c.src.userProject)
|
||||
}
|
||||
if err := applySourceConds(c.src.gen, c.src.conds, call); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := setEncryptionHeaders(call.Header(), c.dst.encryptionKey, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := setEncryptionHeaders(call.Header(), c.src.encryptionKey, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res *raw.RewriteResponse
|
||||
var err error
|
||||
setClientHeader(call.Header())
|
||||
err = runWithRetry(ctx, func() error { res, err = call.Do(); return err })
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.RewriteToken = res.RewriteToken
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ComposerFrom creates a Composer that can compose srcs into dst.
|
||||
// You can immediately call Run on the returned Composer, or you can
|
||||
// configure it first.
|
||||
//
|
||||
// The encryption key for the destination object will be used to decrypt all
|
||||
// source objects and encrypt the destination object. It is an error
|
||||
// to specify an encryption key for any of the source objects.
|
||||
func (dst *ObjectHandle) ComposerFrom(srcs ...*ObjectHandle) *Composer {
|
||||
return &Composer{dst: dst, srcs: srcs}
|
||||
}
|
||||
|
||||
// A Composer composes source objects into a destination object.
|
||||
//
|
||||
// For Requester Pays buckets, the user project of dst is billed.
|
||||
type Composer struct {
|
||||
// ObjectAttrs are optional attributes to set on the destination object.
|
||||
// Any attributes must be initialized before any calls on the Composer. Nil
|
||||
// or zero-valued attributes are ignored.
|
||||
ObjectAttrs
|
||||
|
||||
dst *ObjectHandle
|
||||
srcs []*ObjectHandle
|
||||
}
|
||||
|
||||
// Run performs the compose operation.
|
||||
func (c *Composer) Run(ctx context.Context) (attrs *ObjectAttrs, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Composer.Run")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if err := c.dst.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(c.srcs) == 0 {
|
||||
return nil, errors.New("storage: at least one source object must be specified")
|
||||
}
|
||||
|
||||
req := &raw.ComposeRequest{}
|
||||
// Compose requires a non-empty Destination, so we always set it,
|
||||
// even if the caller-provided ObjectAttrs is the zero value.
|
||||
req.Destination = c.ObjectAttrs.toRawObject(c.dst.bucket)
|
||||
for _, src := range c.srcs {
|
||||
if err := src.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if src.bucket != c.dst.bucket {
|
||||
return nil, fmt.Errorf("storage: all source objects must be in bucket %q, found %q", c.dst.bucket, src.bucket)
|
||||
}
|
||||
if src.encryptionKey != nil {
|
||||
return nil, fmt.Errorf("storage: compose source %s.%s must not have encryption key", src.bucket, src.object)
|
||||
}
|
||||
srcObj := &raw.ComposeRequestSourceObjects{
|
||||
Name: src.object,
|
||||
}
|
||||
if err := applyConds("ComposeFrom source", src.gen, src.conds, composeSourceObj{srcObj}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.SourceObjects = append(req.SourceObjects, srcObj)
|
||||
}
|
||||
|
||||
call := c.dst.c.raw.Objects.Compose(c.dst.bucket, c.dst.object, req).Context(ctx)
|
||||
if err := applyConds("ComposeFrom destination", c.dst.gen, c.dst.conds, call); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.dst.userProject != "" {
|
||||
call.UserProject(c.dst.userProject)
|
||||
}
|
||||
if c.PredefinedACL != "" {
|
||||
call.DestinationPredefinedAcl(c.PredefinedACL)
|
||||
}
|
||||
if err := setEncryptionHeaders(call.Header(), c.dst.encryptionKey, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var obj *raw.Object
|
||||
setClientHeader(call.Header())
|
||||
err = runWithRetry(ctx, func() error { obj, err = call.Do(); return err })
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newObject(obj), nil
|
||||
}
|
||||
203
vendor/cloud.google.com/go/storage/doc.go
generated
vendored
Normal file
203
vendor/cloud.google.com/go/storage/doc.go
generated
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package storage provides an easy way to work with Google Cloud Storage.
|
||||
Google Cloud Storage stores data in named objects, which are grouped into buckets.
|
||||
|
||||
More information about Google Cloud Storage is available at
|
||||
https://cloud.google.com/storage/docs.
|
||||
|
||||
See https://godoc.org/cloud.google.com/go for authentication, timeouts,
|
||||
connection pooling and similar aspects of this package.
|
||||
|
||||
All of the methods of this package use exponential backoff to retry calls that fail
|
||||
with certain errors, as described in
|
||||
https://cloud.google.com/storage/docs/exponential-backoff. Retrying continues
|
||||
indefinitely unless the controlling context is canceled or the client is closed. See
|
||||
context.WithTimeout and context.WithCancel.
|
||||
|
||||
|
||||
Creating a Client
|
||||
|
||||
To start working with this package, create a client:
|
||||
|
||||
ctx := context.Background()
|
||||
client, err := storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
The client will use your default application credentials.
|
||||
|
||||
If you only wish to access public data, you can create
|
||||
an unauthenticated client with
|
||||
|
||||
client, err := storage.NewClient(ctx, option.WithoutAuthentication())
|
||||
|
||||
Buckets
|
||||
|
||||
A Google Cloud Storage bucket is a collection of objects. To work with a
|
||||
bucket, make a bucket handle:
|
||||
|
||||
bkt := client.Bucket(bucketName)
|
||||
|
||||
A handle is a reference to a bucket. You can have a handle even if the
|
||||
bucket doesn't exist yet. To create a bucket in Google Cloud Storage,
|
||||
call Create on the handle:
|
||||
|
||||
if err := bkt.Create(ctx, projectID, nil); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
Note that although buckets are associated with projects, bucket names are
|
||||
global across all projects.
|
||||
|
||||
Each bucket has associated metadata, represented in this package by
|
||||
BucketAttrs. The third argument to BucketHandle.Create allows you to set
|
||||
the initial BucketAttrs of a bucket. To retrieve a bucket's attributes, use
|
||||
Attrs:
|
||||
|
||||
attrs, err := bkt.Attrs(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
fmt.Printf("bucket %s, created at %s, is located in %s with storage class %s\n",
|
||||
attrs.Name, attrs.Created, attrs.Location, attrs.StorageClass)
|
||||
|
||||
Objects
|
||||
|
||||
An object holds arbitrary data as a sequence of bytes, like a file. You
|
||||
refer to objects using a handle, just as with buckets, but unlike buckets
|
||||
you don't explicitly create an object. Instead, the first time you write
|
||||
to an object it will be created. You can use the standard Go io.Reader
|
||||
and io.Writer interfaces to read and write object data:
|
||||
|
||||
obj := bkt.Object("data")
|
||||
// Write something to obj.
|
||||
// w implements io.Writer.
|
||||
w := obj.NewWriter(ctx)
|
||||
// Write some text to obj. This will either create the object or overwrite whatever is there already.
|
||||
if _, err := fmt.Fprintf(w, "This object contains text.\n"); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// Close, just like writing a file.
|
||||
if err := w.Close(); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
// Read it back.
|
||||
r, err := obj.NewReader(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
defer r.Close()
|
||||
if _, err := io.Copy(os.Stdout, r); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// Prints "This object contains text."
|
||||
|
||||
Objects also have attributes, which you can fetch with Attrs:
|
||||
|
||||
objAttrs, err := obj.Attrs(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
fmt.Printf("object %s has size %d and can be read using %s\n",
|
||||
objAttrs.Name, objAttrs.Size, objAttrs.MediaLink)
|
||||
|
||||
Listing objects
|
||||
|
||||
Listing objects in a bucket is done with the Bucket.Objects method:
|
||||
|
||||
query := &storage.Query{Prefix: ""}
|
||||
|
||||
var names []string
|
||||
it := bkt.Objects(ctx, query)
|
||||
for {
|
||||
attrs, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
names = append(names, attrs.Name)
|
||||
}
|
||||
|
||||
If only a subset of object attributes is needed when listing, specifying this
|
||||
subset using Query.SetAttrSelection may speed up the listing process:
|
||||
|
||||
query := &storage.Query{Prefix: ""}
|
||||
query.SetAttrSelection([]string{"Name"})
|
||||
|
||||
// ... as before
|
||||
|
||||
ACLs
|
||||
|
||||
Both objects and buckets have ACLs (Access Control Lists). An ACL is a list of
|
||||
ACLRules, each of which specifies the role of a user, group or project. ACLs
|
||||
are suitable for fine-grained control, but you may prefer using IAM to control
|
||||
access at the project level (see
|
||||
https://cloud.google.com/storage/docs/access-control/iam).
|
||||
|
||||
To list the ACLs of a bucket or object, obtain an ACLHandle and call its List method:
|
||||
|
||||
acls, err := obj.ACL().List(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
for _, rule := range acls {
|
||||
fmt.Printf("%s has role %s\n", rule.Entity, rule.Role)
|
||||
}
|
||||
|
||||
You can also set and delete ACLs.
|
||||
|
||||
Conditions
|
||||
|
||||
Every object has a generation and a metageneration. The generation changes
|
||||
whenever the content changes, and the metageneration changes whenever the
|
||||
metadata changes. Conditions let you check these values before an operation;
|
||||
the operation only executes if the conditions match. You can use conditions to
|
||||
prevent race conditions in read-modify-write operations.
|
||||
|
||||
For example, say you've read an object's metadata into objAttrs. Now
|
||||
you want to write to that object, but only if its contents haven't changed
|
||||
since you read it. Here is how to express that:
|
||||
|
||||
w = obj.If(storage.Conditions{GenerationMatch: objAttrs.Generation}).NewWriter(ctx)
|
||||
// Proceed with writing as above.
|
||||
|
||||
Signed URLs
|
||||
|
||||
You can obtain a URL that lets anyone read or write an object for a limited time.
|
||||
You don't need to create a client to do this. See the documentation of
|
||||
SignedURL for details.
|
||||
|
||||
url, err := storage.SignedURL(bucketName, "shared-object", opts)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
fmt.Println(url)
|
||||
|
||||
Errors
|
||||
|
||||
Errors returned by this client are often of the type [`googleapi.Error`](https://godoc.org/google.golang.org/api/googleapi#Error).
|
||||
These errors can be introspected for more information by type asserting to the richer `googleapi.Error` type. For example:
|
||||
|
||||
if e, ok := err.(*googleapi.Error); ok {
|
||||
if e.Code == 409 { ... }
|
||||
}
|
||||
*/
|
||||
package storage // import "cloud.google.com/go/storage"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user