From 4ed6397239fc92f07155dca7b6dde8b594697208 Mon Sep 17 00:00:00 2001 From: Alexander Apalikov Date: Tue, 18 Dec 2018 18:56:04 +0300 Subject: [PATCH] Add webhook functionality into FleetAutoscaler Add webhook policy type into FleetAutoscaler declaration. Provided an example of a webhook pod which receives FleetAutoscaleReview with a Fleet status and based on that information calculates target replica set. This process is performed on FleetAustoscaler Sync every 30 seconds. Extends Buffer policy functionality with Webhook policy which is proposed in #334 comments. --- Gopkg.lock | 18 + README.md | 1 + docs/create_webhook_fleetautoscaler.md | 231 ++++++++ docs/fleetautoscaler_spec.md | 90 ++- examples/autoscaler-webhook/Dockerfile | 38 ++ examples/autoscaler-webhook/Makefile | 41 ++ examples/autoscaler-webhook/README.md | 12 + .../autoscaler-service.yaml | 66 +++ examples/autoscaler-webhook/main.go | 112 ++++ examples/fleet.yaml | 4 +- examples/webhookfleetautoscaler.yaml | 37 ++ .../templates/crds/fleetautoscaler.yaml | 13 + install/yaml/install.yaml | 13 + pkg/apis/stable/v1alpha1/fleetautoscaler.go | 83 ++- .../stable/v1alpha1/fleetautoscaler_test.go | 69 ++- .../stable/v1alpha1/zz_generated.deepcopy.go | 125 ++++ pkg/fleetautoscalers/controller_test.go | 55 ++ pkg/fleetautoscalers/fleetautoscalers.go | 72 +++ pkg/fleetautoscalers/fleetautoscalers_test.go | 93 ++- vendor/github.com/google/uuid/CONTRIBUTING.md | 10 + vendor/github.com/google/uuid/CONTRIBUTORS | 9 + vendor/github.com/google/uuid/LICENSE | 27 + vendor/github.com/google/uuid/README.md | 19 + vendor/github.com/google/uuid/dce.go | 80 +++ vendor/github.com/google/uuid/doc.go | 12 + vendor/github.com/google/uuid/go.mod | 1 + vendor/github.com/google/uuid/hash.go | 53 ++ vendor/github.com/google/uuid/json_test.go | 62 ++ vendor/github.com/google/uuid/marshal.go | 37 ++ vendor/github.com/google/uuid/node.go | 89 +++ vendor/github.com/google/uuid/node_js.go | 12 + vendor/github.com/google/uuid/node_net.go | 33 ++ vendor/github.com/google/uuid/seq_test.go | 66 +++ vendor/github.com/google/uuid/sql.go | 59 ++ vendor/github.com/google/uuid/sql_test.go | 113 ++++ vendor/github.com/google/uuid/time.go | 123 ++++ vendor/github.com/google/uuid/util.go | 43 ++ vendor/github.com/google/uuid/uuid.go | 245 ++++++++ vendor/github.com/google/uuid/uuid_test.go | 559 ++++++++++++++++++ vendor/github.com/google/uuid/version1.go | 44 ++ vendor/github.com/google/uuid/version4.go | 38 ++ .../github.com/pborman/uuid/CONTRIBUTING.md | 10 + vendor/github.com/pborman/uuid/CONTRIBUTORS | 1 + vendor/github.com/pborman/uuid/LICENSE | 27 + vendor/github.com/pborman/uuid/README.md | 15 + vendor/github.com/pborman/uuid/dce.go | 84 +++ vendor/github.com/pborman/uuid/doc.go | 13 + vendor/github.com/pborman/uuid/hash.go | 53 ++ vendor/github.com/pborman/uuid/marshal.go | 85 +++ .../github.com/pborman/uuid/marshal_test.go | 124 ++++ vendor/github.com/pborman/uuid/node.go | 50 ++ vendor/github.com/pborman/uuid/seq_test.go | 66 +++ vendor/github.com/pborman/uuid/sql.go | 68 +++ vendor/github.com/pborman/uuid/sql_test.go | 96 +++ vendor/github.com/pborman/uuid/time.go | 57 ++ vendor/github.com/pborman/uuid/util.go | 32 + vendor/github.com/pborman/uuid/uuid.go | 163 +++++ vendor/github.com/pborman/uuid/uuid_test.go | 410 +++++++++++++ vendor/github.com/pborman/uuid/version1.go | 23 + vendor/github.com/pborman/uuid/version4.go | 26 + 60 files changed, 4300 insertions(+), 10 deletions(-) create mode 100644 docs/create_webhook_fleetautoscaler.md create mode 100644 examples/autoscaler-webhook/Dockerfile create mode 100644 examples/autoscaler-webhook/Makefile create mode 100644 examples/autoscaler-webhook/README.md create mode 100644 examples/autoscaler-webhook/autoscaler-service.yaml create mode 100644 examples/autoscaler-webhook/main.go create mode 100644 examples/webhookfleetautoscaler.yaml create mode 100644 vendor/github.com/google/uuid/CONTRIBUTING.md create mode 100644 vendor/github.com/google/uuid/CONTRIBUTORS create mode 100644 vendor/github.com/google/uuid/LICENSE create mode 100644 vendor/github.com/google/uuid/README.md create mode 100644 vendor/github.com/google/uuid/dce.go create mode 100644 vendor/github.com/google/uuid/doc.go create mode 100644 vendor/github.com/google/uuid/go.mod create mode 100644 vendor/github.com/google/uuid/hash.go create mode 100644 vendor/github.com/google/uuid/json_test.go create mode 100644 vendor/github.com/google/uuid/marshal.go create mode 100644 vendor/github.com/google/uuid/node.go create mode 100644 vendor/github.com/google/uuid/node_js.go create mode 100644 vendor/github.com/google/uuid/node_net.go create mode 100644 vendor/github.com/google/uuid/seq_test.go create mode 100644 vendor/github.com/google/uuid/sql.go create mode 100644 vendor/github.com/google/uuid/sql_test.go create mode 100644 vendor/github.com/google/uuid/time.go create mode 100644 vendor/github.com/google/uuid/util.go create mode 100644 vendor/github.com/google/uuid/uuid.go create mode 100644 vendor/github.com/google/uuid/uuid_test.go create mode 100644 vendor/github.com/google/uuid/version1.go create mode 100644 vendor/github.com/google/uuid/version4.go create mode 100644 vendor/github.com/pborman/uuid/CONTRIBUTING.md create mode 100644 vendor/github.com/pborman/uuid/CONTRIBUTORS create mode 100644 vendor/github.com/pborman/uuid/LICENSE create mode 100644 vendor/github.com/pborman/uuid/README.md create mode 100644 vendor/github.com/pborman/uuid/dce.go create mode 100644 vendor/github.com/pborman/uuid/doc.go create mode 100644 vendor/github.com/pborman/uuid/hash.go create mode 100644 vendor/github.com/pborman/uuid/marshal.go create mode 100644 vendor/github.com/pborman/uuid/marshal_test.go create mode 100644 vendor/github.com/pborman/uuid/node.go create mode 100644 vendor/github.com/pborman/uuid/seq_test.go create mode 100644 vendor/github.com/pborman/uuid/sql.go create mode 100644 vendor/github.com/pborman/uuid/sql_test.go create mode 100644 vendor/github.com/pborman/uuid/time.go create mode 100644 vendor/github.com/pborman/uuid/util.go create mode 100644 vendor/github.com/pborman/uuid/uuid.go create mode 100644 vendor/github.com/pborman/uuid/uuid_test.go create mode 100644 vendor/github.com/pborman/uuid/version1.go create mode 100644 vendor/github.com/pborman/uuid/version4.go diff --git a/Gopkg.lock b/Gopkg.lock index 1b716b82b6..278e74227a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -101,6 +101,14 @@ pruneopts = "" revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1" +[[projects]] + digest = "1:a25a2c5ae694b01713fb6cd03c3b1ac1ccc1902b9f0a922680a88ec254f968e1" + name = "github.com/google/uuid" + packages = ["."] + pruneopts = "" + revision = "9b3b1e0f5f99ae461456d768e7d301a7acdaa2d8" + version = "v1.1.0" + [[projects]] digest = "1:2a131706ff80636629ab6373f2944569b8252ecc018cda8040931b05d32e3c16" name = "github.com/googleapis/gnostic" @@ -246,6 +254,14 @@ revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd" version = "1.0.1" +[[projects]] + digest = "1:a5484d4fa43127138ae6e7b2299a6a52ae006c7f803d98d717f60abf3e97192e" + name = "github.com/pborman/uuid" + packages = ["."] + pruneopts = "" + revision = "adf5a7427709b9deb95d29d3fa8a2bf9cfd388f1" + version = "v1.2" + [[projects]] digest = "1:9c740db1f7015dffa093aa5c70862d277fe49f5e92b56ca5d0d69ef0e37c01db" name = "github.com/pelletier/go-toml" @@ -640,6 +656,7 @@ "pkg/util/runtime", "pkg/util/sets", "pkg/util/strategicpatch", + "pkg/util/uuid", "pkg/util/validation", "pkg/util/validation/field", "pkg/util/wait", @@ -872,6 +889,7 @@ "k8s.io/apimachinery/pkg/util/clock", "k8s.io/apimachinery/pkg/util/intstr", "k8s.io/apimachinery/pkg/util/runtime", + "k8s.io/apimachinery/pkg/util/uuid", "k8s.io/apimachinery/pkg/util/validation/field", "k8s.io/apimachinery/pkg/util/wait", "k8s.io/apimachinery/pkg/util/yaml", diff --git a/README.md b/README.md index 5ee6d116f8..6acb73ffaf 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ Documentation and usage guides on how to develop and host dedicated game servers - [Create a Game Server](./docs/create_gameserver.md) - [Create a Game Server Fleet](./docs/create_fleet.md) - [Create a Fleet Autoscaler](./docs/create_fleetautoscaler.md) + - [Create a Webhook for FleetAutoscaler](./docs/create_webhook_fleetautoscaler.md) - [Edit Your First Game Server (Go)](./docs/edit_first_game_server.md) ### Guides diff --git a/docs/create_webhook_fleetautoscaler.md b/docs/create_webhook_fleetautoscaler.md new file mode 100644 index 0000000000..5612375c80 --- /dev/null +++ b/docs/create_webhook_fleetautoscaler.md @@ -0,0 +1,231 @@ +# Quickstart Create a Fleet Autoscaler with Webhook Policy + +This guide covers how you can create webhook fleet autoscaler policy. +The main difference from the Buffer policy is that the logic on how many target replicas you need is delegated to a separate pod. +This type of Autoscaler would send an HTTP request to the webhook endpoint every sync period (which is currently 30s) with a JSON body, and scale the target fleet based on the data that is returned. + +## Prerequisites + +It is assumed that you have read the instructions to [Create a Game Server Fleet](./create_fleet.md) +and you have a running fleet of game servers or you could run command from Step #1. + +## Objectives + +- Deploy the Webhook Pod and service for autoscaling +- Create a Fleet Autoscaler with Webhook policy type in Kubernetes using Agones custom resource +- Watch the Fleet scale up when allocating GameServers +- Watch the Fleet scale down after GameServer shutdown + +### 1. Deploy the fleet + +Run a fleet in a cluster: +``` +kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/agones/master/examples/simple-udp/fleet.yaml +``` + +### 2. Deploy a Webhook service for autoscaling + +We need to create a pod which will handle HTTP requests with json payload [`FleetAutoscaleReview`](./fleetautoscaler_spec.md#webhook-endpoint-specification) and return back it with [`FleetAutoscaleResponse`](./fleetautoscaler_spec.md#webhook-endpoint-specification) populated. + +``` +kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/agones/master/examples/autoscaler-webhook/autoscaler-service.yaml +``` + +To check that it is running and liveness probe is fine: +``` +kubectl describe pod autoscaler-webhook +``` + +``` +Name: autoscaler-webhook-86944884c4-sdtqh +Namespace: default +Node: gke-test-cluster-default-1c5dec79-h0tq/10.138.0.2 +... +Status: Running +``` + +### 3. Create a Fleet Autoscaler + +Let's create a Fleet Autoscaler using the following command : + +``` +kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/agones/master/examples/webhookfleetautoscaler.yaml +``` + +You should see a successful ouput similar to this : + +``` +fleetautoscaler.stable.agones.sev "webhook-fleet-autoscaler" created +``` + +This has created a FleetAutoscaler record inside Kubernetes. +It has the link to Webhook service we deployed above. + +### 4. See the fleet and autoscaler status. + +In order to track the list of gameservers which run in your fleet you can run this command in a separate terminal tab: + +``` +watch "kubectl get gs -o=custom-columns=NAME:.metadata.name,STATUS:.status.state,IP:.status.address,PORT:.status.ports[0].port -n default" +``` + +In order to get autoscaler status use the following command: + +``` +kubectl describe fleetautoscaler webhook-fleet-autoscaler +``` + +It should look something like this: + +``` +Name: webhook-fleet-autoscaler +Namespace: default +Labels: +Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"stable.agones.dev/v1alpha1","kind":"FleetAutoscaler","metadata":{"annotations":{},"name":"webhook-fleet-autoscaler","namespace":"default... +API Version: stable.agones.dev/v1alpha1 +Kind: FleetAutoscaler +etadata: + Cluster Name: + Creation Timestamp: 2018-12-22T12:52:23Z + Generation: 1 + Resource Version: 2274579 + Self Link: /apis/stable.agones.dev/v1alpha1/namespaces/default/fleetautoscalers/webhook-fleet-autoscaler + UID: 6d03eae4-05e8-11e9-84c2-42010a8a01c9 +Spec: + Fleet Name: simple-udp + Policy: + Type: Webhook + Webhook: + Service: + Name: autoscaler-webhook-service + Namespace: default + Path: scale + URL: +Status: + Able To Scale: true + Current Replicas: 2 + Desired Replicas: 2 + Last Scale Time: + Scaling Limited: false +Events: +``` + +You can see the status (able to scale, not limited), the last time the fleet was scaled (nil for never), current and desired fleet size. + +The autoscaler make a query to a webhoook service deployed on step 1 and on response changing the target Replica size, and the fleet creates/deletes game server instances +to achieve that number. The convergence is achieved in time, which is usually measured in seconds. + +### 5. Allocate Game Servers from the Fleet to trigger scale up + +If you're interested in more details for game server allocation, you should consult the [Create a Game Server Fleet](./create_fleet.md) page. +Here we only interested in triggering allocations to see the autoscaler in action. + +``` +kubectl create -f https://raw.githubusercontent.com/GoogleCloudPlatform/agones/master/examples/simple-udp/fleetallocation.yaml -o yaml +``` + +You should get in return the allocated game server details, which should end with something like: +``` + status: + address: 35.247.13.175 + nodeName: gke-test-cluster-default-1c5dec79-qrqv + ports: + - name: default + port: 7047 + state: Allocated +``` + +Note the address and port, you might need them later to connect to the server. + +Run the above kubectl command one more time so that we have both servers allocated. + +### 6. Check new Autoscaler and Fleet status + +Now let's wait a few seconds to allow the autoscaler to detect the change in the fleet and check again its status + +``` +kubectl describe fleetautoscaler webhook-fleet-autoscaler +``` + +The last part should look similar to this: + +``` +Spec: + Fleet Name: simple-udp + Policy: + Type: Webhook + Webhook: + Service: + Name: autoscaler-webhook-service + Namespace: default + Path: scale + URL: +Status: + Able To Scale: true + Current Replicas: 4 + Desired Replicas: 4 + Last Scale Time: 2018-12-22T12:53:47Z + Scaling Limited: false +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal AutoScalingFleet 35s fleetautoscaler-controller Scaling fleet simple-udp from 2 to 4 +``` + +You can see that the fleet size has increased in particular case doubled to 4 gameservers, the autoscaler having compensated for the two allocated instances. +Last Scale Time has been updated and a scaling event has been logged. + +Double-check the actual number of game server instances and status by running: + +``` +kubectl get gs -o=custom-columns=NAME:.metadata.name,STATUS:.status.state,IP:.status.address,PORT:.status.ports +``` + +This will get you a list of all the current `GameSevers` and their `Status > State`. + +``` +NAME STATUS IP PORT +simple-udp-dmkp4-8pkk2 Ready 35.247.13.175 [map[name:default port:7386]] +simple-udp-dmkp4-b7x87 Allocated 35.247.13.175 [map[name:default port:7219]] +simple-udp-dmkp4-r4qtt Allocated 35.247.13.175 [map[name:default port:7220]] +simple-udp-dmkp4-rsr6n Ready 35.247.13.175 [map[name:default port:7297]] +``` + +### 7. Check down scaling using Webhook Autoscaler policy + +If the fraction of allocated replicas in whole Replicas count would be less that threshold (0.3) than fleet would scale down by scaleFactor in our example 2. +Note that example webhook server have a limitation that it would not decrease fleet replica count under `minReplicasCount` = 2. +We need to run EXIT command on one gameserver (Use IP address and port of the allocated gameserver from the previous step) in order to decrease the number of allocated gameservers in a fleet (<0.3). +``` +nc -u 35.247.13.175 7220 +EXIT +``` + +Server would be in shutdown state. +Wait about 30 seconds. +Then you should see in the output of next command scaling down event: +``` +kubectl describe fleetautoscaler webhook-fleet-autoscaler +``` +You should see these lines in events: +``` + Normal AutoScalingFleet 11m fleetautoscaler-controller Scaling fleet simple-udp from 2 to 4 + Normal AutoScalingFleet 1m fleetautoscaler-controller Scaling fleet simple-udp from 4 to 2 +``` + +And get gameservers command output: +``` +kubectl get gs -o=custom-columns=NAME:.metadata.name,STATUS:.status.state,IP:.status.address,PORT:.status.ports[0].port -n default +``` + +``` +NAME STATUS IP PORT +simple-udp-884fg-6q5sk Ready 35.247.117.202 7373 +simple-udp-884fg-b7l58 Allocated 35.247.117.202 7766 +``` + +## Next Steps + +Read the advanced [Scheduling and Autoscaling](scheduling_autoscaling.md) guide, for more details on autoscaling. + +If you want to use your own GameServer container make sure you have properly integrated the [Agones SDK](../sdks/). diff --git a/docs/fleetautoscaler_spec.md b/docs/fleetautoscaler_spec.md index f74073ea51..6c494b43ab 100644 --- a/docs/fleetautoscaler_spec.md +++ b/docs/fleetautoscaler_spec.md @@ -21,6 +21,24 @@ spec: maxReplicas: 20 ``` +Or for Webhook FleetAutoscaler below and in [example folder](../examples/webhookfleetautoscaler.yaml): + +```yaml +apiVersion: "stable.agones.dev/v1alpha1" +kind: FleetAutoscaler +metadata: + name: fleet-autoscaler-example +spec: + + fleetName: fleet-example + policy: + type: Webhook + webhook: + name: "fleet-autoscaler-webhook" + namespace: "default" + path: "/scale" +``` + Since Agones defines a new [Custom Resources Definition (CRD)](https://kubernetes.io/docs/concepts/api-extension/custom-resources/) we can define a new resource using the kind `FleetAutoscaler` with the custom group `stable.agones.dev` and API @@ -31,12 +49,78 @@ The `spec` field is the actual `FleetAutoscaler` specification and it is compose - `fleetName` is name of the fleet to attach to and control. Must be an existing `Fleet` in the same namespace as this `FleetAutoscaler`. - `policy` is the autoscaling policy - - `type` is type of the policy. For now, only "Buffer" is available - - `buffer` parameters of the buffer policy + - `type` is type of the policy. "Buffer" and "Webhook" are available + - `buffer` parameters of the buffer policy type - `bufferSize` is the size of a buffer of "ready" game server instances The FleetAutoscaler will scale the fleet up and down trying to maintain this buffer, as instances are being allocated or terminated it can be specified either in absolute (i.e. 5) or percentage format (i.e. 5%) - `minReplicas` is the minimum fleet size to be set by this FleetAutoscaler. if not specified, the minimum fleet size will be bufferSize - - `maxReplicas` is the maximum fleet size that can be set by this FleetAutoscaler. Required. \ No newline at end of file + - `maxReplicas` is the maximum fleet size that can be set by this FleetAutoscaler. Required. + - `webhook` parameters of the webhook policy type + - `service` parameters + - `name` is the service name bound to Deployment of autoscaler webhook [(see example)](../examples/autoscaler-webhook/autoscaler-service.yaml) + The FleetAutoscaler will scale the fleet up and down based on the response from this webhook server + - `namespace` is the kubernetes namespace where webhook is deployed. + If not specified, the "default" would be used + - `path` is the endpoint on the service (i. e. /autoscale) + - `url` is a general URL to make post request to it (optional, instead of service) + +Note: only one `buffer` or `webhook` could be defined for FleetAutoscaler depends on `type` field. For Webhook policy we can define either `service` or `url`. + + +# Webhook Endpoint Specification + +Webhook endpoint is used to delegate the scaling logic to a separate pod or server. + +FleetAutoscaler would send a request to the webhook endpoint every sync period (which is currently 30s) with a JSON body, and scale the target fleet based on the data that is returned. +JSON payload with a FleetAutoscaleReview data structure would be sent to webhook endpoint and received from it with FleetAutoscaleResponse field populated. FleetAutoscaleResponse contains target Replica count which would trigger scaling of the fleet according to it. + +The connection to this webhook endpoint should be defined in `FleetAutoscaler` using Webhook policy type. + +```go +type FleetAutoscaleRequest struct { + // UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are + // otherwise identical (parallel requests, requests when earlier requests did not modify etc) + // The UID is meant to track the round trip (request/response) between the Autoscaler and the WebHook, not the user request. + // It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging. + UID types.UID `json:"uid""` + // Name is the name of the Fleet being scaled + Name string `json:"name"` + // Namespace is the namespace associated with the request (if any). + Namespace string `json:"namespace"` + // The Fleet's status values + Status v1alpha1.FleetStatus `json:"status"` +} + +type FleetAutoscaleResponse struct { + // UID is an identifier for the individual request/response. + // This should be copied over from the corresponding FleetAutoscaleRequest. + UID types.UID `json:"uid"` + // The targeted replica count + // Set to nil if no scaling should occur to the Fleet + Replicas *int32 `json:"replicas"` +} + +// FleetAutoscaleReview is passed to the webhook with a populated Request value, +// and then returned with a populated Response. +type FleetAutoscaleReview struct { + Request *FleetAutoscaleRequest `json:"request"` + Response *FleetAutoscaleResponse `json:"response"` +} + +// FleetStatus is the status of a Fleet +type FleetStatus struct { + // Replicas the total number of current GameServer replicas + Replicas int32 `json:"replicas"` + // ReadyReplicas are the number of Ready GameServer replicas + ReadyReplicas int32 `json:"readyReplicas"` + // AllocatedReplicas are the number of Allocated GameServer replicas + AllocatedReplicas int32 `json:"allocatedReplicas"` +} +``` + +The example of the webhook written in Go could be found [here](../examples/autoscaler-webhook/main.go). + +It implements the [scaling logic](../examples/autoscaler-webhook/README.md) based on the percentage of allocated gameservers in a fleet. diff --git a/examples/autoscaler-webhook/Dockerfile b/examples/autoscaler-webhook/Dockerfile new file mode 100644 index 0000000000..cd4c574f56 --- /dev/null +++ b/examples/autoscaler-webhook/Dockerfile @@ -0,0 +1,38 @@ +# Copyright 2018 Google Inc. All Rights Reserved. +# +# 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. + +# Gather dependencies and build the executable +FROM golang:1.10.3 as builder + +WORKDIR /go/src/agones.dev +RUN git clone https://github.com/GoogleCloudPlatform/agones.git + +WORKDIR /go/src/agones.dev/agones/examples/autoscaler-webhook +COPY ./main.go . +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o service . + + +# Create the final image that will run the webhook service for FleetAutoscaler webhook policy +FROM alpine:3.8 +RUN apk add --update ca-certificates +RUN adduser -D service + +COPY --from=builder /go/src/agones.dev/agones/examples/autoscaler-webhook \ + /home/service + +RUN chown -R service /home/service && \ + chmod o+x /home/service/service + +USER service +ENTRYPOINT /home/service/service diff --git a/examples/autoscaler-webhook/Makefile b/examples/autoscaler-webhook/Makefile new file mode 100644 index 0000000000..4a01e6b36d --- /dev/null +++ b/examples/autoscaler-webhook/Makefile @@ -0,0 +1,41 @@ +# Copyright 2017 Google Inc. All Rights Reserved. +# +# 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. + +# +# Makefile for building a simple udp client and server +# + +# __ __ _ _ _ +# \ \ / /_ _ _ __(_) __ _| |__ | | ___ ___ +# \ \ / / _` | '__| |/ _` | '_ \| |/ _ \ __| +# \ V / (_| | | | | (_| | |_) | | __\__ \ +# \_/ \__,_|_| |_|\__,_|_.__/|_|\___|___/ +# + +REPOSITORY = gcr.io/agones-images + +mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) +project_path := $(dir $(mkfile_path)) +autoscaler_webhook_tag = $(REPOSITORY)/autoscaler-webhook:0.1 + +# _____ _ +# |_ _|_ _ _ __ __ _ ___| |_ ___ +# | |/ _` | '__/ _` |/ _ \ __/ __| +# | | (_| | | | (_| | __/ |_\__ \ +# |_|\__,_|_| \__, |\___|\__|___/ +# |___/ + +# Build a docker image for the server, and tag it +build: + docker build -f $(project_path)/Dockerfile --tag=$(autoscaler_webhook_tag) . diff --git a/examples/autoscaler-webhook/README.md b/examples/autoscaler-webhook/README.md new file mode 100644 index 0000000000..ce07f6440d --- /dev/null +++ b/examples/autoscaler-webhook/README.md @@ -0,0 +1,12 @@ +# Simple Webhook Autoscaler Service + +This service provides an example of the webhook fleetautoscaler service which is used to control the number of GameServers in a Fleet (`Replica` count). + +## Autoscaler Service +The service exposes an endpoint which allows client calls to custom scaling logic. + +When this endpoint is called, target Replica count gets calculated. If fleet don't need to scale we return nil as a `Replica` parameter. Endpoint receives and returns the JSON encoded [`FleetAutoscaleReview`](./fleetautoscaler_spec.md#webhook-endpoint-specification) . + +Note that scaling up logic is based on the percentage of allocated gameservers in a fleet. If this fraction is more than threshold (i. e. 0.7) than `Replica` value is returned increased by the `scaleFactor` (in this example twice) which results in creating more Ready GameServers. If the fraction below the threshold (i. e. 0.3) we decrease a count of gameservers. + +To learn how to deploy the fleet to GKE, please see the tutorial [Create a Fleet (Go)](../../docs/create_fleet.md). \ No newline at end of file diff --git a/examples/autoscaler-webhook/autoscaler-service.yaml b/examples/autoscaler-webhook/autoscaler-service.yaml new file mode 100644 index 0000000000..6a9f4924ea --- /dev/null +++ b/examples/autoscaler-webhook/autoscaler-service.yaml @@ -0,0 +1,66 @@ +# Copyright 2018 Google Inc. All Rights Reserved. +# +# 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. + +# Define a Service for the autoscaler-webhook + +apiVersion: v1 +kind: Service +metadata: + name: autoscaler-webhook-service + annotations: + service.alpha.kubernetes.io/app-protocols: '{"http":"HTTP"}' + labels: + app: autoscaler-webhook +spec: + selector: + app: autoscaler-webhook + ports: + - port: 8000 + protocol: TCP + name: https + targetPort: autoscaler +--- +# Deploy a pod to run the autoscaler-webhook code +apiVersion: apps/v1 +kind: Deployment +metadata: + name: autoscaler-webhook + namespace: default + labels: + app: autoscaler-webhook +spec: + replicas: 1 + selector: + matchLabels: + app: autoscaler-webhook + template: + metadata: + labels: + app: autoscaler-webhook + spec: + #serviceAccount: autoscaler-webhook + containers: + - name: autoscaler-webhook + image: gcr.io/agones-images/autoscaler-webhook:0.1 + imagePullPolicy: Always + ports: + - name: autoscaler + containerPort: 8000 + livenessProbe: + httpGet: + scheme: HTTP + path: /health + port: 8000 + initialDelaySeconds: 3 + periodSeconds: 5 diff --git a/examples/autoscaler-webhook/main.go b/examples/autoscaler-webhook/main.go new file mode 100644 index 0000000000..77589113fd --- /dev/null +++ b/examples/autoscaler-webhook/main.go @@ -0,0 +1,112 @@ +// Copyright 2018 Google Inc. All Rights Reserved. +// +// 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. + +//Autoscaler webhook server which handles FleetAutoscaleReview json payload +package main + +import ( + "encoding/json" + "io" + "io/ioutil" + "net/http" + + v1alpha1 "agones.dev/agones/pkg/apis/stable/v1alpha1" + "agones.dev/agones/pkg/util/runtime" // for the logger +) + +// Constants which define thresholds to trigger scalling up and scale factor +const ( + replicaUpperThreshold = 0.7 + replicaLowerThreshold = 0.3 + scaleFactor = 2 + minReplicasCount = 2 +) + +// Variables for the logger +var ( + logger = runtime.NewLoggerWithSource("main") +) + +// Main will set up an http server and three endpoints +func main() { + // Serve 200 status on /health for k8s health checks + http.HandleFunc("/health", handleHealth) + + // Return the target replica count which is used by Webhook fleet autoscaling policy + http.HandleFunc("/scale", handleAutoscale) + + logger.Info("Starting HTTP server on port 8000") + if err := http.ListenAndServe(":8000", nil); err != nil { + logger.WithError(err).Fatal("HTTP server failed to run") + } +} + +// Let /health return Healthy and status code 200 +func handleHealth(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, err := io.WriteString(w, "Healthy") + if err != nil { + logger.WithError(err).Fatal("Error writing string Healthy from /health") + } +} + +// handleAutoscale is a handler function which return the replica count +// based on received status of the fleet +func handleAutoscale(w http.ResponseWriter, r *http.Request) { + if r == nil { + http.Error(w, "Empty request", http.StatusInternalServerError) + return + } + + var faReq v1alpha1.FleetAutoscaleReview + res, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + err = json.Unmarshal(res, &faReq) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + faResp := v1alpha1.FleetAutoscaleResponse{ + Replicas: nil, + UID: faReq.Request.UID, + } + if faReq.Request.Status.Replicas != 0 { + allocatedPercent := float32(faReq.Request.Status.AllocatedReplicas) / float32(faReq.Request.Status.Replicas) + if allocatedPercent > replicaUpperThreshold { + faResp.UID = faReq.Request.UID + // After scaling we would have percentage of 0.7/2 = 0.35 > replicaLowerThreshold + // So we won't scale down immediately after scale up + targetReplicas := faReq.Request.Status.Replicas * scaleFactor + faResp.Replicas = &targetReplicas + } else if allocatedPercent < replicaLowerThreshold && faReq.Request.Status.Replicas > minReplicasCount { + faResp.UID = faReq.Request.UID + targetReplicas := faReq.Request.Status.Replicas / scaleFactor + faResp.Replicas = &targetReplicas + } + } + w.Header().Set("Content-Type", "application/json") + review := &v1alpha1.FleetAutoscaleReview{ + Request: faReq.Request, + Response: &faResp, + } + logger.Infof("FleetAutoscaleReview Request: %+v ; Response: %+v", *review.Request, *review.Response) + result, _ := json.Marshal(&review) + + _, err = io.WriteString(w, string(result)) + if err != nil { + logger.WithError(err).Fatal("Error writing json from /scale") + } +} diff --git a/examples/fleet.yaml b/examples/fleet.yaml index 7ef1eec9c5..cddbd1dcde 100644 --- a/examples/fleet.yaml +++ b/examples/fleet.yaml @@ -66,5 +66,5 @@ spec: template: spec: containers: - - name: example-server - image: gcr.io/agones/test-server:0.1 \ No newline at end of file + - name: simple-udp + image: gcr.io/agones-images/udp-server:0.5 \ No newline at end of file diff --git a/examples/webhookfleetautoscaler.yaml b/examples/webhookfleetautoscaler.yaml new file mode 100644 index 0000000000..1e683ad8b4 --- /dev/null +++ b/examples/webhookfleetautoscaler.yaml @@ -0,0 +1,37 @@ +# Copyright 2018 Google Inc. All Rights Reserved. +# +# 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. + +# +# Full example of a FleetAutoscaler - this is used to scale a Fleet +# automatically depending on load +# +apiVersion: "stable.agones.dev/v1alpha1" +kind: FleetAutoscaler +metadata: + name: webhook-fleet-autoscaler +spec: + fleetName: simple-udp + policy: + # type of the policy - this example is Webhook + type: Webhook + # parameters for the webhook policy - this is a WebhookClientConfig, as per other K8s webhooks + webhook: + # use a service, or URL + service: + name: autoscaler-webhook-service + namespace: default + path: scale + # optional for URL defined webhooks + # url: "" + # caBundle: TBD (optional, if you want to provide your own ca cert to test against) \ No newline at end of file diff --git a/install/helm/agones/templates/crds/fleetautoscaler.yaml b/install/helm/agones/templates/crds/fleetautoscaler.yaml index 4d02ff4fdb..145f644f00 100644 --- a/install/helm/agones/templates/crds/fleetautoscaler.yaml +++ b/install/helm/agones/templates/crds/fleetautoscaler.yaml @@ -55,6 +55,7 @@ spec: type: string enum: - Buffer + - Webhook buffer: required: - maxReplicas @@ -65,4 +66,16 @@ spec: maxReplicas: type: integer minimum: 1 + webhook: + properties: + service: + properties: + name: + type: string + namespace: + type: string + path: + type: string + url: + type: string {{- end }} \ No newline at end of file diff --git a/install/yaml/install.yaml b/install/yaml/install.yaml index f690457c5c..c7722119b0 100644 --- a/install/yaml/install.yaml +++ b/install/yaml/install.yaml @@ -442,6 +442,7 @@ spec: type: string enum: - Buffer + - Webhook buffer: required: - maxReplicas @@ -452,6 +453,18 @@ spec: maxReplicas: type: integer minimum: 1 + webhook: + properties: + service: + properties: + name: + type: string + namespace: + type: string + path: + type: string + url: + type: string --- # Source: agones/templates/crds/gameserver.yaml # Copyright 2018 Google Inc. All Rights Reserved. diff --git a/pkg/apis/stable/v1alpha1/fleetautoscaler.go b/pkg/apis/stable/v1alpha1/fleetautoscaler.go index d4f6dfb180..87cb1bd32b 100644 --- a/pkg/apis/stable/v1alpha1/fleetautoscaler.go +++ b/pkg/apis/stable/v1alpha1/fleetautoscaler.go @@ -14,8 +14,12 @@ package v1alpha1 -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -import "k8s.io/apimachinery/pkg/util/intstr" +import ( + admregv1b "k8s.io/api/admissionregistration/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" +) // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -55,6 +59,9 @@ type FleetAutoscalerPolicy struct { // Buffer policy config params. Present only if FleetAutoscalerPolicyType = Buffer. // +optional Buffer *BufferPolicy `json:"buffer,omitempty"` + // Webhook policy config params. Present only if FleetAutoscalerPolicyType = Webhook. + // +optional + Webhook *WebhookPolicy `json:"webhook,omitempty"` } // FleetAutoscalerPolicyType is the policy for autoscaling @@ -65,6 +72,9 @@ const ( // BufferPolicyType FleetAutoscalerPolicyType is a simple buffering strategy for Ready // GameServers BufferPolicyType FleetAutoscalerPolicyType = "Buffer" + // WebhookPolicyType is a simple webhook strategy used for horizontal fleet scaling + // GameServers + WebhookPolicyType FleetAutoscalerPolicyType = "Webhook" ) // BufferPolicy controls the desired behavior of the buffer policy. @@ -91,6 +101,11 @@ type BufferPolicy struct { BufferSize intstr.IntOrString `json:"bufferSize"` } +// WebhookPolicy controls the desired behavior of the webhook policy. +// It contains the description of the webhook autoscaler service +// used to form url which is accessible inside the cluster +type WebhookPolicy admregv1b.WebhookClientConfig + // FleetAutoscalerStatus defines the current status of a FleetAutoscaler type FleetAutoscalerStatus struct { // CurrentReplicas is the current number of gameserver replicas @@ -113,10 +128,72 @@ type FleetAutoscalerStatus struct { ScalingLimited bool `json:"scalingLimited"` } +// FleetAutoscaleRequest defines the request to webhook autoscaler endpoint +type FleetAutoscaleRequest struct { + // UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are + // otherwise identical (parallel requests, requests when earlier requests did not modify etc) + // The UID is meant to track the round trip (request/response) between the Autoscaler and the WebHook, not the user request. + // It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging. + UID types.UID `json:"uid"` + // Name is the name of the Fleet being scaled + Name string `json:"name"` + // Namespace is the namespace associated with the request (if any). + Namespace string `json:"namespace"` + // The Fleet's status values + Status FleetStatus `json:"status"` +} + +// FleetAutoscaleResponse defines the response of webhook autoscaler endpoint +type FleetAutoscaleResponse struct { + // UID is an identifier for the individual request/response. + // This should be copied over from the corresponding FleetAutoscaleRequest. + UID types.UID `json:"uid"` + // The targeted replica count + // Set to nil if no scaling should occur to the Fleet + Replicas *int32 `json:"replicas"` +} + +// FleetAutoscaleReview is passed to the webhook with a populated Request value, +// and then returned with a populated Response. +type FleetAutoscaleReview struct { + Request *FleetAutoscaleRequest `json:"request"` + Response *FleetAutoscaleResponse `json:"response"` +} + // Validate validates the FleetAutoscaler scaling settings func (fas *FleetAutoscaler) Validate(causes []metav1.StatusCause) []metav1.StatusCause { - if fas.Spec.Policy.Type == BufferPolicyType { + switch fas.Spec.Policy.Type { + case BufferPolicyType: causes = fas.Spec.Policy.Buffer.ValidateBufferPolicy(causes) + + case WebhookPolicyType: + causes = fas.Spec.Policy.Webhook.ValidateWebhookPolicy(causes) + } + return causes +} + +// ValidateWebhookPolicy validates the FleetAutoscaler Webhook policy settings +func (w *WebhookPolicy) ValidateWebhookPolicy(causes []metav1.StatusCause) []metav1.StatusCause { + if w == nil { + return append(causes, metav1.StatusCause{ + Type: metav1.CauseTypeFieldValueInvalid, + Field: "webhook", + Message: "webhook policy config params are missing", + }) + } + if w.Service == nil && w.URL == nil { + causes = append(causes, metav1.StatusCause{ + Type: metav1.CauseTypeFieldValueNotFound, + Field: "url", + Message: "url should be provided", + }) + } + if w.Service != nil && w.URL != nil { + causes = append(causes, metav1.StatusCause{ + Type: metav1.CauseTypeFieldValueNotFound, + Field: "url", + Message: "service and url cannot be used simultaneously", + }) } return causes } diff --git a/pkg/apis/stable/v1alpha1/fleetautoscaler_test.go b/pkg/apis/stable/v1alpha1/fleetautoscaler_test.go index c6b4841bf1..67b164d646 100644 --- a/pkg/apis/stable/v1alpha1/fleetautoscaler_test.go +++ b/pkg/apis/stable/v1alpha1/fleetautoscaler_test.go @@ -18,6 +18,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + admregv1b "k8s.io/api/admissionregistration/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" ) @@ -99,9 +100,60 @@ func TestFleetAutoscalerValidateUpdate(t *testing.T) { assert.Equal(t, "bufferSize", causes[0].Field) }) } +func TestFleetAutoscalerWebhookValidateUpdate(t *testing.T) { + t.Parallel() + + t.Run("good service value", func(t *testing.T) { + fas := webhookFixture() + causes := fas.Validate(nil) + + assert.Len(t, causes, 0) + }) + + t.Run("good url value", func(t *testing.T) { + fas := webhookFixture() + causes := fas.Validate(nil) + url := "http://good.example.com" + fas.Spec.Policy.Webhook.URL = &url + fas.Spec.Policy.Webhook.Service = nil + + assert.Len(t, causes, 0) + }) + + t.Run("bad URL and service value", func(t *testing.T) { + fas := webhookFixture() + fas.Spec.Policy.Webhook.URL = nil + fas.Spec.Policy.Webhook.Service = nil + causes := fas.Validate(nil) + + assert.Len(t, causes, 1) + assert.Equal(t, "url", causes[0].Field) + }) + + t.Run("both URL and service value are used - fail", func(t *testing.T) { + + fas := webhookFixture() + url := "123" + fas.Spec.Policy.Webhook.URL = &url + + causes := fas.Validate(nil) + + assert.Len(t, causes, 1) + assert.Equal(t, "url", causes[0].Field) + }) + +} func defaultFixture() *FleetAutoscaler { - return &FleetAutoscaler{ + return customFixture(BufferPolicyType) +} + +func webhookFixture() *FleetAutoscaler { + return customFixture(WebhookPolicyType) +} + +func customFixture(t FleetAutoscalerPolicyType) *FleetAutoscaler { + res := &FleetAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "test"}, Spec: FleetAutoscalerSpec{ FleetName: "testing", @@ -114,4 +166,19 @@ func defaultFixture() *FleetAutoscaler { }, }, } + switch t { + case BufferPolicyType: + case WebhookPolicyType: + res.Spec.Policy.Type = WebhookPolicyType + res.Spec.Policy.Buffer = nil + url := "/scale" + res.Spec.Policy.Webhook = &WebhookPolicy{ + Service: &admregv1b.ServiceReference{ + Name: "service1", + Namespace: "default", + Path: &url, + }, + } + } + return res } diff --git a/pkg/apis/stable/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/stable/v1alpha1/zz_generated.deepcopy.go index d85b58df42..2ac7f6baa5 100644 --- a/pkg/apis/stable/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/stable/v1alpha1/zz_generated.deepcopy.go @@ -21,6 +21,7 @@ package v1alpha1 import ( + v1beta1 "k8s.io/api/admissionregistration/v1beta1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -202,6 +203,82 @@ func (in *FleetAllocationStatus) DeepCopy() *FleetAllocationStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FleetAutoscaleRequest) DeepCopyInto(out *FleetAutoscaleRequest) { + *out = *in + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FleetAutoscaleRequest. +func (in *FleetAutoscaleRequest) DeepCopy() *FleetAutoscaleRequest { + if in == nil { + return nil + } + out := new(FleetAutoscaleRequest) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FleetAutoscaleResponse) DeepCopyInto(out *FleetAutoscaleResponse) { + *out = *in + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FleetAutoscaleResponse. +func (in *FleetAutoscaleResponse) DeepCopy() *FleetAutoscaleResponse { + if in == nil { + return nil + } + out := new(FleetAutoscaleResponse) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FleetAutoscaleReview) DeepCopyInto(out *FleetAutoscaleReview) { + *out = *in + if in.Request != nil { + in, out := &in.Request, &out.Request + if *in == nil { + *out = nil + } else { + *out = new(FleetAutoscaleRequest) + **out = **in + } + } + if in.Response != nil { + in, out := &in.Response, &out.Response + if *in == nil { + *out = nil + } else { + *out = new(FleetAutoscaleResponse) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FleetAutoscaleReview. +func (in *FleetAutoscaleReview) DeepCopy() *FleetAutoscaleReview { + if in == nil { + return nil + } + out := new(FleetAutoscaleReview) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FleetAutoscaler) DeepCopyInto(out *FleetAutoscaler) { *out = *in @@ -275,6 +352,15 @@ func (in *FleetAutoscalerPolicy) DeepCopyInto(out *FleetAutoscalerPolicy) { **out = **in } } + if in.Webhook != nil { + in, out := &in.Webhook, &out.Webhook + if *in == nil { + *out = nil + } else { + *out = new(WebhookPolicy) + (*in).DeepCopyInto(*out) + } + } return } @@ -660,3 +746,42 @@ func (in *Health) DeepCopy() *Health { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WebhookPolicy) DeepCopyInto(out *WebhookPolicy) { + *out = *in + if in.URL != nil { + in, out := &in.URL, &out.URL + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + if in.Service != nil { + in, out := &in.Service, &out.Service + if *in == nil { + *out = nil + } else { + *out = new(v1beta1.ServiceReference) + (*in).DeepCopyInto(*out) + } + } + if in.CABundle != nil { + in, out := &in.CABundle, &out.CABundle + *out = make([]byte, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookPolicy. +func (in *WebhookPolicy) DeepCopy() *WebhookPolicy { + if in == nil { + return nil + } + out := new(WebhookPolicy) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/fleetautoscalers/controller_test.go b/pkg/fleetautoscalers/controller_test.go index 3f3e98a7eb..b49fd86d7c 100644 --- a/pkg/fleetautoscalers/controller_test.go +++ b/pkg/fleetautoscalers/controller_test.go @@ -25,6 +25,7 @@ import ( "github.com/heptiolabs/healthcheck" "github.com/stretchr/testify/assert" admv1beta1 "k8s.io/api/admission/v1beta1" + admregv1b "k8s.io/api/admissionregistration/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" @@ -73,6 +74,45 @@ func TestControllerCreationValidationHandler(t *testing.T) { }) } +func TestWebhookControllerCreationValidationHandler(t *testing.T) { + t.Parallel() + + t.Run("valid fleet autoscaler", func(t *testing.T) { + c, m := newFakeController() + fas, _ := defaultWebhookFixtures() + _, cancel := agtesting.StartInformers(m) + defer cancel() + + review, err := newAdmissionReview(*fas) + assert.Nil(t, err) + + result, err := c.validationHandler(review) + assert.Nil(t, err) + assert.True(t, result.Response.Allowed, fmt.Sprintf("%#v", result.Response)) + }) + + t.Run("invalid fleet autoscaler", func(t *testing.T) { + c, m := newFakeController() + fas, _ := defaultWebhookFixtures() + // this make it invalid + fas.Spec.Policy.Webhook = nil + + _, cancel := agtesting.StartInformers(m) + defer cancel() + + review, err := newAdmissionReview(*fas) + assert.Nil(t, err) + + result, err := c.validationHandler(review) + fmt.Printf("%+v", result) + assert.Nil(t, err) + assert.False(t, result.Response.Allowed, fmt.Sprintf("%#v", result.Response)) + assert.Equal(t, metav1.StatusFailure, result.Response.Result.Status) + assert.Equal(t, metav1.StatusReasonInvalid, result.Response.Result.Reason) + assert.NotEmpty(t, result.Response.Result.Details) + }) +} + // nolint:dupl func TestControllerSyncFleetAutoscaler(t *testing.T) { t.Parallel() @@ -445,6 +485,21 @@ func defaultFixtures() (*v1alpha1.FleetAutoscaler, *v1alpha1.Fleet) { return fas, f } +func defaultWebhookFixtures() (*v1alpha1.FleetAutoscaler, *v1alpha1.Fleet) { + fas, f := defaultFixtures() + fas.Spec.Policy.Type = v1alpha1.WebhookPolicyType + fas.Spec.Policy.Buffer = nil + url := "/autoscaler" + fas.Spec.Policy.Webhook = &v1alpha1.WebhookPolicy{ + Service: &admregv1b.ServiceReference{ + Name: "fleetautoscaler-service", + Path: &url, + }, + } + + return fas, f +} + // newFakeController returns a controller, backed by the fake Clientset func newFakeController() (*Controller, agtesting.Mocks) { m := agtesting.NewMocks() diff --git a/pkg/fleetautoscalers/fleetautoscalers.go b/pkg/fleetautoscalers/fleetautoscalers.go index 5c1f510775..f4109fbd04 100644 --- a/pkg/fleetautoscalers/fleetautoscalers.go +++ b/pkg/fleetautoscalers/fleetautoscalers.go @@ -17,10 +17,18 @@ package fleetautoscalers import ( + "encoding/json" + "fmt" + "io/ioutil" "math" + "net/http" + "strings" + "time" + "agones.dev/agones/pkg/apis/stable/v1alpha1" stablev1alpha1 "agones.dev/agones/pkg/apis/stable/v1alpha1" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/uuid" ) // computeDesiredFleetSize computes the new desired size of the given fleet @@ -29,8 +37,72 @@ func computeDesiredFleetSize(fas *stablev1alpha1.FleetAutoscaler, f *stablev1alp switch fas.Spec.Policy.Type { case stablev1alpha1.BufferPolicyType: return applyBufferPolicy(fas.Spec.Policy.Buffer, f) + case stablev1alpha1.WebhookPolicyType: + return applyWebhookPolicy(fas.Spec.Policy.Webhook, f) } + return f.Status.Replicas, false, fmt.Errorf("Wrong Policy Type") +} + +func applyWebhookPolicy(w *stablev1alpha1.WebhookPolicy, f *stablev1alpha1.Fleet) (int32, bool, error) { + faReq := v1alpha1.FleetAutoscaleReview{ + Request: &v1alpha1.FleetAutoscaleRequest{ + UID: uuid.NewUUID(), + Name: f.Name, + Namespace: f.Namespace, + Status: f.Status, + }, + Response: nil, + } + b, err := json.Marshal(faReq) + url := "" + if w.URL != nil { + url = *w.URL + } + var faResp v1alpha1.FleetAutoscaleReview + servicePath := "" + if w.Service != nil { + if w.Service.Path != nil { + servicePath = *w.Service.Path + } + if err != nil { + return f.Status.Replicas, false, err + } + + if w.Service.Namespace == "" { + w.Service.Namespace = "default" + } + url = fmt.Sprintf("http://%s.%s.svc:8000/%s", w.Service.Name, w.Service.Namespace, servicePath) + } + if url == "" { + return f.Status.Replicas, false, fmt.Errorf("URL was not provided") + } + c := &http.Client{ + Timeout: 15 * time.Second, + } + res, err := c.Post( + url, + "application/json", + strings.NewReader(string(b)), + ) + if err != nil { + return f.Status.Replicas, false, err + } + defer res.Body.Close() // nolint: errcheck + if res.StatusCode != http.StatusOK { + return f.Status.Replicas, false, fmt.Errorf("Bad Status code from the server") + } + result, err := ioutil.ReadAll(res.Body) + if err != nil { + return f.Status.Replicas, false, err + } + err = json.Unmarshal(result, &faResp) + if err != nil { + return f.Status.Replicas, false, err + } + if faResp.Response.Replicas != nil { + return *faResp.Response.Replicas, false, nil + } return f.Status.Replicas, false, nil } diff --git a/pkg/fleetautoscalers/fleetautoscalers_test.go b/pkg/fleetautoscalers/fleetautoscalers_test.go index c68093dc55..4049e5f383 100644 --- a/pkg/fleetautoscalers/fleetautoscalers_test.go +++ b/pkg/fleetautoscalers/fleetautoscalers_test.go @@ -17,12 +17,22 @@ package fleetautoscalers import ( + "encoding/json" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" "testing" + "agones.dev/agones/pkg/apis/stable/v1alpha1" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/util/intstr" ) +const ( + scaleFactor = 2 +) + func TestComputeDesiredFleetSize(t *testing.T) { t.Parallel() @@ -44,7 +54,7 @@ func TestComputeDesiredFleetSize(t *testing.T) { f.Status.Replicas = 61 fas.Spec.Policy.Type = "" replicas, limited, err = computeDesiredFleetSize(fas, f) - assert.Nil(t, err) + assert.NotNil(t, err) assert.Equal(t, replicas, int32(61)) assert.Equal(t, limited, false) } @@ -112,3 +122,84 @@ func TestApplyBufferPolicy(t *testing.T) { assert.Equal(t, replicas, int32(2)) assert.Equal(t, limited, false) } + +type testServer struct{} + +func (t testServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r == nil { + http.Error(w, "Empty request", http.StatusInternalServerError) + return + } + + var faRequest v1alpha1.FleetAutoscaleReview + + res, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + err = json.Unmarshal(res, &faRequest) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + faReq := faRequest.Request + faResp := v1alpha1.FleetAutoscaleResponse{ + Replicas: nil, + UID: faReq.UID, + } + allocatedPercent := float32(faReq.Status.AllocatedReplicas) / float32(faReq.Status.Replicas) + if allocatedPercent > 0.7 { + faResp.UID = faReq.UID + targetReplicas := faReq.Status.Replicas * scaleFactor + faResp.Replicas = &targetReplicas + } + w.Header().Set("Content-Type", "application/json") + review := &v1alpha1.FleetAutoscaleReview{ + Request: faReq, + Response: &faResp, + } + result, _ := json.Marshal(&review) + + _, err = io.WriteString(w, string(result)) + if err != nil { + http.Error(w, "Error writing json from /address", http.StatusInternalServerError) + } +} +func TestApplyWebhookPolicy(t *testing.T) { + t.Parallel() + + fas, f := defaultWebhookFixtures() + w := fas.Spec.Policy.Webhook + w.Service = nil + + ts := testServer{} + server := httptest.NewServer(ts) + defer server.Close() + w.URL = &(server.URL) + f.Spec.Replicas = 50 + f.Status.Replicas = f.Spec.Replicas + f.Status.AllocatedReplicas = 10 + f.Status.ReadyReplicas = 40 + + replicas, limited, err := applyWebhookPolicy(w, f) + assert.Nil(t, err) + assert.Equal(t, f.Spec.Replicas, replicas) + assert.Equal(t, limited, false) + + f.Spec.Replicas = 50 + f.Status.Replicas = f.Spec.Replicas + f.Status.AllocatedReplicas = 40 + f.Status.ReadyReplicas = 10 + replicas, limited, err = applyWebhookPolicy(w, f) + assert.Nil(t, err) + assert.Equal(t, f.Status.Replicas*scaleFactor, replicas) + assert.Equal(t, limited, false) + + f.Spec.Replicas = 50 + f.Status.Replicas = f.Spec.Replicas + f.Status.AllocatedReplicas = 35 + f.Status.ReadyReplicas = 15 + replicas, limited, err = applyWebhookPolicy(w, f) + assert.Nil(t, err) + assert.Equal(t, replicas, f.Spec.Replicas) + assert.Equal(t, limited, false) +} diff --git a/vendor/github.com/google/uuid/CONTRIBUTING.md b/vendor/github.com/google/uuid/CONTRIBUTING.md new file mode 100644 index 0000000000..04fdf09f13 --- /dev/null +++ b/vendor/github.com/google/uuid/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# How to contribute + +We definitely welcome patches and contribution to this project! + +### Legal requirements + +In order to protect both you and ourselves, you will need to sign the +[Contributor License Agreement](https://cla.developers.google.com/clas). + +You may have already signed it for other Google projects. diff --git a/vendor/github.com/google/uuid/CONTRIBUTORS b/vendor/github.com/google/uuid/CONTRIBUTORS new file mode 100644 index 0000000000..b4bb97f6bc --- /dev/null +++ b/vendor/github.com/google/uuid/CONTRIBUTORS @@ -0,0 +1,9 @@ +Paul Borman +bmatsuo +shawnps +theory +jboverfelt +dsymonds +cd1 +wallclockbuilder +dansouza diff --git a/vendor/github.com/google/uuid/LICENSE b/vendor/github.com/google/uuid/LICENSE new file mode 100644 index 0000000000..5dc68268d9 --- /dev/null +++ b/vendor/github.com/google/uuid/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009,2014 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/google/uuid/README.md b/vendor/github.com/google/uuid/README.md new file mode 100644 index 0000000000..9d92c11f16 --- /dev/null +++ b/vendor/github.com/google/uuid/README.md @@ -0,0 +1,19 @@ +# uuid ![build status](https://travis-ci.org/google/uuid.svg?branch=master) +The uuid package generates and inspects UUIDs based on +[RFC 4122](http://tools.ietf.org/html/rfc4122) +and DCE 1.1: Authentication and Security Services. + +This package is based on the github.com/pborman/uuid package (previously named +code.google.com/p/go-uuid). It differs from these earlier packages in that +a UUID is a 16 byte array rather than a byte slice. One loss due to this +change is the ability to represent an invalid UUID (vs a NIL UUID). + +###### Install +`go get github.com/google/uuid` + +###### Documentation +[![GoDoc](https://godoc.org/github.com/google/uuid?status.svg)](http://godoc.org/github.com/google/uuid) + +Full `go doc` style documentation for the package can be viewed online without +installing this package by using the GoDoc site here: +http://godoc.org/github.com/google/uuid diff --git a/vendor/github.com/google/uuid/dce.go b/vendor/github.com/google/uuid/dce.go new file mode 100644 index 0000000000..fa820b9d30 --- /dev/null +++ b/vendor/github.com/google/uuid/dce.go @@ -0,0 +1,80 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "fmt" + "os" +) + +// A Domain represents a Version 2 domain +type Domain byte + +// Domain constants for DCE Security (Version 2) UUIDs. +const ( + Person = Domain(0) + Group = Domain(1) + Org = Domain(2) +) + +// NewDCESecurity returns a DCE Security (Version 2) UUID. +// +// The domain should be one of Person, Group or Org. +// On a POSIX system the id should be the users UID for the Person +// domain and the users GID for the Group. The meaning of id for +// the domain Org or on non-POSIX systems is site defined. +// +// For a given domain/id pair the same token may be returned for up to +// 7 minutes and 10 seconds. +func NewDCESecurity(domain Domain, id uint32) (UUID, error) { + uuid, err := NewUUID() + if err == nil { + uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2 + uuid[9] = byte(domain) + binary.BigEndian.PutUint32(uuid[0:], id) + } + return uuid, err +} + +// NewDCEPerson returns a DCE Security (Version 2) UUID in the person +// domain with the id returned by os.Getuid. +// +// NewDCESecurity(Person, uint32(os.Getuid())) +func NewDCEPerson() (UUID, error) { + return NewDCESecurity(Person, uint32(os.Getuid())) +} + +// NewDCEGroup returns a DCE Security (Version 2) UUID in the group +// domain with the id returned by os.Getgid. +// +// NewDCESecurity(Group, uint32(os.Getgid())) +func NewDCEGroup() (UUID, error) { + return NewDCESecurity(Group, uint32(os.Getgid())) +} + +// Domain returns the domain for a Version 2 UUID. Domains are only defined +// for Version 2 UUIDs. +func (uuid UUID) Domain() Domain { + return Domain(uuid[9]) +} + +// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2 +// UUIDs. +func (uuid UUID) ID() uint32 { + return binary.BigEndian.Uint32(uuid[0:4]) +} + +func (d Domain) String() string { + switch d { + case Person: + return "Person" + case Group: + return "Group" + case Org: + return "Org" + } + return fmt.Sprintf("Domain%d", int(d)) +} diff --git a/vendor/github.com/google/uuid/doc.go b/vendor/github.com/google/uuid/doc.go new file mode 100644 index 0000000000..5b8a4b9af8 --- /dev/null +++ b/vendor/github.com/google/uuid/doc.go @@ -0,0 +1,12 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package uuid generates and inspects UUIDs. +// +// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security +// Services. +// +// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to +// maps or compared directly. +package uuid diff --git a/vendor/github.com/google/uuid/go.mod b/vendor/github.com/google/uuid/go.mod new file mode 100644 index 0000000000..fc84cd79d4 --- /dev/null +++ b/vendor/github.com/google/uuid/go.mod @@ -0,0 +1 @@ +module github.com/google/uuid diff --git a/vendor/github.com/google/uuid/hash.go b/vendor/github.com/google/uuid/hash.go new file mode 100644 index 0000000000..b174616315 --- /dev/null +++ b/vendor/github.com/google/uuid/hash.go @@ -0,0 +1,53 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "crypto/md5" + "crypto/sha1" + "hash" +) + +// Well known namespace IDs and UUIDs +var ( + NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) + NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) + NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")) + NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")) + Nil UUID // empty UUID, all zeros +) + +// NewHash returns a new UUID derived from the hash of space concatenated with +// data generated by h. The hash should be at least 16 byte in length. The +// first 16 bytes of the hash are used to form the UUID. The version of the +// UUID will be the lower 4 bits of version. NewHash is used to implement +// NewMD5 and NewSHA1. +func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID { + h.Reset() + h.Write(space[:]) + h.Write(data) + s := h.Sum(nil) + var uuid UUID + copy(uuid[:], s) + uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4) + uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant + return uuid +} + +// NewMD5 returns a new MD5 (Version 3) UUID based on the +// supplied name space and data. It is the same as calling: +// +// NewHash(md5.New(), space, data, 3) +func NewMD5(space UUID, data []byte) UUID { + return NewHash(md5.New(), space, data, 3) +} + +// NewSHA1 returns a new SHA1 (Version 5) UUID based on the +// supplied name space and data. It is the same as calling: +// +// NewHash(sha1.New(), space, data, 5) +func NewSHA1(space UUID, data []byte) UUID { + return NewHash(sha1.New(), space, data, 5) +} diff --git a/vendor/github.com/google/uuid/json_test.go b/vendor/github.com/google/uuid/json_test.go new file mode 100644 index 0000000000..245f91edfb --- /dev/null +++ b/vendor/github.com/google/uuid/json_test.go @@ -0,0 +1,62 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/json" + "reflect" + "testing" +) + +var testUUID = Must(Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")) + +func TestJSON(t *testing.T) { + type S struct { + ID1 UUID + ID2 UUID + } + s1 := S{ID1: testUUID} + data, err := json.Marshal(&s1) + if err != nil { + t.Fatal(err) + } + var s2 S + if err := json.Unmarshal(data, &s2); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(&s1, &s2) { + t.Errorf("got %#v, want %#v", s2, s1) + } +} + +func BenchmarkUUID_MarshalJSON(b *testing.B) { + x := &struct { + UUID UUID `json:"uuid"` + }{} + var err error + x.UUID, err = Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if err != nil { + b.Fatal(err) + } + for i := 0; i < b.N; i++ { + js, err := json.Marshal(x) + if err != nil { + b.Fatalf("marshal json: %#v (%v)", js, err) + } + } +} + +func BenchmarkUUID_UnmarshalJSON(b *testing.B) { + js := []byte(`{"uuid":"f47ac10b-58cc-0372-8567-0e02b2c3d479"}`) + var x *struct { + UUID UUID `json:"uuid"` + } + for i := 0; i < b.N; i++ { + err := json.Unmarshal(js, &x) + if err != nil { + b.Fatalf("marshal json: %#v (%v)", js, err) + } + } +} diff --git a/vendor/github.com/google/uuid/marshal.go b/vendor/github.com/google/uuid/marshal.go new file mode 100644 index 0000000000..7f9e0c6c0e --- /dev/null +++ b/vendor/github.com/google/uuid/marshal.go @@ -0,0 +1,37 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import "fmt" + +// MarshalText implements encoding.TextMarshaler. +func (uuid UUID) MarshalText() ([]byte, error) { + var js [36]byte + encodeHex(js[:], uuid) + return js[:], nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (uuid *UUID) UnmarshalText(data []byte) error { + id, err := ParseBytes(data) + if err == nil { + *uuid = id + } + return err +} + +// MarshalBinary implements encoding.BinaryMarshaler. +func (uuid UUID) MarshalBinary() ([]byte, error) { + return uuid[:], nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +func (uuid *UUID) UnmarshalBinary(data []byte) error { + if len(data) != 16 { + return fmt.Errorf("invalid UUID (got %d bytes)", len(data)) + } + copy(uuid[:], data) + return nil +} diff --git a/vendor/github.com/google/uuid/node.go b/vendor/github.com/google/uuid/node.go new file mode 100644 index 0000000000..3e4e90dc44 --- /dev/null +++ b/vendor/github.com/google/uuid/node.go @@ -0,0 +1,89 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "sync" +) + +var ( + nodeMu sync.Mutex + ifname string // name of interface being used + nodeID [6]byte // hardware for version 1 UUIDs + zeroID [6]byte // nodeID with only 0's +) + +// NodeInterface returns the name of the interface from which the NodeID was +// derived. The interface "user" is returned if the NodeID was set by +// SetNodeID. +func NodeInterface() string { + defer nodeMu.Unlock() + nodeMu.Lock() + return ifname +} + +// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs. +// If name is "" then the first usable interface found will be used or a random +// Node ID will be generated. If a named interface cannot be found then false +// is returned. +// +// SetNodeInterface never fails when name is "". +func SetNodeInterface(name string) bool { + defer nodeMu.Unlock() + nodeMu.Lock() + return setNodeInterface(name) +} + +func setNodeInterface(name string) bool { + iname, addr := getHardwareInterface(name) // null implementation for js + if iname != "" && addr != nil { + ifname = iname + copy(nodeID[:], addr) + return true + } + + // We found no interfaces with a valid hardware address. If name + // does not specify a specific interface generate a random Node ID + // (section 4.1.6) + if name == "" { + randomBits(nodeID[:]) + return true + } + return false +} + +// NodeID returns a slice of a copy of the current Node ID, setting the Node ID +// if not already set. +func NodeID() []byte { + defer nodeMu.Unlock() + nodeMu.Lock() + if nodeID == zeroID { + setNodeInterface("") + } + nid := nodeID + return nid[:] +} + +// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes +// of id are used. If id is less than 6 bytes then false is returned and the +// Node ID is not set. +func SetNodeID(id []byte) bool { + if len(id) < 6 { + return false + } + defer nodeMu.Unlock() + nodeMu.Lock() + copy(nodeID[:], id) + ifname = "user" + return true +} + +// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is +// not valid. The NodeID is only well defined for version 1 and 2 UUIDs. +func (uuid UUID) NodeID() []byte { + var node [6]byte + copy(node[:], uuid[10:]) + return node[:] +} diff --git a/vendor/github.com/google/uuid/node_js.go b/vendor/github.com/google/uuid/node_js.go new file mode 100644 index 0000000000..24b78edc90 --- /dev/null +++ b/vendor/github.com/google/uuid/node_js.go @@ -0,0 +1,12 @@ +// Copyright 2017 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build js + +package uuid + +// getHardwareInterface returns nil values for the JS version of the code. +// This remvoves the "net" dependency, because it is not used in the browser. +// Using the "net" library inflates the size of the transpiled JS code by 673k bytes. +func getHardwareInterface(name string) (string, []byte) { return "", nil } diff --git a/vendor/github.com/google/uuid/node_net.go b/vendor/github.com/google/uuid/node_net.go new file mode 100644 index 0000000000..0cbbcddbd6 --- /dev/null +++ b/vendor/github.com/google/uuid/node_net.go @@ -0,0 +1,33 @@ +// Copyright 2017 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !js + +package uuid + +import "net" + +var interfaces []net.Interface // cached list of interfaces + +// getHardwareInterface returns the name and hardware address of interface name. +// If name is "" then the name and hardware address of one of the system's +// interfaces is returned. If no interfaces are found (name does not exist or +// there are no interfaces) then "", nil is returned. +// +// Only addresses of at least 6 bytes are returned. +func getHardwareInterface(name string) (string, []byte) { + if interfaces == nil { + var err error + interfaces, err = net.Interfaces() + if err != nil { + return "", nil + } + } + for _, ifs := range interfaces { + if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) { + return ifs.Name, ifs.HardwareAddr + } + } + return "", nil +} diff --git a/vendor/github.com/google/uuid/seq_test.go b/vendor/github.com/google/uuid/seq_test.go new file mode 100644 index 0000000000..4f6c549125 --- /dev/null +++ b/vendor/github.com/google/uuid/seq_test.go @@ -0,0 +1,66 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "flag" + "runtime" + "testing" + "time" +) + +// This test is only run when --regressions is passed on the go test line. +var regressions = flag.Bool("regressions", false, "run uuid regression tests") + +// TestClockSeqRace tests for a particular race condition of returning two +// identical Version1 UUIDs. The duration of 1 minute was chosen as the race +// condition, before being fixed, nearly always occurred in under 30 seconds. +func TestClockSeqRace(t *testing.T) { + if !*regressions { + t.Skip("skipping regression tests") + } + duration := time.Minute + + done := make(chan struct{}) + defer close(done) + + ch := make(chan UUID, 10000) + ncpu := runtime.NumCPU() + switch ncpu { + case 0, 1: + // We can't run the test effectively. + t.Skip("skipping race test, only one CPU detected") + return + default: + runtime.GOMAXPROCS(ncpu) + } + for i := 0; i < ncpu; i++ { + go func() { + for { + select { + case <-done: + return + case ch <- Must(NewUUID()): + } + } + }() + } + + uuids := make(map[string]bool) + cnt := 0 + start := time.Now() + for u := range ch { + s := u.String() + if uuids[s] { + t.Errorf("duplicate uuid after %d in %v: %s", cnt, time.Since(start), s) + return + } + uuids[s] = true + if time.Since(start) > duration { + return + } + cnt++ + } +} diff --git a/vendor/github.com/google/uuid/sql.go b/vendor/github.com/google/uuid/sql.go new file mode 100644 index 0000000000..f326b54db3 --- /dev/null +++ b/vendor/github.com/google/uuid/sql.go @@ -0,0 +1,59 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "database/sql/driver" + "fmt" +) + +// Scan implements sql.Scanner so UUIDs can be read from databases transparently +// Currently, database types that map to string and []byte are supported. Please +// consult database-specific driver documentation for matching types. +func (uuid *UUID) Scan(src interface{}) error { + switch src := src.(type) { + case nil: + return nil + + case string: + // if an empty UUID comes from a table, we return a null UUID + if src == "" { + return nil + } + + // see Parse for required string format + u, err := Parse(src) + if err != nil { + return fmt.Errorf("Scan: %v", err) + } + + *uuid = u + + case []byte: + // if an empty UUID comes from a table, we return a null UUID + if len(src) == 0 { + return nil + } + + // assumes a simple slice of bytes if 16 bytes + // otherwise attempts to parse + if len(src) != 16 { + return uuid.Scan(string(src)) + } + copy((*uuid)[:], src) + + default: + return fmt.Errorf("Scan: unable to scan type %T into UUID", src) + } + + return nil +} + +// Value implements sql.Valuer so that UUIDs can be written to databases +// transparently. Currently, UUIDs map to strings. Please consult +// database-specific driver documentation for matching types. +func (uuid UUID) Value() (driver.Value, error) { + return uuid.String(), nil +} diff --git a/vendor/github.com/google/uuid/sql_test.go b/vendor/github.com/google/uuid/sql_test.go new file mode 100644 index 0000000000..1803dfd879 --- /dev/null +++ b/vendor/github.com/google/uuid/sql_test.go @@ -0,0 +1,113 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "strings" + "testing" +) + +func TestScan(t *testing.T) { + stringTest := "f47ac10b-58cc-0372-8567-0e02b2c3d479" + badTypeTest := 6 + invalidTest := "f47ac10b-58cc-0372-8567-0e02b2c3d4" + + byteTest := make([]byte, 16) + byteTestUUID := Must(Parse(stringTest)) + copy(byteTest, byteTestUUID[:]) + + // sunny day tests + + var uuid UUID + err := (&uuid).Scan(stringTest) + if err != nil { + t.Fatal(err) + } + + err = (&uuid).Scan([]byte(stringTest)) + if err != nil { + t.Fatal(err) + } + + err = (&uuid).Scan(byteTest) + if err != nil { + t.Fatal(err) + } + + // bad type tests + + err = (&uuid).Scan(badTypeTest) + if err == nil { + t.Error("int correctly parsed and shouldn't have") + } + if !strings.Contains(err.Error(), "unable to scan type") { + t.Error("attempting to parse an int returned an incorrect error message") + } + + // invalid/incomplete uuids + + err = (&uuid).Scan(invalidTest) + if err == nil { + t.Error("invalid uuid was parsed without error") + } + if !strings.Contains(err.Error(), "invalid UUID") { + t.Error("attempting to parse an invalid UUID returned an incorrect error message") + } + + err = (&uuid).Scan(byteTest[:len(byteTest)-2]) + if err == nil { + t.Error("invalid byte uuid was parsed without error") + } + if !strings.Contains(err.Error(), "invalid UUID") { + t.Error("attempting to parse an invalid byte UUID returned an incorrect error message") + } + + // empty tests + + uuid = UUID{} + var emptySlice []byte + err = (&uuid).Scan(emptySlice) + if err != nil { + t.Fatal(err) + } + + for _, v := range uuid { + if v != 0 { + t.Error("UUID was not nil after scanning empty byte slice") + } + } + + uuid = UUID{} + var emptyString string + err = (&uuid).Scan(emptyString) + if err != nil { + t.Fatal(err) + } + for _, v := range uuid { + if v != 0 { + t.Error("UUID was not nil after scanning empty byte slice") + } + } + + uuid = UUID{} + err = (&uuid).Scan(nil) + if err != nil { + t.Fatal(err) + } + for _, v := range uuid { + if v != 0 { + t.Error("UUID was not nil after scanning nil") + } + } +} + +func TestValue(t *testing.T) { + stringTest := "f47ac10b-58cc-0372-8567-0e02b2c3d479" + uuid := Must(Parse(stringTest)) + val, _ := uuid.Value() + if val != stringTest { + t.Error("Value() did not return expected string") + } +} diff --git a/vendor/github.com/google/uuid/time.go b/vendor/github.com/google/uuid/time.go new file mode 100644 index 0000000000..e6ef06cdc8 --- /dev/null +++ b/vendor/github.com/google/uuid/time.go @@ -0,0 +1,123 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "sync" + "time" +) + +// A Time represents a time as the number of 100's of nanoseconds since 15 Oct +// 1582. +type Time int64 + +const ( + lillian = 2299160 // Julian day of 15 Oct 1582 + unix = 2440587 // Julian day of 1 Jan 1970 + epoch = unix - lillian // Days between epochs + g1582 = epoch * 86400 // seconds between epochs + g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs +) + +var ( + timeMu sync.Mutex + lasttime uint64 // last time we returned + clockSeq uint16 // clock sequence for this run + + timeNow = time.Now // for testing +) + +// UnixTime converts t the number of seconds and nanoseconds using the Unix +// epoch of 1 Jan 1970. +func (t Time) UnixTime() (sec, nsec int64) { + sec = int64(t - g1582ns100) + nsec = (sec % 10000000) * 100 + sec /= 10000000 + return sec, nsec +} + +// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and +// clock sequence as well as adjusting the clock sequence as needed. An error +// is returned if the current time cannot be determined. +func GetTime() (Time, uint16, error) { + defer timeMu.Unlock() + timeMu.Lock() + return getTime() +} + +func getTime() (Time, uint16, error) { + t := timeNow() + + // If we don't have a clock sequence already, set one. + if clockSeq == 0 { + setClockSequence(-1) + } + now := uint64(t.UnixNano()/100) + g1582ns100 + + // If time has gone backwards with this clock sequence then we + // increment the clock sequence + if now <= lasttime { + clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000 + } + lasttime = now + return Time(now), clockSeq, nil +} + +// ClockSequence returns the current clock sequence, generating one if not +// already set. The clock sequence is only used for Version 1 UUIDs. +// +// The uuid package does not use global static storage for the clock sequence or +// the last time a UUID was generated. Unless SetClockSequence is used, a new +// random clock sequence is generated the first time a clock sequence is +// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) +func ClockSequence() int { + defer timeMu.Unlock() + timeMu.Lock() + return clockSequence() +} + +func clockSequence() int { + if clockSeq == 0 { + setClockSequence(-1) + } + return int(clockSeq & 0x3fff) +} + +// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to +// -1 causes a new sequence to be generated. +func SetClockSequence(seq int) { + defer timeMu.Unlock() + timeMu.Lock() + setClockSequence(seq) +} + +func setClockSequence(seq int) { + if seq == -1 { + var b [2]byte + randomBits(b[:]) // clock sequence + seq = int(b[0])<<8 | int(b[1]) + } + oldSeq := clockSeq + clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant + if oldSeq != clockSeq { + lasttime = 0 + } +} + +// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in +// uuid. The time is only defined for version 1 and 2 UUIDs. +func (uuid UUID) Time() Time { + time := int64(binary.BigEndian.Uint32(uuid[0:4])) + time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32 + time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48 + return Time(time) +} + +// ClockSequence returns the clock sequence encoded in uuid. +// The clock sequence is only well defined for version 1 and 2 UUIDs. +func (uuid UUID) ClockSequence() int { + return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff +} diff --git a/vendor/github.com/google/uuid/util.go b/vendor/github.com/google/uuid/util.go new file mode 100644 index 0000000000..5ea6c73780 --- /dev/null +++ b/vendor/github.com/google/uuid/util.go @@ -0,0 +1,43 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "io" +) + +// randomBits completely fills slice b with random data. +func randomBits(b []byte) { + if _, err := io.ReadFull(rander, b); err != nil { + panic(err.Error()) // rand should never fail + } +} + +// xvalues returns the value of a byte as a hexadecimal digit or 255. +var xvalues = [256]byte{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +} + +// xtob converts hex characters x1 and x2 into a byte. +func xtob(x1, x2 byte) (byte, bool) { + b1 := xvalues[x1] + b2 := xvalues[x2] + return (b1 << 4) | b2, b1 != 255 && b2 != 255 +} diff --git a/vendor/github.com/google/uuid/uuid.go b/vendor/github.com/google/uuid/uuid.go new file mode 100644 index 0000000000..524404cc52 --- /dev/null +++ b/vendor/github.com/google/uuid/uuid.go @@ -0,0 +1,245 @@ +// Copyright 2018 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "errors" + "fmt" + "io" + "strings" +) + +// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC +// 4122. +type UUID [16]byte + +// A Version represents a UUID's version. +type Version byte + +// A Variant represents a UUID's variant. +type Variant byte + +// Constants returned by Variant. +const ( + Invalid = Variant(iota) // Invalid UUID + RFC4122 // The variant specified in RFC4122 + Reserved // Reserved, NCS backward compatibility. + Microsoft // Reserved, Microsoft Corporation backward compatibility. + Future // Reserved for future definition. +) + +var rander = rand.Reader // random function + +// Parse decodes s into a UUID or returns an error. Both the standard UUID +// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the +// Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex +// encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. +func Parse(s string) (UUID, error) { + var uuid UUID + switch len(s) { + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + case 36: + + // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + case 36 + 9: + if strings.ToLower(s[:9]) != "urn:uuid:" { + return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9]) + } + s = s[9:] + + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + case 36 + 2: + s = s[1:] + + // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + case 32: + var ok bool + for i := range uuid { + uuid[i], ok = xtob(s[i*2], s[i*2+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + } + return uuid, nil + default: + return uuid, fmt.Errorf("invalid UUID length: %d", len(s)) + } + // s is now at least 36 bytes long + // it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { + return uuid, errors.New("invalid UUID format") + } + for i, x := range [16]int{ + 0, 2, 4, 6, + 9, 11, + 14, 16, + 19, 21, + 24, 26, 28, 30, 32, 34} { + v, ok := xtob(s[x], s[x+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + uuid[i] = v + } + return uuid, nil +} + +// ParseBytes is like Parse, except it parses a byte slice instead of a string. +func ParseBytes(b []byte) (UUID, error) { + var uuid UUID + switch len(b) { + case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) { + return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9]) + } + b = b[9:] + case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + b = b[1:] + case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + var ok bool + for i := 0; i < 32; i += 2 { + uuid[i/2], ok = xtob(b[i], b[i+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + } + return uuid, nil + default: + return uuid, fmt.Errorf("invalid UUID length: %d", len(b)) + } + // s is now at least 36 bytes long + // it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' { + return uuid, errors.New("invalid UUID format") + } + for i, x := range [16]int{ + 0, 2, 4, 6, + 9, 11, + 14, 16, + 19, 21, + 24, 26, 28, 30, 32, 34} { + v, ok := xtob(b[x], b[x+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + uuid[i] = v + } + return uuid, nil +} + +// MustParse is like Parse but panics if the string cannot be parsed. +// It simplifies safe initialization of global variables holding compiled UUIDs. +func MustParse(s string) UUID { + uuid, err := Parse(s) + if err != nil { + panic(`uuid: Parse(` + s + `): ` + err.Error()) + } + return uuid +} + +// FromBytes creates a new UUID from a byte slice. Returns an error if the slice +// does not have a length of 16. The bytes are copied from the slice. +func FromBytes(b []byte) (uuid UUID, err error) { + err = uuid.UnmarshalBinary(b) + return uuid, err +} + +// Must returns uuid if err is nil and panics otherwise. +func Must(uuid UUID, err error) UUID { + if err != nil { + panic(err) + } + return uuid +} + +// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +// , or "" if uuid is invalid. +func (uuid UUID) String() string { + var buf [36]byte + encodeHex(buf[:], uuid) + return string(buf[:]) +} + +// URN returns the RFC 2141 URN form of uuid, +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid. +func (uuid UUID) URN() string { + var buf [36 + 9]byte + copy(buf[:], "urn:uuid:") + encodeHex(buf[9:], uuid) + return string(buf[:]) +} + +func encodeHex(dst []byte, uuid UUID) { + hex.Encode(dst, uuid[:4]) + dst[8] = '-' + hex.Encode(dst[9:13], uuid[4:6]) + dst[13] = '-' + hex.Encode(dst[14:18], uuid[6:8]) + dst[18] = '-' + hex.Encode(dst[19:23], uuid[8:10]) + dst[23] = '-' + hex.Encode(dst[24:], uuid[10:]) +} + +// Variant returns the variant encoded in uuid. +func (uuid UUID) Variant() Variant { + switch { + case (uuid[8] & 0xc0) == 0x80: + return RFC4122 + case (uuid[8] & 0xe0) == 0xc0: + return Microsoft + case (uuid[8] & 0xe0) == 0xe0: + return Future + default: + return Reserved + } +} + +// Version returns the version of uuid. +func (uuid UUID) Version() Version { + return Version(uuid[6] >> 4) +} + +func (v Version) String() string { + if v > 15 { + return fmt.Sprintf("BAD_VERSION_%d", v) + } + return fmt.Sprintf("VERSION_%d", v) +} + +func (v Variant) String() string { + switch v { + case RFC4122: + return "RFC4122" + case Reserved: + return "Reserved" + case Microsoft: + return "Microsoft" + case Future: + return "Future" + case Invalid: + return "Invalid" + } + return fmt.Sprintf("BadVariant%d", int(v)) +} + +// SetRand sets the random number generator to r, which implements io.Reader. +// If r.Read returns an error when the package requests random data then +// a panic will be issued. +// +// Calling SetRand with nil sets the random number generator to the default +// generator. +func SetRand(r io.Reader) { + if r == nil { + rander = rand.Reader + return + } + rander = r +} diff --git a/vendor/github.com/google/uuid/uuid_test.go b/vendor/github.com/google/uuid/uuid_test.go new file mode 100644 index 0000000000..e7876f151a --- /dev/null +++ b/vendor/github.com/google/uuid/uuid_test.go @@ -0,0 +1,559 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "fmt" + "os" + "runtime" + "strings" + "testing" + "time" + "unsafe" +) + +type test struct { + in string + version Version + variant Variant + isuuid bool +} + +var tests = []test{ + {"f47ac10b-58cc-0372-8567-0e02b2c3d479", 0, RFC4122, true}, + {"f47ac10b-58cc-1372-8567-0e02b2c3d479", 1, RFC4122, true}, + {"f47ac10b-58cc-2372-8567-0e02b2c3d479", 2, RFC4122, true}, + {"f47ac10b-58cc-3372-8567-0e02b2c3d479", 3, RFC4122, true}, + {"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-5372-8567-0e02b2c3d479", 5, RFC4122, true}, + {"f47ac10b-58cc-6372-8567-0e02b2c3d479", 6, RFC4122, true}, + {"f47ac10b-58cc-7372-8567-0e02b2c3d479", 7, RFC4122, true}, + {"f47ac10b-58cc-8372-8567-0e02b2c3d479", 8, RFC4122, true}, + {"f47ac10b-58cc-9372-8567-0e02b2c3d479", 9, RFC4122, true}, + {"f47ac10b-58cc-a372-8567-0e02b2c3d479", 10, RFC4122, true}, + {"f47ac10b-58cc-b372-8567-0e02b2c3d479", 11, RFC4122, true}, + {"f47ac10b-58cc-c372-8567-0e02b2c3d479", 12, RFC4122, true}, + {"f47ac10b-58cc-d372-8567-0e02b2c3d479", 13, RFC4122, true}, + {"f47ac10b-58cc-e372-8567-0e02b2c3d479", 14, RFC4122, true}, + {"f47ac10b-58cc-f372-8567-0e02b2c3d479", 15, RFC4122, true}, + + {"urn:uuid:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true}, + {"URN:UUID:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-1567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-2567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-3567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-4567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-5567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-6567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-7567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-9567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-a567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-b567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-c567-0e02b2c3d479", 4, Microsoft, true}, + {"f47ac10b-58cc-4372-d567-0e02b2c3d479", 4, Microsoft, true}, + {"f47ac10b-58cc-4372-e567-0e02b2c3d479", 4, Future, true}, + {"f47ac10b-58cc-4372-f567-0e02b2c3d479", 4, Future, true}, + + + {"f47ac10b158cc-5372-a567-0e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc25372-a567-0e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc-53723a567-0e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc-5372-a56740e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc-5372-a567-0e02-2c3d479", 0, Invalid, false}, + {"g47ac10b-58cc-4372-a567-0e02b2c3d479", 0, Invalid, false}, + + + {"{f47ac10b-58cc-0372-8567-0e02b2c3d479}", 0, RFC4122, true}, + {"{f47ac10b-58cc-0372-8567-0e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc-0372-8567-0e02b2c3d479}", 0, Invalid, false}, + + {"f47ac10b58cc037285670e02b2c3d479", 0, RFC4122, true}, + {"f47ac10b58cc037285670e02b2c3d4790", 0, Invalid, false}, + {"f47ac10b58cc037285670e02b2c3d47", 0, Invalid, false}, +} + +var constants = []struct { + c interface{} + name string +}{ + {Person, "Person"}, + {Group, "Group"}, + {Org, "Org"}, + {Invalid, "Invalid"}, + {RFC4122, "RFC4122"}, + {Reserved, "Reserved"}, + {Microsoft, "Microsoft"}, + {Future, "Future"}, + {Domain(17), "Domain17"}, + {Variant(42), "BadVariant42"}, +} + +func testTest(t *testing.T, in string, tt test) { + uuid, err := Parse(in) + if ok := (err == nil); ok != tt.isuuid { + t.Errorf("Parse(%s) got %v expected %v\b", in, ok, tt.isuuid) + } + if err != nil { + return + } + + if v := uuid.Variant(); v != tt.variant { + t.Errorf("Variant(%s) got %d expected %d\b", in, v, tt.variant) + } + if v := uuid.Version(); v != tt.version { + t.Errorf("Version(%s) got %d expected %d\b", in, v, tt.version) + } +} + +func testBytes(t *testing.T, in []byte, tt test) { + uuid, err := ParseBytes(in) + if ok := (err == nil); ok != tt.isuuid { + t.Errorf("ParseBytes(%s) got %v expected %v\b", in, ok, tt.isuuid) + } + if err != nil { + return + } + suuid, _ := Parse(string(in)) + if uuid != suuid { + t.Errorf("ParseBytes(%s) got %v expected %v\b", in, uuid, suuid) + } +} + +func TestUUID(t *testing.T) { + for _, tt := range tests { + testTest(t, tt.in, tt) + testTest(t, strings.ToUpper(tt.in), tt) + testBytes(t, []byte(tt.in), tt) + } +} + +func TestFromBytes(t *testing.T) { + b := []byte{ + 0x7d, 0x44, 0x48, 0x40, + 0x9d, 0xc0, + 0x11, 0xd1, + 0xb2, 0x45, + 0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2, + } + uuid, err := FromBytes(b) + if err != nil { + t.Fatalf("%s", err) + } + for i := 0; i < len(uuid); i++ { + if b[i] != uuid[i] { + t.Fatalf("FromBytes() got %v expected %v\b", uuid[:], b) + } + } +} + +func TestConstants(t *testing.T) { + for x, tt := range constants { + v, ok := tt.c.(fmt.Stringer) + if !ok { + t.Errorf("%x: %v: not a stringer", x, v) + } else if s := v.String(); s != tt.name { + v, _ := tt.c.(int) + t.Errorf("%x: Constant %T:%d gives %q, expected %q", x, tt.c, v, s, tt.name) + } + } +} + +func TestRandomUUID(t *testing.T) { + m := make(map[string]bool) + for x := 1; x < 32; x++ { + uuid := New() + s := uuid.String() + if m[s] { + t.Errorf("NewRandom returned duplicated UUID %s", s) + } + m[s] = true + if v := uuid.Version(); v != 4 { + t.Errorf("Random UUID of version %s", v) + } + if uuid.Variant() != RFC4122 { + t.Errorf("Random UUID is variant %d", uuid.Variant()) + } + } +} + +func TestNew(t *testing.T) { + m := make(map[UUID]bool) + for x := 1; x < 32; x++ { + s := New() + if m[s] { + t.Errorf("New returned duplicated UUID %s", s) + } + m[s] = true + uuid, err := Parse(s.String()) + if err != nil { + t.Errorf("New.String() returned %q which does not decode", s) + continue + } + if v := uuid.Version(); v != 4 { + t.Errorf("Random UUID of version %s", v) + } + if uuid.Variant() != RFC4122 { + t.Errorf("Random UUID is variant %d", uuid.Variant()) + } + } +} + +func TestClockSeq(t *testing.T) { + // Fake time.Now for this test to return a monotonically advancing time; restore it at end. + defer func(orig func() time.Time) { timeNow = orig }(timeNow) + monTime := time.Now() + timeNow = func() time.Time { + monTime = monTime.Add(1 * time.Second) + return monTime + } + + SetClockSequence(-1) + uuid1, err := NewUUID() + if err != nil { + t.Fatalf("could not create UUID: %v", err) + } + uuid2, err := NewUUID() + if err != nil { + t.Fatalf("could not create UUID: %v", err) + } + + if s1, s2 := uuid1.ClockSequence(), uuid2.ClockSequence(); s1 != s2 { + t.Errorf("clock sequence %d != %d", s1, s2) + } + + SetClockSequence(-1) + uuid2, err = NewUUID() + if err != nil { + t.Fatalf("could not create UUID: %v", err) + } + + // Just on the very off chance we generated the same sequence + // two times we try again. + if uuid1.ClockSequence() == uuid2.ClockSequence() { + SetClockSequence(-1) + uuid2, err = NewUUID() + if err != nil { + t.Fatalf("could not create UUID: %v", err) + } + } + if s1, s2 := uuid1.ClockSequence(), uuid2.ClockSequence(); s1 == s2 { + t.Errorf("Duplicate clock sequence %d", s1) + } + + SetClockSequence(0x1234) + uuid1, err = NewUUID() + if err != nil { + t.Fatalf("could not create UUID: %v", err) + } + if seq := uuid1.ClockSequence(); seq != 0x1234 { + t.Errorf("%s: expected seq 0x1234 got 0x%04x", uuid1, seq) + } +} + +func TestCoding(t *testing.T) { + text := "7d444840-9dc0-11d1-b245-5ffdce74fad2" + urn := "urn:uuid:7d444840-9dc0-11d1-b245-5ffdce74fad2" + data := UUID{ + 0x7d, 0x44, 0x48, 0x40, + 0x9d, 0xc0, + 0x11, 0xd1, + 0xb2, 0x45, + 0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2, + } + if v := data.String(); v != text { + t.Errorf("%x: encoded to %s, expected %s", data, v, text) + } + if v := data.URN(); v != urn { + t.Errorf("%x: urn is %s, expected %s", data, v, urn) + } + + uuid, err := Parse(text) + if err != nil { + t.Errorf("Parse returned unexpected error %v", err) + } + if data != uuid { + t.Errorf("%s: decoded to %s, expected %s", text, uuid, data) + } +} + +func TestVersion1(t *testing.T) { + uuid1, err := NewUUID() + if err != nil { + t.Fatalf("could not create UUID: %v", err) + } + uuid2, err := NewUUID() + if err != nil { + t.Fatalf("could not create UUID: %v", err) + } + + if uuid1 == uuid2 { + t.Errorf("%s:duplicate uuid", uuid1) + } + if v := uuid1.Version(); v != 1 { + t.Errorf("%s: version %s expected 1", uuid1, v) + } + if v := uuid2.Version(); v != 1 { + t.Errorf("%s: version %s expected 1", uuid2, v) + } + n1 := uuid1.NodeID() + n2 := uuid2.NodeID() + if !bytes.Equal(n1, n2) { + t.Errorf("Different nodes %x != %x", n1, n2) + } + t1 := uuid1.Time() + t2 := uuid2.Time() + q1 := uuid1.ClockSequence() + q2 := uuid2.ClockSequence() + + switch { + case t1 == t2 && q1 == q2: + t.Error("time stopped") + case t1 > t2 && q1 == q2: + t.Error("time reversed") + case t1 < t2 && q1 != q2: + t.Error("clock sequence changed unexpectedly") + } +} + +func TestNode(t *testing.T) { + // This test is mostly to make sure we don't leave nodeMu locked. + ifname = "" + if ni := NodeInterface(); ni != "" { + t.Errorf("NodeInterface got %q, want %q", ni, "") + } + if SetNodeInterface("xyzzy") { + t.Error("SetNodeInterface succeeded on a bad interface name") + } + if !SetNodeInterface("") { + t.Error("SetNodeInterface failed") + } + if runtime.GOARCH != "js" { + if ni := NodeInterface(); ni == "" { + t.Error("NodeInterface returned an empty string") + } + } + + ni := NodeID() + if len(ni) != 6 { + t.Errorf("ni got %d bytes, want 6", len(ni)) + } + hasData := false + for _, b := range ni { + if b != 0 { + hasData = true + } + } + if !hasData { + t.Error("nodeid is all zeros") + } + + id := []byte{1, 2, 3, 4, 5, 6, 7, 8} + SetNodeID(id) + ni = NodeID() + if !bytes.Equal(ni, id[:6]) { + t.Errorf("got nodeid %v, want %v", ni, id[:6]) + } + + if ni := NodeInterface(); ni != "user" { + t.Errorf("got interface %q, want %q", ni, "user") + } +} + +func TestNodeAndTime(t *testing.T) { + // Time is February 5, 1998 12:30:23.136364800 AM GMT + + uuid, err := Parse("7d444840-9dc0-11d1-b245-5ffdce74fad2") + if err != nil { + t.Fatalf("Parser returned unexpected error %v", err) + } + node := []byte{0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2} + + ts := uuid.Time() + c := time.Unix(ts.UnixTime()) + want := time.Date(1998, 2, 5, 0, 30, 23, 136364800, time.UTC) + if !c.Equal(want) { + t.Errorf("Got time %v, want %v", c, want) + } + if !bytes.Equal(node, uuid.NodeID()) { + t.Errorf("Expected node %v got %v", node, uuid.NodeID()) + } +} + +func TestMD5(t *testing.T) { + uuid := NewMD5(NameSpaceDNS, []byte("python.org")).String() + want := "6fa459ea-ee8a-3ca4-894e-db77e160355e" + if uuid != want { + t.Errorf("MD5: got %q expected %q", uuid, want) + } +} + +func TestSHA1(t *testing.T) { + uuid := NewSHA1(NameSpaceDNS, []byte("python.org")).String() + want := "886313e1-3b8a-5372-9b90-0c9aee199e5d" + if uuid != want { + t.Errorf("SHA1: got %q expected %q", uuid, want) + } +} + +func TestNodeID(t *testing.T) { + nid := []byte{1, 2, 3, 4, 5, 6} + SetNodeInterface("") + s := NodeInterface() + if runtime.GOARCH != "js" { + if s == "" || s == "user" { + t.Errorf("NodeInterface %q after SetInterface", s) + } + } + node1 := NodeID() + if node1 == nil { + t.Error("NodeID nil after SetNodeInterface", s) + } + SetNodeID(nid) + s = NodeInterface() + if s != "user" { + t.Errorf("Expected NodeInterface %q got %q", "user", s) + } + node2 := NodeID() + if node2 == nil { + t.Error("NodeID nil after SetNodeID", s) + } + if bytes.Equal(node1, node2) { + t.Error("NodeID not changed after SetNodeID", s) + } else if !bytes.Equal(nid, node2) { + t.Errorf("NodeID is %x, expected %x", node2, nid) + } +} + +func testDCE(t *testing.T, name string, uuid UUID, err error, domain Domain, id uint32) { + if err != nil { + t.Errorf("%s failed: %v", name, err) + return + } + if v := uuid.Version(); v != 2 { + t.Errorf("%s: %s: expected version 2, got %s", name, uuid, v) + return + } + if v := uuid.Domain(); v != domain { + t.Errorf("%s: %s: expected domain %d, got %d", name, uuid, domain, v) + } + if v := uuid.ID(); v != id { + t.Errorf("%s: %s: expected id %d, got %d", name, uuid, id, v) + } +} + +func TestDCE(t *testing.T) { + uuid, err := NewDCESecurity(42, 12345678) + testDCE(t, "NewDCESecurity", uuid, err, 42, 12345678) + uuid, err = NewDCEPerson() + testDCE(t, "NewDCEPerson", uuid, err, Person, uint32(os.Getuid())) + uuid, err = NewDCEGroup() + testDCE(t, "NewDCEGroup", uuid, err, Group, uint32(os.Getgid())) +} + +type badRand struct{} + +func (r badRand) Read(buf []byte) (int, error) { + for i := range buf { + buf[i] = byte(i) + } + return len(buf), nil +} + +func TestBadRand(t *testing.T) { + SetRand(badRand{}) + uuid1 := New() + uuid2 := New() + if uuid1 != uuid2 { + t.Errorf("expected duplicates, got %q and %q", uuid1, uuid2) + } + SetRand(nil) + uuid1 = New() + uuid2 = New() + if uuid1 == uuid2 { + t.Errorf("unexpected duplicates, got %q", uuid1) + } +} + +var asString = "f47ac10b-58cc-0372-8567-0e02b2c3d479" +var asBytes = []byte(asString) + +func BenchmarkParse(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := Parse(asString) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkParseBytes(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := ParseBytes(asBytes) + if err != nil { + b.Fatal(err) + } + } +} + +// parseBytesUnsafe is to benchmark using unsafe. +func parseBytesUnsafe(b []byte) (UUID, error) { + return Parse(*(*string)(unsafe.Pointer(&b))) +} + +func BenchmarkParseBytesUnsafe(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := parseBytesUnsafe(asBytes) + if err != nil { + b.Fatal(err) + } + } +} + +// parseBytesCopy is to benchmark not using unsafe. +func parseBytesCopy(b []byte) (UUID, error) { + return Parse(string(b)) +} + +func BenchmarkParseBytesCopy(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := parseBytesCopy(asBytes) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkNew(b *testing.B) { + for i := 0; i < b.N; i++ { + New() + } +} + +func BenchmarkUUID_String(b *testing.B) { + uuid, err := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if err != nil { + b.Fatal(err) + } + for i := 0; i < b.N; i++ { + if uuid.String() == "" { + b.Fatal("invalid uuid") + } + } +} + +func BenchmarkUUID_URN(b *testing.B) { + uuid, err := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if err != nil { + b.Fatal(err) + } + for i := 0; i < b.N; i++ { + if uuid.URN() == "" { + b.Fatal("invalid uuid") + } + } +} diff --git a/vendor/github.com/google/uuid/version1.go b/vendor/github.com/google/uuid/version1.go new file mode 100644 index 0000000000..199a1ac654 --- /dev/null +++ b/vendor/github.com/google/uuid/version1.go @@ -0,0 +1,44 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" +) + +// NewUUID returns a Version 1 UUID based on the current NodeID and clock +// sequence, and the current time. If the NodeID has not been set by SetNodeID +// or SetNodeInterface then it will be set automatically. If the NodeID cannot +// be set NewUUID returns nil. If clock sequence has not been set by +// SetClockSequence then it will be set automatically. If GetTime fails to +// return the current NewUUID returns nil and an error. +// +// In most cases, New should be used. +func NewUUID() (UUID, error) { + nodeMu.Lock() + if nodeID == zeroID { + setNodeInterface("") + } + nodeMu.Unlock() + + var uuid UUID + now, seq, err := GetTime() + if err != nil { + return uuid, err + } + + timeLow := uint32(now & 0xffffffff) + timeMid := uint16((now >> 32) & 0xffff) + timeHi := uint16((now >> 48) & 0x0fff) + timeHi |= 0x1000 // Version 1 + + binary.BigEndian.PutUint32(uuid[0:], timeLow) + binary.BigEndian.PutUint16(uuid[4:], timeMid) + binary.BigEndian.PutUint16(uuid[6:], timeHi) + binary.BigEndian.PutUint16(uuid[8:], seq) + copy(uuid[10:], nodeID[:]) + + return uuid, nil +} diff --git a/vendor/github.com/google/uuid/version4.go b/vendor/github.com/google/uuid/version4.go new file mode 100644 index 0000000000..84af91c9f5 --- /dev/null +++ b/vendor/github.com/google/uuid/version4.go @@ -0,0 +1,38 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import "io" + +// New creates a new random UUID or panics. New is equivalent to +// the expression +// +// uuid.Must(uuid.NewRandom()) +func New() UUID { + return Must(NewRandom()) +} + +// NewRandom returns a Random (Version 4) UUID. +// +// The strength of the UUIDs is based on the strength of the crypto/rand +// package. +// +// A note about uniqueness derived from the UUID Wikipedia entry: +// +// Randomly generated UUIDs have 122 random bits. One's annual risk of being +// hit by a meteorite is estimated to be one chance in 17 billion, that +// means the probability is about 0.00000000006 (6 × 10−11), +// equivalent to the odds of creating a few tens of trillions of UUIDs in a +// year and having one duplicate. +func NewRandom() (UUID, error) { + var uuid UUID + _, err := io.ReadFull(rander, uuid[:]) + if err != nil { + return Nil, err + } + uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 + uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 + return uuid, nil +} diff --git a/vendor/github.com/pborman/uuid/CONTRIBUTING.md b/vendor/github.com/pborman/uuid/CONTRIBUTING.md new file mode 100644 index 0000000000..04fdf09f13 --- /dev/null +++ b/vendor/github.com/pborman/uuid/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# How to contribute + +We definitely welcome patches and contribution to this project! + +### Legal requirements + +In order to protect both you and ourselves, you will need to sign the +[Contributor License Agreement](https://cla.developers.google.com/clas). + +You may have already signed it for other Google projects. diff --git a/vendor/github.com/pborman/uuid/CONTRIBUTORS b/vendor/github.com/pborman/uuid/CONTRIBUTORS new file mode 100644 index 0000000000..b382a04eda --- /dev/null +++ b/vendor/github.com/pborman/uuid/CONTRIBUTORS @@ -0,0 +1 @@ +Paul Borman diff --git a/vendor/github.com/pborman/uuid/LICENSE b/vendor/github.com/pborman/uuid/LICENSE new file mode 100644 index 0000000000..5dc68268d9 --- /dev/null +++ b/vendor/github.com/pborman/uuid/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009,2014 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pborman/uuid/README.md b/vendor/github.com/pborman/uuid/README.md new file mode 100644 index 0000000000..810ad40dc9 --- /dev/null +++ b/vendor/github.com/pborman/uuid/README.md @@ -0,0 +1,15 @@ +This project was automatically exported from code.google.com/p/go-uuid + +# uuid ![build status](https://travis-ci.org/pborman/uuid.svg?branch=master) +The uuid package generates and inspects UUIDs based on [RFC 4122](http://tools.ietf.org/html/rfc4122) and DCE 1.1: Authentication and Security Services. + +This package now leverages the github.com/google/uuid package (which is based off an earlier version of this package). + +###### Install +`go get github.com/pborman/uuid` + +###### Documentation +[![GoDoc](https://godoc.org/github.com/pborman/uuid?status.svg)](http://godoc.org/github.com/pborman/uuid) + +Full `go doc` style documentation for the package can be viewed online without installing this package by using the GoDoc site here: +http://godoc.org/github.com/pborman/uuid diff --git a/vendor/github.com/pborman/uuid/dce.go b/vendor/github.com/pborman/uuid/dce.go new file mode 100644 index 0000000000..50a0f2d099 --- /dev/null +++ b/vendor/github.com/pborman/uuid/dce.go @@ -0,0 +1,84 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "fmt" + "os" +) + +// A Domain represents a Version 2 domain +type Domain byte + +// Domain constants for DCE Security (Version 2) UUIDs. +const ( + Person = Domain(0) + Group = Domain(1) + Org = Domain(2) +) + +// NewDCESecurity returns a DCE Security (Version 2) UUID. +// +// The domain should be one of Person, Group or Org. +// On a POSIX system the id should be the users UID for the Person +// domain and the users GID for the Group. The meaning of id for +// the domain Org or on non-POSIX systems is site defined. +// +// For a given domain/id pair the same token may be returned for up to +// 7 minutes and 10 seconds. +func NewDCESecurity(domain Domain, id uint32) UUID { + uuid := NewUUID() + if uuid != nil { + uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2 + uuid[9] = byte(domain) + binary.BigEndian.PutUint32(uuid[0:], id) + } + return uuid +} + +// NewDCEPerson returns a DCE Security (Version 2) UUID in the person +// domain with the id returned by os.Getuid. +// +// NewDCEPerson(Person, uint32(os.Getuid())) +func NewDCEPerson() UUID { + return NewDCESecurity(Person, uint32(os.Getuid())) +} + +// NewDCEGroup returns a DCE Security (Version 2) UUID in the group +// domain with the id returned by os.Getgid. +// +// NewDCEGroup(Group, uint32(os.Getgid())) +func NewDCEGroup() UUID { + return NewDCESecurity(Group, uint32(os.Getgid())) +} + +// Domain returns the domain for a Version 2 UUID or false. +func (uuid UUID) Domain() (Domain, bool) { + if v, _ := uuid.Version(); v != 2 { + return 0, false + } + return Domain(uuid[9]), true +} + +// Id returns the id for a Version 2 UUID or false. +func (uuid UUID) Id() (uint32, bool) { + if v, _ := uuid.Version(); v != 2 { + return 0, false + } + return binary.BigEndian.Uint32(uuid[0:4]), true +} + +func (d Domain) String() string { + switch d { + case Person: + return "Person" + case Group: + return "Group" + case Org: + return "Org" + } + return fmt.Sprintf("Domain%d", int(d)) +} diff --git a/vendor/github.com/pborman/uuid/doc.go b/vendor/github.com/pborman/uuid/doc.go new file mode 100644 index 0000000000..727d761674 --- /dev/null +++ b/vendor/github.com/pborman/uuid/doc.go @@ -0,0 +1,13 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The uuid package generates and inspects UUIDs. +// +// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security +// Services. +// +// This package is a partial wrapper around the github.com/google/uuid package. +// This package represents a UUID as []byte while github.com/google/uuid +// represents a UUID as [16]byte. +package uuid diff --git a/vendor/github.com/pborman/uuid/hash.go b/vendor/github.com/pborman/uuid/hash.go new file mode 100644 index 0000000000..a0420c1ef3 --- /dev/null +++ b/vendor/github.com/pborman/uuid/hash.go @@ -0,0 +1,53 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "crypto/md5" + "crypto/sha1" + "hash" +) + +// Well known Name Space IDs and UUIDs +var ( + NameSpace_DNS = Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + NameSpace_URL = Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8") + NameSpace_OID = Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8") + NameSpace_X500 = Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8") + NIL = Parse("00000000-0000-0000-0000-000000000000") +) + +// NewHash returns a new UUID derived from the hash of space concatenated with +// data generated by h. The hash should be at least 16 byte in length. The +// first 16 bytes of the hash are used to form the UUID. The version of the +// UUID will be the lower 4 bits of version. NewHash is used to implement +// NewMD5 and NewSHA1. +func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID { + h.Reset() + h.Write(space) + h.Write([]byte(data)) + s := h.Sum(nil) + uuid := make([]byte, 16) + copy(uuid, s) + uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4) + uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant + return uuid +} + +// NewMD5 returns a new MD5 (Version 3) UUID based on the +// supplied name space and data. +// +// NewHash(md5.New(), space, data, 3) +func NewMD5(space UUID, data []byte) UUID { + return NewHash(md5.New(), space, data, 3) +} + +// NewSHA1 returns a new SHA1 (Version 5) UUID based on the +// supplied name space and data. +// +// NewHash(sha1.New(), space, data, 5) +func NewSHA1(space UUID, data []byte) UUID { + return NewHash(sha1.New(), space, data, 5) +} diff --git a/vendor/github.com/pborman/uuid/marshal.go b/vendor/github.com/pborman/uuid/marshal.go new file mode 100644 index 0000000000..35b89352ad --- /dev/null +++ b/vendor/github.com/pborman/uuid/marshal.go @@ -0,0 +1,85 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "errors" + "fmt" + + guuid "github.com/google/uuid" +) + +// MarshalText implements encoding.TextMarshaler. +func (u UUID) MarshalText() ([]byte, error) { + if len(u) != 16 { + return nil, nil + } + var js [36]byte + encodeHex(js[:], u) + return js[:], nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (u *UUID) UnmarshalText(data []byte) error { + if len(data) == 0 { + return nil + } + id := Parse(string(data)) + if id == nil { + return errors.New("invalid UUID") + } + *u = id + return nil +} + +// MarshalBinary implements encoding.BinaryMarshaler. +func (u UUID) MarshalBinary() ([]byte, error) { + return u[:], nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +func (u *UUID) UnmarshalBinary(data []byte) error { + if len(data) == 0 { + return nil + } + if len(data) != 16 { + return fmt.Errorf("invalid UUID (got %d bytes)", len(data)) + } + var id [16]byte + copy(id[:], data) + *u = id[:] + return nil +} + +// MarshalText implements encoding.TextMarshaler. +func (u Array) MarshalText() ([]byte, error) { + var js [36]byte + encodeHex(js[:], u[:]) + return js[:], nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (u *Array) UnmarshalText(data []byte) error { + id, err := guuid.ParseBytes(data) + if err != nil { + return err + } + *u = Array(id) + return nil +} + +// MarshalBinary implements encoding.BinaryMarshaler. +func (u Array) MarshalBinary() ([]byte, error) { + return u[:], nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +func (u *Array) UnmarshalBinary(data []byte) error { + if len(data) != 16 { + return fmt.Errorf("invalid UUID (got %d bytes)", len(data)) + } + copy(u[:], data) + return nil +} diff --git a/vendor/github.com/pborman/uuid/marshal_test.go b/vendor/github.com/pborman/uuid/marshal_test.go new file mode 100644 index 0000000000..4e85b6bab9 --- /dev/null +++ b/vendor/github.com/pborman/uuid/marshal_test.go @@ -0,0 +1,124 @@ +// Copyright 2014 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "encoding/json" + "reflect" + "testing" +) + +var testUUID = Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") +var testArray = testUUID.Array() + +func TestJSON(t *testing.T) { + type S struct { + ID1 UUID + ID2 UUID + } + s1 := S{ID1: testUUID} + data, err := json.Marshal(&s1) + if err != nil { + t.Fatal(err) + } + var s2 S + if err := json.Unmarshal(data, &s2); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(&s1, &s2) { + t.Errorf("got %#v, want %#v", s2, s1) + } +} + +func TestJSONArray(t *testing.T) { + type S struct { + ID1 Array + ID2 Array + } + s1 := S{ID1: testArray} + data, err := json.Marshal(&s1) + if err != nil { + t.Fatal(err) + } + var s2 S + if err := json.Unmarshal(data, &s2); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(&s1, &s2) { + t.Errorf("got %#v, want %#v", s2, s1) + } +} + +func TestMarshal(t *testing.T) { + data, err := testUUID.MarshalBinary() + if err != nil { + t.Fatalf("MarhsalBinary returned unexpected error %v", err) + } + if !bytes.Equal(data, testUUID) { + t.Fatalf("MarhsalBinary returns %x, want %x", data, testUUID) + } + var u UUID + u.UnmarshalBinary(data) + if !Equal(data, u) { + t.Fatalf("UnmarhsalBinary returns %v, want %v", u, testUUID) + } +} + +func TestMarshalArray(t *testing.T) { + data, err := testArray.MarshalBinary() + if err != nil { + t.Fatalf("MarhsalBinary returned unexpected error %v", err) + } + if !bytes.Equal(data, testUUID) { + t.Fatalf("MarhsalBinary returns %x, want %x", data, testUUID) + } + var a Array + a.UnmarshalBinary(data) + if a != testArray { + t.Fatalf("UnmarhsalBinary returns %v, want %v", a, testArray) + } +} + +func TestMarshalTextArray(t *testing.T) { + data, err := testArray.MarshalText() + if err != nil { + t.Fatalf("MarhsalText returned unexpected error %v", err) + } + var a Array + a.UnmarshalText(data) + if a != testArray { + t.Fatalf("UnmarhsalText returns %v, want %v", a, testArray) + } +} + +func BenchmarkUUID_MarshalJSON(b *testing.B) { + x := &struct { + UUID UUID `json:"uuid"` + }{} + x.UUID = Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if x.UUID == nil { + b.Fatal("invalid uuid") + } + for i := 0; i < b.N; i++ { + js, err := json.Marshal(x) + if err != nil { + b.Fatalf("marshal json: %#v (%v)", js, err) + } + } +} + +func BenchmarkUUID_UnmarshalJSON(b *testing.B) { + js := []byte(`{"uuid":"f47ac10b-58cc-0372-8567-0e02b2c3d479"}`) + var x *struct { + UUID UUID `json:"uuid"` + } + for i := 0; i < b.N; i++ { + err := json.Unmarshal(js, &x) + if err != nil { + b.Fatalf("marshal json: %#v (%v)", js, err) + } + } +} diff --git a/vendor/github.com/pborman/uuid/node.go b/vendor/github.com/pborman/uuid/node.go new file mode 100644 index 0000000000..e524e0101b --- /dev/null +++ b/vendor/github.com/pborman/uuid/node.go @@ -0,0 +1,50 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + guuid "github.com/google/uuid" +) + +// NodeInterface returns the name of the interface from which the NodeID was +// derived. The interface "user" is returned if the NodeID was set by +// SetNodeID. +func NodeInterface() string { + return guuid.NodeInterface() +} + +// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs. +// If name is "" then the first usable interface found will be used or a random +// Node ID will be generated. If a named interface cannot be found then false +// is returned. +// +// SetNodeInterface never fails when name is "". +func SetNodeInterface(name string) bool { + return guuid.SetNodeInterface(name) +} + +// NodeID returns a slice of a copy of the current Node ID, setting the Node ID +// if not already set. +func NodeID() []byte { + return guuid.NodeID() +} + +// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes +// of id are used. If id is less than 6 bytes then false is returned and the +// Node ID is not set. +func SetNodeID(id []byte) bool { + return guuid.SetNodeID(id) +} + +// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is +// not valid. The NodeID is only well defined for version 1 and 2 UUIDs. +func (uuid UUID) NodeID() []byte { + if len(uuid) != 16 { + return nil + } + node := make([]byte, 6) + copy(node, uuid[10:]) + return node +} diff --git a/vendor/github.com/pborman/uuid/seq_test.go b/vendor/github.com/pborman/uuid/seq_test.go new file mode 100644 index 0000000000..3b3d1430d5 --- /dev/null +++ b/vendor/github.com/pborman/uuid/seq_test.go @@ -0,0 +1,66 @@ +// Copyright 2014 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "flag" + "runtime" + "testing" + "time" +) + +// This test is only run when --regressions is passed on the go test line. +var regressions = flag.Bool("regressions", false, "run uuid regression tests") + +// TestClockSeqRace tests for a particular race condition of returning two +// identical Version1 UUIDs. The duration of 1 minute was chosen as the race +// condition, before being fixed, nearly always occured in under 30 seconds. +func TestClockSeqRace(t *testing.T) { + if !*regressions { + t.Skip("skipping regression tests") + } + duration := time.Minute + + done := make(chan struct{}) + defer close(done) + + ch := make(chan UUID, 10000) + ncpu := runtime.NumCPU() + switch ncpu { + case 0, 1: + // We can't run the test effectively. + t.Skip("skipping race test, only one CPU detected") + return + default: + runtime.GOMAXPROCS(ncpu) + } + for i := 0; i < ncpu; i++ { + go func() { + for { + select { + case <-done: + return + case ch <- NewUUID(): + } + } + }() + } + + uuids := make(map[string]bool) + cnt := 0 + start := time.Now() + for u := range ch { + s := u.String() + if uuids[s] { + t.Errorf("duplicate uuid after %d in %v: %s", cnt, time.Since(start), s) + return + } + uuids[s] = true + if time.Since(start) > duration { + return + } + cnt++ + } +} diff --git a/vendor/github.com/pborman/uuid/sql.go b/vendor/github.com/pborman/uuid/sql.go new file mode 100644 index 0000000000..929c3847e2 --- /dev/null +++ b/vendor/github.com/pborman/uuid/sql.go @@ -0,0 +1,68 @@ +// Copyright 2015 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "database/sql/driver" + "errors" + "fmt" +) + +// Scan implements sql.Scanner so UUIDs can be read from databases transparently +// Currently, database types that map to string and []byte are supported. Please +// consult database-specific driver documentation for matching types. +func (uuid *UUID) Scan(src interface{}) error { + switch src.(type) { + case string: + // if an empty UUID comes from a table, we return a null UUID + if src.(string) == "" { + return nil + } + + // see uuid.Parse for required string format + parsed := Parse(src.(string)) + + if parsed == nil { + return errors.New("Scan: invalid UUID format") + } + + *uuid = parsed + case []byte: + b := src.([]byte) + + // if an empty UUID comes from a table, we return a null UUID + if len(b) == 0 { + return nil + } + + // assumes a simple slice of bytes if 16 bytes + // otherwise attempts to parse + if len(b) == 16 { + parsed := make([]byte, 16) + copy(parsed, b) + *uuid = UUID(parsed) + } else { + u := Parse(string(b)) + + if u == nil { + return errors.New("Scan: invalid UUID format") + } + + *uuid = u + } + + default: + return fmt.Errorf("Scan: unable to scan type %T into UUID", src) + } + + return nil +} + +// Value implements sql.Valuer so that UUIDs can be written to databases +// transparently. Currently, UUIDs map to strings. Please consult +// database-specific driver documentation for matching types. +func (uuid UUID) Value() (driver.Value, error) { + return uuid.String(), nil +} diff --git a/vendor/github.com/pborman/uuid/sql_test.go b/vendor/github.com/pborman/uuid/sql_test.go new file mode 100644 index 0000000000..1030951569 --- /dev/null +++ b/vendor/github.com/pborman/uuid/sql_test.go @@ -0,0 +1,96 @@ +// Copyright 2015 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "strings" + "testing" +) + +func TestScan(t *testing.T) { + var stringTest string = "f47ac10b-58cc-0372-8567-0e02b2c3d479" + var byteTest []byte = Parse(stringTest) + var badTypeTest int = 6 + var invalidTest string = "f47ac10b-58cc-0372-8567-0e02b2c3d4" + + // sunny day tests + + var uuid UUID + err := (&uuid).Scan(stringTest) + if err != nil { + t.Fatal(err) + } + + err = (&uuid).Scan([]byte(stringTest)) + if err != nil { + t.Fatal(err) + } + + err = (&uuid).Scan(byteTest) + if err != nil { + t.Fatal(err) + } + + // bad type tests + + err = (&uuid).Scan(badTypeTest) + if err == nil { + t.Error("int correctly parsed and shouldn't have") + } + if !strings.Contains(err.Error(), "unable to scan type") { + t.Error("attempting to parse an int returned an incorrect error message") + } + + // invalid/incomplete uuids + + err = (&uuid).Scan(invalidTest) + if err == nil { + t.Error("invalid uuid was parsed without error") + } + if !strings.Contains(err.Error(), "invalid UUID") { + t.Error("attempting to parse an invalid UUID returned an incorrect error message") + } + + err = (&uuid).Scan(byteTest[:len(byteTest)-2]) + if err == nil { + t.Error("invalid byte uuid was parsed without error") + } + if !strings.Contains(err.Error(), "invalid UUID") { + t.Error("attempting to parse an invalid byte UUID returned an incorrect error message") + } + + // empty tests + + uuid = nil + var emptySlice []byte + err = (&uuid).Scan(emptySlice) + if err != nil { + t.Fatal(err) + } + + if uuid != nil { + t.Error("UUID was not nil after scanning empty byte slice") + } + + uuid = nil + var emptyString string + err = (&uuid).Scan(emptyString) + if err != nil { + t.Fatal(err) + } + + if uuid != nil { + t.Error("UUID was not nil after scanning empty string") + } +} + +func TestValue(t *testing.T) { + stringTest := "f47ac10b-58cc-0372-8567-0e02b2c3d479" + uuid := Parse(stringTest) + val, _ := uuid.Value() + if val != stringTest { + t.Error("Value() did not return expected string") + } +} diff --git a/vendor/github.com/pborman/uuid/time.go b/vendor/github.com/pborman/uuid/time.go new file mode 100644 index 0000000000..5c0960d872 --- /dev/null +++ b/vendor/github.com/pborman/uuid/time.go @@ -0,0 +1,57 @@ +// Copyright 2014 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + + guuid "github.com/google/uuid" +) + +// A Time represents a time as the number of 100's of nanoseconds since 15 Oct +// 1582. +type Time = guuid.Time + +// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and +// clock sequence as well as adjusting the clock sequence as needed. An error +// is returned if the current time cannot be determined. +func GetTime() (Time, uint16, error) { return guuid.GetTime() } + +// ClockSequence returns the current clock sequence, generating one if not +// already set. The clock sequence is only used for Version 1 UUIDs. +// +// The uuid package does not use global static storage for the clock sequence or +// the last time a UUID was generated. Unless SetClockSequence a new random +// clock sequence is generated the first time a clock sequence is requested by +// ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) sequence is generated +// for +func ClockSequence() int { return guuid.ClockSequence() } + +// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to +// -1 causes a new sequence to be generated. +func SetClockSequence(seq int) { guuid.SetClockSequence(seq) } + +// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in +// uuid. It returns false if uuid is not valid. The time is only well defined +// for version 1 and 2 UUIDs. +func (uuid UUID) Time() (Time, bool) { + if len(uuid) != 16 { + return 0, false + } + time := int64(binary.BigEndian.Uint32(uuid[0:4])) + time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32 + time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48 + return Time(time), true +} + +// ClockSequence returns the clock sequence encoded in uuid. It returns false +// if uuid is not valid. The clock sequence is only well defined for version 1 +// and 2 UUIDs. +func (uuid UUID) ClockSequence() (int, bool) { + if len(uuid) != 16 { + return 0, false + } + return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff, true +} diff --git a/vendor/github.com/pborman/uuid/util.go b/vendor/github.com/pborman/uuid/util.go new file mode 100644 index 0000000000..255b5e2485 --- /dev/null +++ b/vendor/github.com/pborman/uuid/util.go @@ -0,0 +1,32 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +// xvalues returns the value of a byte as a hexadecimal digit or 255. +var xvalues = [256]byte{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +} + +// xtob converts the the first two hex bytes of x into a byte. +func xtob(x string) (byte, bool) { + b1 := xvalues[x[0]] + b2 := xvalues[x[1]] + return (b1 << 4) | b2, b1 != 255 && b2 != 255 +} diff --git a/vendor/github.com/pborman/uuid/uuid.go b/vendor/github.com/pborman/uuid/uuid.go new file mode 100644 index 0000000000..a2429b0dd0 --- /dev/null +++ b/vendor/github.com/pborman/uuid/uuid.go @@ -0,0 +1,163 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "io" + + guuid "github.com/google/uuid" +) + +// Array is a pass-by-value UUID that can be used as an effecient key in a map. +type Array [16]byte + +// UUID converts uuid into a slice. +func (uuid Array) UUID() UUID { + return uuid[:] +} + +// String returns the string representation of uuid, +// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. +func (uuid Array) String() string { + return guuid.UUID(uuid).String() +} + +// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC +// 4122. +type UUID []byte + +// A Version represents a UUIDs version. +type Version = guuid.Version + +// A Variant represents a UUIDs variant. +type Variant = guuid.Variant + +// Constants returned by Variant. +const ( + Invalid = guuid.Invalid // Invalid UUID + RFC4122 = guuid.RFC4122 // The variant specified in RFC4122 + Reserved = guuid.Reserved // Reserved, NCS backward compatibility. + Microsoft = guuid.Microsoft // Reserved, Microsoft Corporation backward compatibility. + Future = guuid.Future // Reserved for future definition. +) + +var rander = rand.Reader // random function + +// New returns a new random (version 4) UUID as a string. It is a convenience +// function for NewRandom().String(). +func New() string { + return NewRandom().String() +} + +// Parse decodes s into a UUID or returns nil. Both the UUID form of +// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded. +func Parse(s string) UUID { + gu, err := guuid.Parse(s) + if err == nil { + return gu[:] + } + return nil +} + +// ParseBytes is like Parse, except it parses a byte slice instead of a string. +func ParseBytes(b []byte) (UUID, error) { + gu, err := guuid.ParseBytes(b) + if err == nil { + return gu[:], nil + } + return nil, err +} + +// Equal returns true if uuid1 and uuid2 are equal. +func Equal(uuid1, uuid2 UUID) bool { + return bytes.Equal(uuid1, uuid2) +} + +// Array returns an array representation of uuid that can be used as a map key. +// Array panics if uuid is not valid. +func (uuid UUID) Array() Array { + if len(uuid) != 16 { + panic("invalid uuid") + } + var a Array + copy(a[:], uuid) + return a +} + +// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +// , or "" if uuid is invalid. +func (uuid UUID) String() string { + if len(uuid) != 16 { + return "" + } + var buf [36]byte + encodeHex(buf[:], uuid) + return string(buf[:]) +} + +// URN returns the RFC 2141 URN form of uuid, +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid. +func (uuid UUID) URN() string { + if len(uuid) != 16 { + return "" + } + var buf [36 + 9]byte + copy(buf[:], "urn:uuid:") + encodeHex(buf[9:], uuid) + return string(buf[:]) +} + +func encodeHex(dst []byte, uuid UUID) { + hex.Encode(dst[:], uuid[:4]) + dst[8] = '-' + hex.Encode(dst[9:13], uuid[4:6]) + dst[13] = '-' + hex.Encode(dst[14:18], uuid[6:8]) + dst[18] = '-' + hex.Encode(dst[19:23], uuid[8:10]) + dst[23] = '-' + hex.Encode(dst[24:], uuid[10:]) +} + +// Variant returns the variant encoded in uuid. It returns Invalid if +// uuid is invalid. +func (uuid UUID) Variant() Variant { + if len(uuid) != 16 { + return Invalid + } + switch { + case (uuid[8] & 0xc0) == 0x80: + return RFC4122 + case (uuid[8] & 0xe0) == 0xc0: + return Microsoft + case (uuid[8] & 0xe0) == 0xe0: + return Future + default: + return Reserved + } +} + +// Version returns the version of uuid. It returns false if uuid is not +// valid. +func (uuid UUID) Version() (Version, bool) { + if len(uuid) != 16 { + return 0, false + } + return Version(uuid[6] >> 4), true +} + +// SetRand sets the random number generator to r, which implements io.Reader. +// If r.Read returns an error when the package requests random data then +// a panic will be issued. +// +// Calling SetRand with nil sets the random number generator to the default +// generator. +func SetRand(r io.Reader) { + guuid.SetRand(r) +} diff --git a/vendor/github.com/pborman/uuid/uuid_test.go b/vendor/github.com/pborman/uuid/uuid_test.go new file mode 100644 index 0000000000..7e9d144fba --- /dev/null +++ b/vendor/github.com/pborman/uuid/uuid_test.go @@ -0,0 +1,410 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +// Some of these tests can probably be removed as they are redundant with the +// tests in github.com/google/uuid. + +import ( + "bytes" + "fmt" + "os" + "strings" + "testing" +) + +type test struct { + in string + version Version + variant Variant + isuuid bool +} + +var tests = []test{ + {"f47ac10b-58cc-0372-8567-0e02b2c3d479", 0, RFC4122, true}, + {"f47ac10b-58cc-1372-8567-0e02b2c3d479", 1, RFC4122, true}, + {"f47ac10b-58cc-2372-8567-0e02b2c3d479", 2, RFC4122, true}, + {"f47ac10b-58cc-3372-8567-0e02b2c3d479", 3, RFC4122, true}, + {"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-5372-8567-0e02b2c3d479", 5, RFC4122, true}, + {"f47ac10b-58cc-6372-8567-0e02b2c3d479", 6, RFC4122, true}, + {"f47ac10b-58cc-7372-8567-0e02b2c3d479", 7, RFC4122, true}, + {"f47ac10b-58cc-8372-8567-0e02b2c3d479", 8, RFC4122, true}, + {"f47ac10b-58cc-9372-8567-0e02b2c3d479", 9, RFC4122, true}, + {"f47ac10b-58cc-a372-8567-0e02b2c3d479", 10, RFC4122, true}, + {"f47ac10b-58cc-b372-8567-0e02b2c3d479", 11, RFC4122, true}, + {"f47ac10b-58cc-c372-8567-0e02b2c3d479", 12, RFC4122, true}, + {"f47ac10b-58cc-d372-8567-0e02b2c3d479", 13, RFC4122, true}, + {"f47ac10b-58cc-e372-8567-0e02b2c3d479", 14, RFC4122, true}, + {"f47ac10b-58cc-f372-8567-0e02b2c3d479", 15, RFC4122, true}, + + {"urn:uuid:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true}, + {"URN:UUID:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-1567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-2567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-3567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-4567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-5567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-6567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-7567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-9567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-a567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-b567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-c567-0e02b2c3d479", 4, Microsoft, true}, + {"f47ac10b-58cc-4372-d567-0e02b2c3d479", 4, Microsoft, true}, + {"f47ac10b-58cc-4372-e567-0e02b2c3d479", 4, Future, true}, + {"f47ac10b-58cc-4372-f567-0e02b2c3d479", 4, Future, true}, + + {"f47ac10b158cc-5372-a567-0e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc25372-a567-0e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc-53723a567-0e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc-5372-a56740e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc-5372-a567-0e02-2c3d479", 0, Invalid, false}, + {"g47ac10b-58cc-4372-a567-0e02b2c3d479", 0, Invalid, false}, +} + +var constants = []struct { + c interface{} + name string +}{ + {Person, "Person"}, + {Group, "Group"}, + {Org, "Org"}, + {Invalid, "Invalid"}, + {RFC4122, "RFC4122"}, + {Reserved, "Reserved"}, + {Microsoft, "Microsoft"}, + {Future, "Future"}, + {Domain(17), "Domain17"}, + {Variant(42), "BadVariant42"}, +} + +func testTest(t *testing.T, in string, tt test) { + uuid := Parse(in) + if ok := (uuid != nil); ok != tt.isuuid { + t.Errorf("Parse(%s) got %v expected %v\b", in, ok, tt.isuuid) + } + if uuid == nil { + return + } + + if v := uuid.Variant(); v != tt.variant { + t.Errorf("Variant(%s) got %d expected %d\b", in, v, tt.variant) + } + if v, _ := uuid.Version(); v != tt.version { + t.Errorf("Version(%s) got %d expected %d\b", in, v, tt.version) + } +} + +func TestUUID(t *testing.T) { + for _, tt := range tests { + testTest(t, tt.in, tt) + testTest(t, strings.ToUpper(tt.in), tt) + } +} + +func TestConstants(t *testing.T) { + for x, tt := range constants { + v, ok := tt.c.(fmt.Stringer) + if !ok { + t.Errorf("%x: %v: not a stringer", x, v) + } else if s := v.String(); s != tt.name { + v, _ := tt.c.(int) + t.Errorf("%x: Constant %T:%d gives %q, expected %q", x, tt.c, v, s, tt.name) + } + } +} + +func TestRandomUUID(t *testing.T) { + m := make(map[string]bool) + for x := 1; x < 32; x++ { + uuid := NewRandom() + s := uuid.String() + if m[s] { + t.Errorf("NewRandom returned duplicated UUID %s", s) + } + m[s] = true + if v, _ := uuid.Version(); v != 4 { + t.Errorf("Random UUID of version %s", v) + } + if uuid.Variant() != RFC4122 { + t.Errorf("Random UUID is variant %d", uuid.Variant()) + } + } +} + +func TestNew(t *testing.T) { + m := make(map[string]bool) + for x := 1; x < 32; x++ { + s := New() + if m[s] { + t.Errorf("New returned duplicated UUID %s", s) + } + m[s] = true + uuid := Parse(s) + if uuid == nil { + t.Errorf("New returned %q which does not decode", s) + continue + } + if v, _ := uuid.Version(); v != 4 { + t.Errorf("Random UUID of version %s", v) + } + if uuid.Variant() != RFC4122 { + t.Errorf("Random UUID is variant %d", uuid.Variant()) + } + } +} + +func TestCoding(t *testing.T) { + text := "7d444840-9dc0-11d1-b245-5ffdce74fad2" + urn := "urn:uuid:7d444840-9dc0-11d1-b245-5ffdce74fad2" + data := UUID{ + 0x7d, 0x44, 0x48, 0x40, + 0x9d, 0xc0, + 0x11, 0xd1, + 0xb2, 0x45, + 0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2, + } + if v := data.String(); v != text { + t.Errorf("%x: encoded to %s, expected %s", data, v, text) + } + if v := data.URN(); v != urn { + t.Errorf("%x: urn is %s, expected %s", data, v, urn) + } + + uuid := Parse(text) + if !Equal(uuid, data) { + t.Errorf("%s: decoded to %s, expected %s", text, uuid, data) + } +} + +func TestVersion1(t *testing.T) { + uuid1 := NewUUID() + uuid2 := NewUUID() + + if Equal(uuid1, uuid2) { + t.Errorf("%s:duplicate uuid", uuid1) + } + if v, _ := uuid1.Version(); v != 1 { + t.Errorf("%s: version %s expected 1", uuid1, v) + } + if v, _ := uuid2.Version(); v != 1 { + t.Errorf("%s: version %s expected 1", uuid2, v) + } + n1 := uuid1.NodeID() + n2 := uuid2.NodeID() + if !bytes.Equal(n1, n2) { + t.Errorf("Different nodes %x != %x", n1, n2) + } + t1, ok := uuid1.Time() + if !ok { + t.Errorf("%s: invalid time", uuid1) + } + t2, ok := uuid2.Time() + if !ok { + t.Errorf("%s: invalid time", uuid2) + } + q1, ok := uuid1.ClockSequence() + if !ok { + t.Errorf("%s: invalid clock sequence", uuid1) + } + q2, ok := uuid2.ClockSequence() + if !ok { + t.Errorf("%s: invalid clock sequence", uuid2) + } + + switch { + case t1 == t2 && q1 == q2: + t.Error("time stopped") + case t1 > t2 && q1 == q2: + t.Error("time reversed") + case t1 < t2 && q1 != q2: + t.Error("clock sequence chaned unexpectedly") + } +} + +func TestMD5(t *testing.T) { + uuid := NewMD5(NameSpace_DNS, []byte("python.org")).String() + want := "6fa459ea-ee8a-3ca4-894e-db77e160355e" + if uuid != want { + t.Errorf("MD5: got %q expected %q", uuid, want) + } +} + +func TestSHA1(t *testing.T) { + uuid := NewSHA1(NameSpace_DNS, []byte("python.org")).String() + want := "886313e1-3b8a-5372-9b90-0c9aee199e5d" + if uuid != want { + t.Errorf("SHA1: got %q expected %q", uuid, want) + } +} + +func testDCE(t *testing.T, name string, uuid UUID, domain Domain, id uint32) { + if uuid == nil { + t.Errorf("%s failed", name) + return + } + if v, _ := uuid.Version(); v != 2 { + t.Errorf("%s: %s: expected version 2, got %s", name, uuid, v) + return + } + if v, ok := uuid.Domain(); !ok || v != domain { + if !ok { + t.Errorf("%s: %d: Domain failed", name, uuid) + } else { + t.Errorf("%s: %s: expected domain %d, got %d", name, uuid, domain, v) + } + } + if v, ok := uuid.Id(); !ok || v != id { + if !ok { + t.Errorf("%s: %d: Id failed", name, uuid) + } else { + t.Errorf("%s: %s: expected id %d, got %d", name, uuid, id, v) + } + } +} + +func TestDCE(t *testing.T) { + testDCE(t, "NewDCESecurity", NewDCESecurity(42, 12345678), 42, 12345678) + testDCE(t, "NewDCEPerson", NewDCEPerson(), Person, uint32(os.Getuid())) + testDCE(t, "NewDCEGroup", NewDCEGroup(), Group, uint32(os.Getgid())) +} + +type badRand struct{} + +func (r badRand) Read(buf []byte) (int, error) { + for i, _ := range buf { + buf[i] = byte(i) + } + return len(buf), nil +} + +func TestBadRand(t *testing.T) { + SetRand(badRand{}) + uuid1 := New() + uuid2 := New() + if uuid1 != uuid2 { + t.Errorf("expected duplicates, got %q and %q", uuid1, uuid2) + } + SetRand(nil) + uuid1 = New() + uuid2 = New() + if uuid1 == uuid2 { + t.Errorf("unexpected duplicates, got %q", uuid1) + } +} + +func TestUUID_Array(t *testing.T) { + expect := Array{ + 0xf4, 0x7a, 0xc1, 0x0b, + 0x58, 0xcc, + 0x03, 0x72, + 0x85, 0x67, + 0x0e, 0x02, 0xb2, 0xc3, 0xd4, 0x79, + } + uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if uuid == nil { + t.Fatal("invalid uuid") + } + if uuid.Array() != expect { + t.Fatal("invalid array") + } +} + +func TestArray_UUID(t *testing.T) { + array := Array{ + 0xf4, 0x7a, 0xc1, 0x0b, + 0x58, 0xcc, + 0x03, 0x72, + 0x85, 0x67, + 0x0e, 0x02, 0xb2, 0xc3, 0xd4, 0x79, + } + expect := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if expect == nil { + t.Fatal("invalid uuid") + } + if !bytes.Equal(array.UUID(), expect) { + t.Fatal("invalid uuid") + } +} + +func BenchmarkParse(b *testing.B) { + for i := 0; i < b.N; i++ { + uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if uuid == nil { + b.Fatal("invalid uuid") + } + } +} + +func BenchmarkNew(b *testing.B) { + for i := 0; i < b.N; i++ { + New() + } +} + +func BenchmarkUUID_String(b *testing.B) { + uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if uuid == nil { + b.Fatal("invalid uuid") + } + for i := 0; i < b.N; i++ { + if uuid.String() == "" { + b.Fatal("invalid uuid") + } + } +} + +func BenchmarkUUID_URN(b *testing.B) { + uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if uuid == nil { + b.Fatal("invalid uuid") + } + for i := 0; i < b.N; i++ { + if uuid.URN() == "" { + b.Fatal("invalid uuid") + } + } +} + +func BenchmarkUUID_Array(b *testing.B) { + expect := Array{ + 0xf4, 0x7a, 0xc1, 0x0b, + 0x58, 0xcc, + 0x03, 0x72, + 0x85, 0x67, + 0x0e, 0x02, 0xb2, 0xc3, 0xd4, 0x79, + } + uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if uuid == nil { + b.Fatal("invalid uuid") + } + for i := 0; i < b.N; i++ { + if uuid.Array() != expect { + b.Fatal("invalid array") + } + } +} + +func BenchmarkArray_UUID(b *testing.B) { + array := Array{ + 0xf4, 0x7a, 0xc1, 0x0b, + 0x58, 0xcc, + 0x03, 0x72, + 0x85, 0x67, + 0x0e, 0x02, 0xb2, 0xc3, 0xd4, 0x79, + } + expect := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if expect == nil { + b.Fatal("invalid uuid") + } + for i := 0; i < b.N; i++ { + if !bytes.Equal(array.UUID(), expect) { + b.Fatal("invalid uuid") + } + } +} diff --git a/vendor/github.com/pborman/uuid/version1.go b/vendor/github.com/pborman/uuid/version1.go new file mode 100644 index 0000000000..7af948da79 --- /dev/null +++ b/vendor/github.com/pborman/uuid/version1.go @@ -0,0 +1,23 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + guuid "github.com/google/uuid" +) + +// NewUUID returns a Version 1 UUID based on the current NodeID and clock +// sequence, and the current time. If the NodeID has not been set by SetNodeID +// or SetNodeInterface then it will be set automatically. If the NodeID cannot +// be set NewUUID returns nil. If clock sequence has not been set by +// SetClockSequence then it will be set automatically. If GetTime fails to +// return the current NewUUID returns nil. +func NewUUID() UUID { + gu, err := guuid.NewUUID() + if err == nil { + return UUID(gu[:]) + } + return nil +} diff --git a/vendor/github.com/pborman/uuid/version4.go b/vendor/github.com/pborman/uuid/version4.go new file mode 100644 index 0000000000..b459d46d13 --- /dev/null +++ b/vendor/github.com/pborman/uuid/version4.go @@ -0,0 +1,26 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import guuid "github.com/google/uuid" + +// Random returns a Random (Version 4) UUID or panics. +// +// The strength of the UUIDs is based on the strength of the crypto/rand +// package. +// +// A note about uniqueness derived from the UUID Wikipedia entry: +// +// Randomly generated UUIDs have 122 random bits. One's annual risk of being +// hit by a meteorite is estimated to be one chance in 17 billion, that +// means the probability is about 0.00000000006 (6 × 10−11), +// equivalent to the odds of creating a few tens of trillions of UUIDs in a +// year and having one duplicate. +func NewRandom() UUID { + if gu, err := guuid.NewRandom(); err == nil { + return UUID(gu[:]) + } + return nil +}