mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into develop
This commit is contained in:
commit
9605ab4475
21
CHANGELOG.md
21
CHANGELOG.md
@ -12,8 +12,21 @@
|
||||
## New Features
|
||||
* **Data Source Proxy**: Add support for whitelisting specified cookies that will be passed through to the data source when proxying data source requests [#5457](https://github.com/grafana/grafana/issues/5457), thanks [@robingustafsson](https://github.com/robingustafsson)
|
||||
|
||||
## Fixes
|
||||
|
||||
## Tech
|
||||
* **RabbitMq**: Remove support for publishing events to RabbitMQ [#9645](https://github.com/grafana/grafana/issues/9645)
|
||||
|
||||
## Fixes
|
||||
* **Sensu**: Send alert message to sensu output [#9551](https://github.com/grafana/grafana/issues/9551), thx [@cjchand](https://github.com/cjchand)
|
||||
* **Singlestat**: suppress error when result contains no datapoints [#9636](https://github.com/grafana/grafana/issues/9636), thx [@utkarshcmu](https://github.com/utkarshcmu)
|
||||
* **Postgres/MySQL**: Control quoting in SQL-queries when using template variables [#9030](https://github.com/grafana/grafana/issues/9030), thanks [@svenklemm](https://github.com/svenklemm)
|
||||
|
||||
# 4.6.0 (2017-10-26)
|
||||
|
||||
## Fixes
|
||||
* **Alerting**: Viewer can no longer pause alert rules [#9640](https://github.com/grafana/grafana/issues/9640)
|
||||
* **Playlist**: Bug where playlist controls was missing [#9639](https://github.com/grafana/grafana/issues/9639)
|
||||
* **Firefox**: Creating region annotations now work in firefox [#9638](https://github.com/grafana/grafana/issues/9638)
|
||||
|
||||
# 4.6.0-beta3 (2017-10-23)
|
||||
|
||||
@ -56,11 +69,11 @@
|
||||
* **OAuth**: Verify TLS during OAuth callback [#9373](https://github.com/grafana/grafana/issues/9373), thx [@mattbostock](https://github.com/mattbostock)
|
||||
|
||||
## Minor
|
||||
* **SMTP**: Make it possible to set specific EHLO for smtp client. [#9319](https://github.com/grafana/grafana/issues/9319)
|
||||
* **Dataproxy**: Allow grafan to renegotiate tls connection [#9250](https://github.com/grafana/grafana/issues/9250)
|
||||
* **SMTP**: Make it possible to set specific HELO for smtp client. [#9319](https://github.com/grafana/grafana/issues/9319)
|
||||
* **Dataproxy**: Allow grafana to renegotiate tls connection [#9250](https://github.com/grafana/grafana/issues/9250)
|
||||
* **HTTP**: set net.Dialer.DualStack to true for all http clients [#9367](https://github.com/grafana/grafana/pull/9367)
|
||||
* **Alerting**: Add diff and percent diff as series reducers [#9386](https://github.com/grafana/grafana/pull/9386), thx [@shanhuhai5739](https://github.com/shanhuhai5739)
|
||||
* **Slack**: Allow images to be uploaded to slack when Token is precent [#7175](https://github.com/grafana/grafana/issues/7175), thx [@xginn8](https://github.com/xginn8)
|
||||
* **Slack**: Allow images to be uploaded to slack when Token is present [#7175](https://github.com/grafana/grafana/issues/7175), thx [@xginn8](https://github.com/xginn8)
|
||||
* **Opsgenie**: Use their latest API instead of old version [#9399](https://github.com/grafana/grafana/pull/9399), thx [@cglrkn](https://github.com/cglrkn)
|
||||
* **Table**: Add support for displaying the timestamp with milliseconds [#9429](https://github.com/grafana/grafana/pull/9429), thx [@s1061123](https://github.com/s1061123)
|
||||
* **Hipchat**: Add metrics, message and image to hipchat notifications [#9110](https://github.com/grafana/grafana/issues/9110), thx [@eloo](https://github.com/eloo)
|
||||
|
11
codecov.yml
Normal file
11
codecov.yml
Normal file
@ -0,0 +1,11 @@
|
||||
coverage:
|
||||
precision: 2
|
||||
round: down
|
||||
range: "50...100"
|
||||
|
||||
status:
|
||||
project: yes
|
||||
patch: yes
|
||||
changes: no
|
||||
|
||||
comment: false
|
@ -381,13 +381,6 @@ facility =
|
||||
# Syslog tag. By default, the process' argv[0] is used.
|
||||
tag =
|
||||
|
||||
|
||||
#################################### AMQP Event Publisher ################
|
||||
[event_publisher]
|
||||
enabled = false
|
||||
rabbitmq_url = amqp://localhost/
|
||||
exchange = grafana_events
|
||||
|
||||
#################################### Dashboard JSON files ################
|
||||
[dashboards.json]
|
||||
enabled = false
|
||||
|
@ -360,12 +360,6 @@
|
||||
;tag =
|
||||
|
||||
|
||||
#################################### AMQP Event Publisher ##########################
|
||||
[event_publisher]
|
||||
;enabled = false
|
||||
;rabbitmq_url = amqp://localhost/
|
||||
;exchange = grafana_events
|
||||
|
||||
;#################################### Dashboard JSON files ##########################
|
||||
[dashboards.json]
|
||||
;enabled = false
|
||||
|
@ -27,8 +27,7 @@ and the conditions that need to be met for the alert to change state and trigger
|
||||
## Execution
|
||||
|
||||
The alert rules are evaluated in the Grafana backend in a scheduler and query execution engine that is part
|
||||
of core Grafana. Only some data sources are supported right now. They include `Graphite`, `Prometheus`,
|
||||
`InfluxDB` and `OpenTSDB`.
|
||||
of core Grafana. Only some data sources are supported right now. They include `Graphite`, `Prometheus`, `InfluxDB`, `OpenTSDB`, `MySQL`, `Postgres` and `Cloudwatch`.
|
||||
|
||||
### Clustering
|
||||
|
||||
|
@ -169,5 +169,3 @@ Amazon provides 1 million CloudWatch API requests each month at no additional ch
|
||||
it costs $0.01 per 1,000 GetMetricStatistics or ListMetrics requests. For each query Grafana will
|
||||
issue a GetMetricStatistics request and every time you pick a dimension in the query editor
|
||||
Grafana will issue a ListMetrics request.
|
||||
|
||||
|
||||
|
@ -28,8 +28,9 @@ The following datasources are officially supported:
|
||||
* [InfluxDB]({{< relref "influxdb.md" >}})
|
||||
* [OpenTSDB]({{< relref "opentsdb.md" >}})
|
||||
* [Prometheus]({{< relref "prometheus.md" >}})
|
||||
* [MySQL]({{< relref "mysql.md" >}})
|
||||
* [Postgres]({{< relref "postgres.md" >}})
|
||||
|
||||
## Data source plugins
|
||||
|
||||
Since grafana 3.0 you can install data sources as plugins. Checkout [Grafana.net](https://grafana.com/plugins) for more data sources.
|
||||
|
||||
|
@ -173,6 +173,4 @@ SELECT title, description from events WHERE $timeFilter order asc
|
||||
|
||||
For InfluxDB you need to enter a query like in the above example. You need to have the ```where $timeFilter```
|
||||
part. If you only select one column you will not need to enter anything in the column mapping fields. The
|
||||
Tags field can be a comma seperated string.
|
||||
|
||||
|
||||
Tags field can be a comma separated string.
|
||||
|
@ -142,7 +142,11 @@ SELECT hostname FROM my_host WHERE region IN($region)
|
||||
|
||||
### Using Variables in Queries
|
||||
|
||||
Template variables are quoted automatically so if it is a string value do not wrap them in quotes in where clauses. If the variable is a multi-value variable then use the `IN` comparison operator rather than `=` to match against multiple values.
|
||||
From Grafana 4.3.0 to 4.6.0, template variables are always quoted automatically so if it is a string value do not wrap them in quotes in where clauses.
|
||||
|
||||
From Grafana 4.7.0, template variable values are only quoted when the template variable is a `multi-value`.
|
||||
|
||||
If the variable is a multi-value variable then use the `IN` comparison operator rather than `=` to match against multiple values.
|
||||
|
||||
There are two syntaxes:
|
||||
|
||||
@ -170,7 +174,28 @@ WHERE $__timeFilter(atimestamp) and hostname in([[hostname]])
|
||||
ORDER BY atimestamp ASC
|
||||
```
|
||||
|
||||
## Annotations
|
||||
|
||||
[Annotations]({{< relref "reference/annotations.md" >}}) allows you to overlay rich event information on top of graphs. You add annotation queries via the Dashboard menu / Annotations view.
|
||||
|
||||
An example query:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
UNIX_TIMESTAMP(atimestamp) as time_sec,
|
||||
value as text,
|
||||
CONCAT(tag1, ',', tag2) as tags
|
||||
FROM my_table
|
||||
WHERE $__timeFilter(atimestamp)
|
||||
ORDER BY atimestamp ASC
|
||||
```
|
||||
|
||||
Name | Description
|
||||
------------ | -------------
|
||||
time_sec | The name of the date/time field.
|
||||
text | Event description field.
|
||||
tags | Optional field name to use for event tags as a comma separated string.
|
||||
|
||||
## Alerting
|
||||
|
||||
Time series queries should work in alerting conditions. Table formatted queries is not yet supported in alert rule
|
||||
conditions.
|
||||
Time series queries should work in alerting conditions. Table formatted queries is not yet supported in alert rule conditions.
|
||||
|
@ -154,7 +154,11 @@ SELECT hostname FROM host WHERE region IN($region)
|
||||
|
||||
### Using Variables in Queries
|
||||
|
||||
Template variables are quoted automatically so if it is a string value do not wrap them in quotes in where clauses. If the variable is a multi-value variable then use the `IN` comparison operator rather than `=` to match against multiple values.
|
||||
From Grafana 4.3.0 to 4.6.0, template variables are always quoted automatically so if it is a string value do not wrap them in quotes in where clauses.
|
||||
|
||||
From Grafana 4.7.0, template variable values are only quoted when the template variable is a `multi-value`.
|
||||
|
||||
If the variable is a multi-value variable then use the `IN` comparison operator rather than `=` to match against multiple values.
|
||||
|
||||
There are two syntaxes:
|
||||
|
||||
@ -180,6 +184,29 @@ WHERE $__timeFilter(atimestamp) and hostname in([[hostname]])
|
||||
ORDER BY atimestamp ASC
|
||||
```
|
||||
|
||||
## Annotations
|
||||
|
||||
[Annotations]({{< relref "reference/annotations.md" >}}) allows you to overlay rich event information on top of graphs. You add annotation queries via the Dashboard menu / Annotations view.
|
||||
|
||||
An example query:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
extract(epoch from time_date_time) AS time,
|
||||
metric1 as text,
|
||||
concat_ws(', ', metric1::text, metric2::text) as tags
|
||||
FROM
|
||||
public.test_data
|
||||
WHERE
|
||||
$__timeFilter(time_date_time)
|
||||
```
|
||||
|
||||
Name | Description
|
||||
------------ | -------------
|
||||
time | The name of the date/time field.
|
||||
text | Event description field.
|
||||
tags | Optional field name to use for event tags as a comma separated string.
|
||||
|
||||
## Alerting
|
||||
|
||||
Time series queries should work in alerting conditions. Table formatted queries is not yet supported in alert rule
|
||||
|
@ -102,11 +102,6 @@ Content-Type: application/json
|
||||
"templates_pattern":"emails/*.html",
|
||||
"welcome_email_on_sign_up":"false"
|
||||
},
|
||||
"event_publisher":{
|
||||
"enabled":"false",
|
||||
"exchange":"grafana_events",
|
||||
"rabbitmq_url":"amqp://localhost/"
|
||||
},
|
||||
"log":{
|
||||
"buffer_len":"10000",
|
||||
"level":"Info",
|
||||
|
@ -14,12 +14,12 @@ parent = "http_api"
|
||||
|
||||
## Get current Organisation
|
||||
|
||||
`GET /api/org`
|
||||
`GET /api/org/`
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
GET /api/org HTTP/1.1
|
||||
GET /api/org/ HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
@ -49,6 +49,8 @@ Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
Note: The api will only work when you pass the admin name and password
|
||||
to the request http url, like http://admin:admin@localhost:3000/api/orgs/1
|
||||
|
||||
**Example Response**:
|
||||
|
||||
@ -81,6 +83,8 @@ Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
Note: The api will only work when you pass the admin name and password
|
||||
to the request http url, like http://admin:admin@localhost:3000/api/orgs/name/Main%20Org%2E
|
||||
|
||||
**Example Response**:
|
||||
|
||||
@ -118,6 +122,9 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
"name":"New Org."
|
||||
}
|
||||
```
|
||||
Note: The api will work in the following two ways
|
||||
1) Need to set GF_USERS_ALLOW_ORG_CREATE=true
|
||||
2) Set the config users.allow_org_create to true in ini file
|
||||
|
||||
**Example Response**:
|
||||
|
||||
@ -279,6 +286,8 @@ Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
Note: The api will only work when you pass the admin name and password
|
||||
to the request http url, like http://admin:admin@localhost:3000/api/orgs
|
||||
|
||||
**Example Response**:
|
||||
|
||||
@ -334,6 +343,9 @@ Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
Note: The api will only work when you pass the admin name and password
|
||||
to the request http url, like http://admin:admin@localhost:3000/api/orgs/1/users
|
||||
|
||||
|
||||
**Example Response**:
|
||||
|
||||
|
@ -35,17 +35,19 @@ The back-end web server has a number of configuration options. Go the
|
||||
those options.
|
||||
|
||||
|
||||
## Getting started
|
||||
## Getting Started
|
||||
|
||||
- [Getting Started]({{< relref "guides/getting_started.md" >}})
|
||||
- [Basic Concepts]({{< relref "guides/basic_concepts.md" >}})
|
||||
- [Screencasts]({{< relref "tutorials/screencasts.md" >}})
|
||||
|
||||
## Data sources guides
|
||||
## Data Source Guides
|
||||
|
||||
- [Graphite]({{< relref "features/datasources/graphite.md" >}})
|
||||
- [Elasticsearch]({{< relref "features/datasources/elasticsearch.md" >}})
|
||||
- [InfluxDB]({{< relref "features/datasources/influxdb.md" >}})
|
||||
- [OpenTSDB]({{< relref "features/datasources/opentsdb.md" >}})
|
||||
|
||||
|
||||
- [Prometheus]({{< relref "features/datasources/influxdb.md" >}})
|
||||
- [OpenTSDB]({{< relref "features/datasources/prometheus.md" >}})
|
||||
- [MySQL]({{< relref "features/datasources/mysql.md" >}})
|
||||
- [Postgres]({{< relref "features/datasources/postgres.md" >}})
|
||||
- [Cloudwatch]({{< relref "features/datasources/cloudwatch.md" >}})
|
||||
|
@ -15,7 +15,7 @@ weight = 1
|
||||
|
||||
Description | Download
|
||||
------------ | -------------
|
||||
Stable for Debian-based Linux | [grafana_4.5.2_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.5.2_amd64.deb)
|
||||
Stable for Debian-based Linux | [grafana_4.6.0_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.6.0_amd64.deb)
|
||||
|
||||
<!-- Beta for Debian-based Linux | [grafana_4.5.0-beta1_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.5.0-beta1_amd64.deb) -->
|
||||
|
||||
@ -26,9 +26,9 @@ installation.
|
||||
|
||||
|
||||
```bash
|
||||
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.5.2_amd64.deb
|
||||
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.6.0_amd64.deb
|
||||
sudo apt-get install -y adduser libfontconfig
|
||||
sudo dpkg -i grafana_4.5.2_amd64.deb
|
||||
sudo dpkg -i grafana_4.6.0_amd64.deb
|
||||
```
|
||||
|
||||
<!--
|
||||
|
@ -36,11 +36,75 @@ $ docker run -d -p 3000:3000 \
|
||||
```
|
||||
|
||||
In the above example I map the data folder and sets a configuration option via
|
||||
an `ENV` instruction.
|
||||
an `ENV` instruction.
|
||||
|
||||
See the [docker volumes documentation](https://docs.docker.com/engine/admin/volumes/volumes/) if you want to create a volume to use with the Grafana docker image instead of a bind mount (binding to a directory in the host system).
|
||||
|
||||
## Configuration
|
||||
|
||||
The back-end web server has a number of configuration options. Go the
|
||||
All options defined in conf/grafana.ini can be overridden using environment
|
||||
variables by using the syntax `GF_<SectionName>_<KeyName>`.
|
||||
For example:
|
||||
|
||||
```bash
|
||||
$ docker run \
|
||||
-d \
|
||||
-p 3000:3000 \
|
||||
--name=grafana \
|
||||
-e "GF_SERVER_ROOT_URL=http://grafana.server.name" \
|
||||
-e "GF_SECURITY_ADMIN_PASSWORD=secret" \
|
||||
grafana/grafana
|
||||
```
|
||||
|
||||
You can use your own grafana.ini file by using environment variable `GF_PATHS_CONFIG`.
|
||||
|
||||
The back-end web server has a number of configuration options. Go to the
|
||||
[Configuration]({{< relref "configuration.md" >}}) page for details on all
|
||||
those options.
|
||||
|
||||
## Installing Plugins for Grafana
|
||||
|
||||
Pass the plugins you want installed to docker with the `GF_INSTALL_PLUGINS` environment variable as a comma separated list. This will pass each plugin name to `grafana-cli plugins install ${plugin}`.
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
-d \
|
||||
-p 3000:3000 \
|
||||
--name=grafana \
|
||||
-e "GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource" \
|
||||
grafana/grafana
|
||||
```
|
||||
|
||||
## Running a Specific Version of Grafana
|
||||
|
||||
```bash
|
||||
# specify right tag, e.g. 4.5.2 - see Docker Hub for available tags
|
||||
$ docker run \
|
||||
-d \
|
||||
-p 3000:3000 \
|
||||
--name grafana \
|
||||
grafana/grafana:4.5.2
|
||||
```
|
||||
|
||||
## Configuring AWS Credentials for CloudWatch Support
|
||||
|
||||
```bash
|
||||
$ docker run \
|
||||
-d \
|
||||
-p 3000:3000 \
|
||||
--name=grafana \
|
||||
-e "GF_AWS_PROFILES=default" \
|
||||
-e "GF_AWS_default_ACCESS_KEY_ID=YOUR_ACCESS_KEY" \
|
||||
-e "GF_AWS_default_SECRET_ACCESS_KEY=YOUR_SECRET_KEY" \
|
||||
-e "GF_AWS_default_REGION=us-east-1" \
|
||||
grafana/grafana
|
||||
```
|
||||
|
||||
You may also specify multiple profiles to `GF_AWS_PROFILES` (e.g.
|
||||
`GF_AWS_PROFILES=default another`).
|
||||
|
||||
Supported variables:
|
||||
|
||||
- `GF_AWS_${profile}_ACCESS_KEY_ID`: AWS access key ID (required).
|
||||
- `GF_AWS_${profile}_SECRET_ACCESS_KEY`: AWS secret access key (required).
|
||||
- `GF_AWS_${profile}_REGION`: AWS region (optional).
|
||||
|
@ -15,7 +15,7 @@ weight = 2
|
||||
|
||||
Description | Download
|
||||
------------ | -------------
|
||||
Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [4.5.2 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.2-1.x86_64.rpm)
|
||||
Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [4.6.0 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.6.0-1.x86_64.rpm)
|
||||
|
||||
<!-- Latest Beta for CentOS / Fedora / OpenSuse / Redhat Linux | [4.5.0-beta1 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.0-beta1.x86_64.rpm) -->
|
||||
|
||||
@ -27,7 +27,7 @@ installation.
|
||||
You can install Grafana using Yum directly.
|
||||
|
||||
```bash
|
||||
$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.2-1.x86_64.rpm
|
||||
$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.6.0-1.x86_64.rpm
|
||||
```
|
||||
|
||||
Or install manually using `rpm`.
|
||||
@ -35,15 +35,15 @@ Or install manually using `rpm`.
|
||||
#### On CentOS / Fedora / Redhat:
|
||||
|
||||
```bash
|
||||
$ wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.2-1.x86_64.rpm
|
||||
$ wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.6.0-1.x86_64.rpm
|
||||
$ sudo yum install initscripts fontconfig
|
||||
$ sudo rpm -Uvh grafana-4.5.2-1.x86_64.rpm
|
||||
$ sudo rpm -Uvh grafana-4.6.0-1.x86_64.rpm
|
||||
```
|
||||
|
||||
#### On OpenSuse:
|
||||
|
||||
```bash
|
||||
$ sudo rpm -i --nodeps grafana-4.5.2-1.x86_64.rpm
|
||||
$ sudo rpm -i --nodeps grafana-4.6.0-1.x86_64.rpm
|
||||
```
|
||||
|
||||
## Install via YUM Repository
|
||||
|
@ -13,7 +13,7 @@ weight = 3
|
||||
|
||||
Description | Download
|
||||
------------ | -------------
|
||||
Latest stable package for Windows | [grafana.4.5.2.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.2.windows-x64.zip)
|
||||
Latest stable package for Windows | [grafana.4.6.0.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.6.0.windows-x64.zip)
|
||||
|
||||
Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing
|
||||
installation.
|
||||
|
@ -16,7 +16,7 @@ You can extend Grafana by writing your own plugins and then share then with othe
|
||||
|
||||
1. [Setup grafana](http://docs.grafana.org/project/building_from_source/)
|
||||
2. Clone an example plugin into ```/var/lib/grafana/plugins``` or `data/plugins` (relative to grafana git repo if you're running development version from source dir)
|
||||
3. You one of our example plugins as starting point
|
||||
3. Use one of our example plugins as starting point
|
||||
|
||||
Example plugins
|
||||
|
||||
|
@ -69,5 +69,5 @@ The annotation query options are different for each data source.
|
||||
- [Elasticsearch annotation queries]({{< relref "features/datasources/elasticsearch.md#annotations" >}})
|
||||
- [InfluxDB annotation queries]({{< relref "features/datasources/influxdb.md#annotations" >}})
|
||||
- [Prometheus annotation queries]({{< relref "features/datasources/prometheus.md#annotations" >}})
|
||||
|
||||
|
||||
- [MySQL annotation queries]({{< relref "features/datasources/mysql.md#annotations" >}})
|
||||
- [Postgres annotation queries]({{< relref "features/datasources/postgres.md#annotations" >}})
|
||||
|
@ -287,7 +287,7 @@ func (hs *HttpServer) registerRoutes() {
|
||||
|
||||
apiRoute.Group("/alerts", func(alertsRoute RouteRegister) {
|
||||
alertsRoute.Post("/test", bind(dtos.AlertTestCommand{}), wrap(AlertTest))
|
||||
alertsRoute.Post("/:alertId/pause", bind(dtos.PauseAlertCommand{}), wrap(PauseAlert), reqEditorRole)
|
||||
alertsRoute.Post("/:alertId/pause", reqEditorRole, bind(dtos.PauseAlertCommand{}), wrap(PauseAlert))
|
||||
alertsRoute.Get("/:alertId", ValidateOrgAlert, wrap(GetAlert))
|
||||
alertsRoute.Get("/", wrap(GetAlerts))
|
||||
alertsRoute.Get("/states-for-dashboard", wrap(GetAlertStatesForDashboard))
|
||||
|
@ -119,7 +119,13 @@ func AddDataSource(c *middleware.Context, cmd m.AddDataSourceCommand) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, util.DynMap{"message": "Datasource added", "id": cmd.Result.Id, "name": cmd.Result.Name})
|
||||
ds := convertModelToDtos(cmd.Result)
|
||||
c.JSON(200, util.DynMap{
|
||||
"message": "Datasource added",
|
||||
"id": cmd.Result.Id,
|
||||
"name": cmd.Result.Name,
|
||||
"datasource": ds,
|
||||
})
|
||||
}
|
||||
|
||||
func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) Response {
|
||||
@ -133,10 +139,19 @@ func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) Resp
|
||||
|
||||
err = bus.Dispatch(&cmd)
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to update datasource", err)
|
||||
if err == m.ErrDataSourceUpdatingOldVersion {
|
||||
return ApiError(500, "Failed to update datasource. Reload new version and try again", err)
|
||||
} else {
|
||||
return ApiError(500, "Failed to update datasource", err)
|
||||
}
|
||||
}
|
||||
|
||||
return Json(200, util.DynMap{"message": "Datasource updated", "id": cmd.Id, "name": cmd.Name})
|
||||
ds := convertModelToDtos(cmd.Result)
|
||||
return Json(200, util.DynMap{
|
||||
"message": "Datasource updated",
|
||||
"id": cmd.Id,
|
||||
"name": cmd.Name,
|
||||
"datasource": ds,
|
||||
})
|
||||
}
|
||||
|
||||
func fillWithSecureJsonData(cmd *m.UpdateDataSourceCommand) error {
|
||||
@ -158,8 +173,6 @@ func fillWithSecureJsonData(cmd *m.UpdateDataSourceCommand) error {
|
||||
}
|
||||
}
|
||||
|
||||
// set version from db
|
||||
cmd.Version = ds.Version
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -228,6 +241,7 @@ func convertModelToDtos(ds *m.DataSource) dtos.DataSource {
|
||||
IsDefault: ds.IsDefault,
|
||||
JsonData: ds.JsonData,
|
||||
SecureJsonFields: map[string]bool{},
|
||||
Version: ds.Version,
|
||||
}
|
||||
|
||||
for k, v := range ds.SecureJsonData {
|
||||
|
59
pkg/api/dtos/datasource.go
Normal file
59
pkg/api/dtos/datasource.go
Normal file
@ -0,0 +1,59 @@
|
||||
package dtos
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
type DataSource struct {
|
||||
Id int64 `json:"id"`
|
||||
OrgId int64 `json:"orgId"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
TypeLogoUrl string `json:"typeLogoUrl"`
|
||||
Access m.DsAccess `json:"access"`
|
||||
Url string `json:"url"`
|
||||
Password string `json:"password"`
|
||||
User string `json:"user"`
|
||||
Database string `json:"database"`
|
||||
BasicAuth bool `json:"basicAuth"`
|
||||
BasicAuthUser string `json:"basicAuthUser"`
|
||||
BasicAuthPassword string `json:"basicAuthPassword"`
|
||||
WithCredentials bool `json:"withCredentials"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
JsonData *simplejson.Json `json:"jsonData,omitempty"`
|
||||
SecureJsonFields map[string]bool `json:"secureJsonFields"`
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
type DataSourceListItemDTO struct {
|
||||
Id int64 `json:"id"`
|
||||
OrgId int64 `json:"orgId"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
TypeLogoUrl string `json:"typeLogoUrl"`
|
||||
Access m.DsAccess `json:"access"`
|
||||
Url string `json:"url"`
|
||||
Password string `json:"password"`
|
||||
User string `json:"user"`
|
||||
Database string `json:"database"`
|
||||
BasicAuth bool `json:"basicAuth"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
JsonData *simplejson.Json `json:"jsonData,omitempty"`
|
||||
}
|
||||
|
||||
type DataSourceList []DataSourceListItemDTO
|
||||
|
||||
func (slice DataSourceList) Len() int {
|
||||
return len(slice)
|
||||
}
|
||||
|
||||
func (slice DataSourceList) Less(i, j int) bool {
|
||||
return strings.ToLower(slice[i].Name) < strings.ToLower(slice[j].Name)
|
||||
}
|
||||
|
||||
func (slice DataSourceList) Swap(i, j int) {
|
||||
slice[i], slice[j] = slice[j], slice[i]
|
||||
}
|
@ -38,56 +38,6 @@ type CurrentUser struct {
|
||||
HelpFlags1 m.HelpFlags1 `json:"helpFlags1"`
|
||||
}
|
||||
|
||||
type DataSource struct {
|
||||
Id int64 `json:"id"`
|
||||
OrgId int64 `json:"orgId"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
TypeLogoUrl string `json:"typeLogoUrl"`
|
||||
Access m.DsAccess `json:"access"`
|
||||
Url string `json:"url"`
|
||||
Password string `json:"password"`
|
||||
User string `json:"user"`
|
||||
Database string `json:"database"`
|
||||
BasicAuth bool `json:"basicAuth"`
|
||||
BasicAuthUser string `json:"basicAuthUser"`
|
||||
BasicAuthPassword string `json:"basicAuthPassword"`
|
||||
WithCredentials bool `json:"withCredentials"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
JsonData *simplejson.Json `json:"jsonData,omitempty"`
|
||||
SecureJsonFields map[string]bool `json:"secureJsonFields"`
|
||||
}
|
||||
|
||||
type DataSourceListItemDTO struct {
|
||||
Id int64 `json:"id"`
|
||||
OrgId int64 `json:"orgId"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
TypeLogoUrl string `json:"typeLogoUrl"`
|
||||
Access m.DsAccess `json:"access"`
|
||||
Url string `json:"url"`
|
||||
Password string `json:"password"`
|
||||
User string `json:"user"`
|
||||
Database string `json:"database"`
|
||||
BasicAuth bool `json:"basicAuth"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
JsonData *simplejson.Json `json:"jsonData,omitempty"`
|
||||
}
|
||||
|
||||
type DataSourceList []DataSourceListItemDTO
|
||||
|
||||
func (slice DataSourceList) Len() int {
|
||||
return len(slice)
|
||||
}
|
||||
|
||||
func (slice DataSourceList) Less(i, j int) bool {
|
||||
return strings.ToLower(slice[i].Name) < strings.ToLower(slice[j].Name)
|
||||
}
|
||||
|
||||
func (slice DataSourceList) Swap(i, j int) {
|
||||
slice[i], slice[j] = slice[j], slice[i]
|
||||
}
|
||||
|
||||
type MetricRequest struct {
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
|
@ -19,7 +19,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/cleanup"
|
||||
"github.com/grafana/grafana/pkg/services/eventpublisher"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -59,7 +58,6 @@ func (g *GrafanaServerImpl) Start() {
|
||||
search.Init()
|
||||
login.Init()
|
||||
social.NewOAuthService()
|
||||
eventpublisher.Init()
|
||||
plugins.Init()
|
||||
|
||||
closer, err := tracing.Init(setting.Cfg)
|
||||
|
@ -129,7 +129,9 @@ func RenderToPng(params *RenderOpts) (string, error) {
|
||||
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
cmd.Wait()
|
||||
if err := cmd.Wait(); err != nil {
|
||||
rendererLog.Error("failed to render an image", "error", err)
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
|
@ -23,10 +23,10 @@ const (
|
||||
DS_ACCESS_PROXY = "proxy"
|
||||
)
|
||||
|
||||
// Typed errors
|
||||
var (
|
||||
ErrDataSourceNotFound = errors.New("Data source not found")
|
||||
ErrDataSourceNameExists = errors.New("Data source with same name already exists")
|
||||
ErrDataSourceNotFound = errors.New("Data source not found")
|
||||
ErrDataSourceNameExists = errors.New("Data source with same name already exists")
|
||||
ErrDataSourceUpdatingOldVersion = errors.New("Trying to update old version of datasource")
|
||||
)
|
||||
|
||||
type DsAccess string
|
||||
@ -131,10 +131,12 @@ type UpdateDataSourceCommand struct {
|
||||
IsDefault bool `json:"isDefault"`
|
||||
JsonData *simplejson.Json `json:"jsonData"`
|
||||
SecureJsonData map[string]string `json:"secureJsonData"`
|
||||
Version int `json:"version"`
|
||||
|
||||
OrgId int64 `json:"-"`
|
||||
Id int64 `json:"-"`
|
||||
Version int `json:"-"`
|
||||
OrgId int64 `json:"-"`
|
||||
Id int64 `json:"-"`
|
||||
|
||||
Result *DataSource
|
||||
}
|
||||
|
||||
type DeleteDataSourceByIdCommand struct {
|
||||
|
@ -67,7 +67,7 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) {
|
||||
metrics.M_Alerting_Execution_Time.Observe(float64(elapsedTime))
|
||||
}
|
||||
|
||||
// This should be move into evalContext once its been refactored.
|
||||
// This should be move into evalContext once its been refactored. (Carl Bergquist)
|
||||
func (handler *DefaultEvalHandler) getNewState(evalContext *EvalContext) models.AlertStateType {
|
||||
if evalContext.Error != nil {
|
||||
handler.log.Error("Alert Rule Result Error",
|
||||
|
@ -1,150 +0,0 @@
|
||||
package eventpublisher
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/events"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/streadway/amqp"
|
||||
)
|
||||
|
||||
var (
|
||||
url string
|
||||
exchange string
|
||||
conn *amqp.Connection
|
||||
channel *amqp.Channel
|
||||
)
|
||||
|
||||
func getConnection() (*amqp.Connection, error) {
|
||||
c, err := amqp.Dial(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
|
||||
func getChannel() (*amqp.Channel, error) {
|
||||
ch, err := conn.Channel()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ch.ExchangeDeclare(
|
||||
exchange, // name
|
||||
"topic", // type
|
||||
true, // durable
|
||||
false, // auto-deleted
|
||||
false, // internal
|
||||
false, // no-wait
|
||||
nil, // arguments
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ch, err
|
||||
}
|
||||
|
||||
func Init() {
|
||||
sec := setting.Cfg.Section("event_publisher")
|
||||
|
||||
if !sec.Key("enabled").MustBool(false) {
|
||||
return
|
||||
}
|
||||
|
||||
url = sec.Key("rabbitmq_url").String()
|
||||
exchange = sec.Key("exchange").String()
|
||||
bus.AddWildcardListener(eventListener)
|
||||
|
||||
if err := Setup(); err != nil {
|
||||
log.Fatal(4, "Failed to connect to notification queue: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Every connection should declare the topology they expect
|
||||
func Setup() error {
|
||||
c, err := getConnection()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn = c
|
||||
ch, err := getChannel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
channel = ch
|
||||
|
||||
// listen for close events so we can reconnect.
|
||||
errChan := channel.NotifyClose(make(chan *amqp.Error))
|
||||
go func() {
|
||||
for e := range errChan {
|
||||
fmt.Println("connection to rabbitmq lost.")
|
||||
fmt.Println(e)
|
||||
fmt.Println("attempting to create new rabbitmq channel.")
|
||||
ch, err := getChannel()
|
||||
if err == nil {
|
||||
channel = ch
|
||||
break
|
||||
}
|
||||
|
||||
//could not create channel, so lets close the connection
|
||||
// and re-create.
|
||||
_ = conn.Close()
|
||||
|
||||
for err != nil {
|
||||
time.Sleep(2 * time.Second)
|
||||
fmt.Println("attempting to reconnect to rabbitmq.")
|
||||
err = Setup()
|
||||
}
|
||||
fmt.Println("Connected to rabbitmq again.")
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func publish(routingKey string, msgString []byte) {
|
||||
for {
|
||||
err := channel.Publish(
|
||||
exchange, //exchange
|
||||
routingKey, // routing key
|
||||
false, // mandatory
|
||||
false, // immediate
|
||||
amqp.Publishing{
|
||||
ContentType: "application/json",
|
||||
Body: msgString,
|
||||
},
|
||||
)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
// failures are most likely because the connection was lost.
|
||||
// the connection will be re-established, so just keep
|
||||
// retrying every 2seconds until we successfully publish.
|
||||
time.Sleep(2 * time.Second)
|
||||
fmt.Println("publish failed, retrying.")
|
||||
}
|
||||
}
|
||||
|
||||
func eventListener(event interface{}) error {
|
||||
wireEvent, err := events.ToOnWriteEvent(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgString, err := json.Marshal(wireEvent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
routingKey := fmt.Sprintf("%s.%s", wireEvent.Priority, wireEvent.EventType)
|
||||
// this is run in a greenthread and we expect that publish will keep
|
||||
// retrying until the message gets sent.
|
||||
go publish(routingKey, msgString)
|
||||
return nil
|
||||
}
|
@ -3,6 +3,8 @@ package sqlstore
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/securejsondata"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
@ -69,7 +71,6 @@ func DeleteDataSourceByName(cmd *m.DeleteDataSourceByNameCommand) error {
|
||||
}
|
||||
|
||||
func AddDataSource(cmd *m.AddDataSourceCommand) error {
|
||||
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
existing := m.DataSource{OrgId: cmd.OrgId, Name: cmd.Name}
|
||||
has, _ := sess.Get(&existing)
|
||||
@ -96,6 +97,7 @@ func AddDataSource(cmd *m.AddDataSourceCommand) error {
|
||||
SecureJsonData: securejsondata.GetEncryptedJsonData(cmd.SecureJsonData),
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
Version: 1,
|
||||
}
|
||||
|
||||
if _, err := sess.Insert(ds); err != nil {
|
||||
@ -122,7 +124,6 @@ func updateIsDefaultFlag(ds *m.DataSource, sess *DBSession) error {
|
||||
}
|
||||
|
||||
func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
|
||||
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
ds := &m.DataSource{
|
||||
Id: cmd.Id,
|
||||
@ -149,12 +150,29 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
|
||||
sess.UseBool("basic_auth")
|
||||
sess.UseBool("with_credentials")
|
||||
|
||||
_, err := sess.Where("id=? and org_id=?", ds.Id, ds.OrgId).Update(ds)
|
||||
var updateSession *xorm.Session
|
||||
if cmd.Version != 0 {
|
||||
// the reason we allow cmd.version > db.version is make it possible for people to force
|
||||
// updates to datasources using the datasource.yaml file without knowing exactly what version
|
||||
// a datasource have in the db.
|
||||
updateSession = sess.Where("id=? and org_id=? and version < ?", ds.Id, ds.OrgId, ds.Version)
|
||||
|
||||
} else {
|
||||
updateSession = sess.Where("id=? and org_id=?", ds.Id, ds.OrgId)
|
||||
}
|
||||
|
||||
affected, err := updateSession.Update(ds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if affected == 0 {
|
||||
return m.ErrDataSourceUpdatingOldVersion
|
||||
}
|
||||
|
||||
err = updateIsDefaultFlag(ds, sess)
|
||||
|
||||
cmd.Result = ds
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
@ -37,12 +37,9 @@ type Test struct {
|
||||
}
|
||||
|
||||
func TestDataAccess(t *testing.T) {
|
||||
|
||||
Convey("Testing DB", t, func() {
|
||||
InitTestDB(t)
|
||||
|
||||
Convey("Can add datasource", func() {
|
||||
|
||||
err := AddDataSource(&m.AddDataSourceCommand{
|
||||
OrgId: 10,
|
||||
Name: "laban",
|
||||
@ -67,7 +64,6 @@ func TestDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Given a datasource", func() {
|
||||
|
||||
err := AddDataSource(&m.AddDataSourceCommand{
|
||||
OrgId: 10,
|
||||
Name: "nisse",
|
||||
@ -83,6 +79,89 @@ func TestDataAccess(t *testing.T) {
|
||||
|
||||
ds := query.Result[0]
|
||||
|
||||
Convey(" updated ", func() {
|
||||
cmd := &m.UpdateDataSourceCommand{
|
||||
Id: ds.Id,
|
||||
OrgId: 10,
|
||||
Name: "nisse",
|
||||
Type: m.DS_GRAPHITE,
|
||||
Access: m.DS_ACCESS_PROXY,
|
||||
Url: "http://test",
|
||||
Version: ds.Version,
|
||||
}
|
||||
|
||||
Convey("with same version as source", func() {
|
||||
err := UpdateDataSource(cmd)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("when someone else updated between read and update", func() {
|
||||
query := m.GetDataSourcesQuery{OrgId: 10}
|
||||
err = GetDataSources(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
ds := query.Result[0]
|
||||
intendedUpdate := &m.UpdateDataSourceCommand{
|
||||
Id: ds.Id,
|
||||
OrgId: 10,
|
||||
Name: "nisse",
|
||||
Type: m.DS_GRAPHITE,
|
||||
Access: m.DS_ACCESS_PROXY,
|
||||
Url: "http://test",
|
||||
Version: ds.Version,
|
||||
}
|
||||
|
||||
updateFromOtherUser := &m.UpdateDataSourceCommand{
|
||||
Id: ds.Id,
|
||||
OrgId: 10,
|
||||
Name: "nisse",
|
||||
Type: m.DS_GRAPHITE,
|
||||
Access: m.DS_ACCESS_PROXY,
|
||||
Url: "http://test",
|
||||
Version: ds.Version,
|
||||
}
|
||||
|
||||
err := UpdateDataSource(updateFromOtherUser)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = UpdateDataSource(intendedUpdate)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("updating datasource without version", func() {
|
||||
cmd := &m.UpdateDataSourceCommand{
|
||||
Id: ds.Id,
|
||||
OrgId: 10,
|
||||
Name: "nisse",
|
||||
Type: m.DS_GRAPHITE,
|
||||
Access: m.DS_ACCESS_PROXY,
|
||||
Url: "http://test",
|
||||
}
|
||||
|
||||
Convey("should not raise errors", func() {
|
||||
err := UpdateDataSource(cmd)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("updating datasource without higher version", func() {
|
||||
cmd := &m.UpdateDataSourceCommand{
|
||||
Id: ds.Id,
|
||||
OrgId: 10,
|
||||
Name: "nisse",
|
||||
Type: m.DS_GRAPHITE,
|
||||
Access: m.DS_ACCESS_PROXY,
|
||||
Url: "http://test",
|
||||
Version: 90000,
|
||||
}
|
||||
|
||||
Convey("should not raise errors", func() {
|
||||
err := UpdateDataSource(cmd)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Can delete datasource by id", func() {
|
||||
err := DeleteDataSourceById(&m.DeleteDataSourceByIdCommand{Id: ds.Id, OrgId: ds.OrgId})
|
||||
So(err, ShouldBeNil)
|
||||
@ -106,9 +185,6 @@ func TestDataAccess(t *testing.T) {
|
||||
GetDataSources(&query)
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
@ -120,4 +120,10 @@ func addDataSourceMigration(mg *Migrator) {
|
||||
{Name: "json_data", Type: DB_Text, Nullable: true},
|
||||
{Name: "secure_json_data", Type: DB_Text, Nullable: true},
|
||||
}))
|
||||
|
||||
const setVersionToOneWhereZero = `UPDATE data_source SET version = 1 WHERE version = 0`
|
||||
mg.AddMigration("Update initial version to 1", new(RawSqlMigration).
|
||||
Sqlite(setVersionToOneWhereZero).
|
||||
Postgres(setVersionToOneWhereZero).
|
||||
Mysql(setVersionToOneWhereZero))
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import store from 'app/core/store';
|
||||
import coreModule from 'app/core/core_module';
|
||||
|
||||
|
@ -1,27 +1,14 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'jquery',
|
||||
'../core_module',
|
||||
],
|
||||
function (angular, _, $, coreModule) {
|
||||
'use strict';
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import coreModule from '../core_module';
|
||||
|
||||
coreModule.default.controller('InspectCtrl', function($scope, $sanitize) {
|
||||
export class InspectCtrl {
|
||||
|
||||
/** @ngInject */
|
||||
constructor($scope, $sanitize) {
|
||||
var model = $scope.inspector;
|
||||
|
||||
function getParametersFromQueryString(queryString) {
|
||||
var result = [];
|
||||
var parameters = queryString.split("&");
|
||||
for (var i = 0; i < parameters.length; i++) {
|
||||
var keyValue = parameters[i].split("=");
|
||||
if (keyValue[1].length > 0) {
|
||||
result.push({ key: keyValue[0], value: window.unescape(keyValue[1]) });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
$scope.init = function () {
|
||||
$scope.editor = { index: 0 };
|
||||
|
||||
@ -57,7 +44,7 @@ function (angular, _, $, coreModule) {
|
||||
$scope.editor.index = 2;
|
||||
|
||||
if (_.isString(model.error.config.data)) {
|
||||
$scope.request_parameters = getParametersFromQueryString(model.error.config.data);
|
||||
$scope.request_parameters = this.getParametersFromQueryString(model.error.config.data);
|
||||
} else {
|
||||
$scope.request_parameters = _.map(model.error.config.data, function(value, key) {
|
||||
return {key: key, value: angular.toJson(value, true)};
|
||||
@ -65,7 +52,18 @@ function (angular, _, $, coreModule) {
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
getParametersFromQueryString(queryString) {
|
||||
var result = [];
|
||||
var parameters = queryString.split("&");
|
||||
for (var i = 0; i < parameters.length; i++) {
|
||||
var keyValue = parameters[i].split("=");
|
||||
if (keyValue[1].length > 0) {
|
||||
result.push({ key: keyValue[0], value: (<any>window).unescape(keyValue[1]) });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
coreModule.controller('InspectCtrl', InspectCtrl);
|
@ -7,14 +7,15 @@ export class DeltaCtrl {
|
||||
observer: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor($rootScope) {
|
||||
const waitForCompile = function(mutations) {
|
||||
constructor(private $rootScope) {
|
||||
|
||||
const waitForCompile = (mutations) => {
|
||||
if (mutations.length === 1) {
|
||||
this.$rootScope.appEvent('json-diff-ready');
|
||||
}
|
||||
};
|
||||
|
||||
this.observer = new MutationObserver(waitForCompile.bind(this));
|
||||
this.observer = new MutationObserver(waitForCompile);
|
||||
|
||||
const observerConfig = {
|
||||
attributes: true,
|
||||
|
@ -1,10 +1,9 @@
|
||||
define([
|
||||
'../core_module',
|
||||
],
|
||||
function (coreModule) {
|
||||
"use strict";
|
||||
import coreModule from '../core_module';
|
||||
|
||||
coreModule.default.controller('LoadDashboardCtrl', function($scope, $routeParams, dashboardLoaderSrv, backendSrv, $location) {
|
||||
export class LoadDashboardCtrl {
|
||||
|
||||
/** @ngInject */
|
||||
constructor($scope, $routeParams, dashboardLoaderSrv, backendSrv, $location) {
|
||||
$scope.appEvent("dashboard-fetch-start");
|
||||
|
||||
if (!$routeParams.slug) {
|
||||
@ -26,10 +25,13 @@ function (coreModule) {
|
||||
}
|
||||
$scope.initDashboard(result, $scope);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
export class NewDashboardCtrl {
|
||||
|
||||
coreModule.default.controller('NewDashboardCtrl', function($scope) {
|
||||
/** @ngInject */
|
||||
constructor($scope) {
|
||||
$scope.initDashboard({
|
||||
meta: { canStar: false, canShare: false, isNew: true },
|
||||
dashboard: {
|
||||
@ -38,12 +40,14 @@ function (coreModule) {
|
||||
{
|
||||
title: 'Dashboard Row',
|
||||
height: '350px',
|
||||
panels:[],
|
||||
panels: [],
|
||||
isNew: true,
|
||||
}
|
||||
]
|
||||
},
|
||||
}, $scope);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
coreModule.controller('LoadDashboardCtrl', LoadDashboardCtrl);
|
||||
coreModule.controller('NewDashboardCtrl', NewDashboardCtrl);
|
@ -1,41 +0,0 @@
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'app/core/core_module',
|
||||
'app/core/config',
|
||||
],
|
||||
function(angular, $, coreModule, config) {
|
||||
'use strict';
|
||||
|
||||
config = config.default;
|
||||
|
||||
coreModule.default.service('googleAnalyticsSrv', function($rootScope, $location) {
|
||||
|
||||
function gaInit() {
|
||||
$.getScript('https://www.google-analytics.com/analytics.js'); // jQuery shortcut
|
||||
var ga = window.ga = window.ga || function () { (ga.q = ga.q || []).push(arguments); }; ga.l = +new Date;
|
||||
ga('create', config.googleAnalyticsId, 'auto');
|
||||
return ga;
|
||||
}
|
||||
|
||||
this.init = function() {
|
||||
|
||||
$rootScope.$on('$viewContentLoaded', function() {
|
||||
var track = { page: $location.url() };
|
||||
|
||||
var ga = window.ga || gaInit();
|
||||
|
||||
ga('set', track);
|
||||
ga('send', 'pageview');
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
}).run(function(googleAnalyticsSrv) {
|
||||
|
||||
if (config.googleAnalyticsId) {
|
||||
googleAnalyticsSrv.init();
|
||||
}
|
||||
|
||||
});
|
||||
});
|
36
public/app/core/services/analytics.ts
Normal file
36
public/app/core/services/analytics.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import $ from 'jquery';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import config from 'app/core/config';
|
||||
|
||||
export class Analytics {
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $rootScope, private $location) {
|
||||
}
|
||||
|
||||
gaInit() {
|
||||
$.getScript('https://www.google-analytics.com/analytics.js'); // jQuery shortcut
|
||||
var ga = (<any>window).ga = (<any>window).ga || function () { (ga.q = ga.q || []).push(arguments); }; ga.l = +new Date;
|
||||
ga('create', (<any>config).googleAnalyticsId, 'auto');
|
||||
return ga;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.$rootScope.$on('$viewContentLoaded', () => {
|
||||
var track = { page: this.$location.url() };
|
||||
var ga = (<any>window).ga || this.gaInit();
|
||||
ga('set', track);
|
||||
ga('send', 'pageview');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** @ngInject */
|
||||
function startAnalytics(googleAnalyticsSrv) {
|
||||
if ((<any>config).googleAnalyticsId) {
|
||||
googleAnalyticsSrv.init();
|
||||
}
|
||||
}
|
||||
|
||||
coreModule.service('googleAnalyticsSrv', Analytics).run(startAnalytics);
|
||||
|
@ -1,33 +0,0 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'../core_module',
|
||||
],
|
||||
function (angular, _, coreModule) {
|
||||
'use strict';
|
||||
|
||||
coreModule.default.service('timer', function($timeout) {
|
||||
// This service really just tracks a list of $timeout promises to give us a
|
||||
// method for cancelling them all when we need to
|
||||
|
||||
var timers = [];
|
||||
|
||||
this.register = function(promise) {
|
||||
timers.push(promise);
|
||||
return promise;
|
||||
};
|
||||
|
||||
this.cancel = function(promise) {
|
||||
timers = _.without(timers,promise);
|
||||
$timeout.cancel(promise);
|
||||
};
|
||||
|
||||
this.cancelAll = function() {
|
||||
_.each(timers, function(t) {
|
||||
$timeout.cancel(t);
|
||||
});
|
||||
timers = [];
|
||||
};
|
||||
});
|
||||
|
||||
});
|
31
public/app/core/services/timer.ts
Normal file
31
public/app/core/services/timer.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import _ from 'lodash';
|
||||
import coreModule from 'app/core/core_module';
|
||||
|
||||
// This service really just tracks a list of $timeout promises to give us a
|
||||
// method for cancelling them all when we need to
|
||||
export class Timer {
|
||||
timers = [];
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $timeout) {
|
||||
}
|
||||
|
||||
register(promise) {
|
||||
this.timers.push(promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
cancel(promise) {
|
||||
this.timers = _.without(this.timers, promise);
|
||||
this.$timeout.cancel(promise);
|
||||
}
|
||||
|
||||
cancelAll() {
|
||||
_.each(this.timers, function (t) {
|
||||
this.$timeout.cancel(t);
|
||||
});
|
||||
this.timers = [];
|
||||
}
|
||||
}
|
||||
|
||||
coreModule.service('timer', Timer);
|
44
public/app/core/specs/store.jest.ts
Normal file
44
public/app/core/specs/store.jest.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import store from '../store';
|
||||
|
||||
Object.assign(window, {
|
||||
localStorage: {
|
||||
removeItem(key) {
|
||||
delete window.localStorage[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('store', () => {
|
||||
|
||||
it("should store", ()=> {
|
||||
store.set("key1", "123");
|
||||
expect(store.get("key1")).toBe("123");
|
||||
});
|
||||
|
||||
it("get key when undefined", ()=> {
|
||||
expect(store.get("key2")).toBe(undefined);
|
||||
});
|
||||
|
||||
it("check if key exixts", ()=> {
|
||||
store.set("key3", "123");
|
||||
expect(store.exists("key3")).toBe(true);
|
||||
});
|
||||
|
||||
it("get boolean when no key", ()=> {
|
||||
expect(store.getBool("key4", false)).toBe(false);
|
||||
});
|
||||
|
||||
it("get boolean", ()=> {
|
||||
store.set("key5", "true");
|
||||
expect(store.getBool("key5", false)).toBe(true);
|
||||
});
|
||||
|
||||
it("key should be deleted", ()=> {
|
||||
store.set("key6", "123");
|
||||
store.delete("key6");
|
||||
expect(store.exists("key6")).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
@ -1,26 +0,0 @@
|
||||
define([], function() {
|
||||
'use strict';
|
||||
|
||||
return {
|
||||
get: function(key) {
|
||||
return window.localStorage[key];
|
||||
},
|
||||
set: function(key, value) {
|
||||
window.localStorage[key] = value;
|
||||
},
|
||||
getBool: function(key, def) {
|
||||
if (def !== void 0 && !this.exists(key)) {
|
||||
return def;
|
||||
}
|
||||
return window.localStorage[key] === 'true';
|
||||
},
|
||||
exists: function(key) {
|
||||
return window.localStorage[key] !== void 0;
|
||||
},
|
||||
delete: function(key) {
|
||||
window.localStorage.removeItem(key);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
});
|
29
public/app/core/store.ts
Normal file
29
public/app/core/store.ts
Normal file
@ -0,0 +1,29 @@
|
||||
export class Store {
|
||||
|
||||
get(key) {
|
||||
return window.localStorage[key];
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
window.localStorage[key] = value;
|
||||
}
|
||||
|
||||
getBool(key, def) {
|
||||
if (def !== void 0 && !this.exists(key)) {
|
||||
return def;
|
||||
}
|
||||
return window.localStorage[key] === 'true';
|
||||
}
|
||||
|
||||
exists(key) {
|
||||
return window.localStorage[key] !== void 0;
|
||||
}
|
||||
|
||||
delete(key) {
|
||||
window.localStorage.removeItem(key);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const store = new Store();
|
||||
export default store;
|
@ -1,32 +0,0 @@
|
||||
// outline.js
|
||||
// based on http://www.paciellogroup.com/blog/2012/04/how-to-remove-css-outlines-in-an-accessible-manner/
|
||||
(function(d) {
|
||||
"use strict";
|
||||
|
||||
var style_element = d.createElement('STYLE'),
|
||||
dom_events = 'addEventListener' in d,
|
||||
add_event_listener = function(type, callback) {
|
||||
// Basic cross-browser event handling
|
||||
if(dom_events){
|
||||
d.addEventListener(type, callback);
|
||||
} else {
|
||||
d.attachEvent('on' + type, callback);
|
||||
}
|
||||
},
|
||||
set_css = function(css_text) {
|
||||
// Handle setting of <style> element contents in IE8
|
||||
!!style_element.styleSheet ? style_element.styleSheet.cssText = css_text : style_element.innerHTML = css_text;
|
||||
};
|
||||
|
||||
d.getElementsByTagName('HEAD')[0].appendChild(style_element);
|
||||
|
||||
// Using mousedown instead of mouseover, so that previously focused elements don't lose focus ring on mouse move
|
||||
add_event_listener('mousedown', function() {
|
||||
set_css(':focus{outline:0 !important}::-moz-focus-inner{border:0;}');
|
||||
});
|
||||
|
||||
add_event_listener('keydown', function() {
|
||||
set_css('');
|
||||
});
|
||||
|
||||
})(document);
|
34
public/app/core/utils/outline.ts
Normal file
34
public/app/core/utils/outline.ts
Normal file
@ -0,0 +1,34 @@
|
||||
// based on http://www.paciellogroup.com/blog/2012/04/how-to-remove-css-outlines-in-an-accessible-manner/
|
||||
function outlineFixer() {
|
||||
let d: any = document;
|
||||
|
||||
var style_element = d.createElement('STYLE');
|
||||
var dom_events = 'addEventListener' in d;
|
||||
|
||||
var add_event_listener = function (type, callback) {
|
||||
// Basic cross-browser event handling
|
||||
if (dom_events) {
|
||||
d.addEventListener(type, callback);
|
||||
} else {
|
||||
d.attachEvent('on' + type, callback);
|
||||
}
|
||||
};
|
||||
|
||||
var set_css = function (css_text) {
|
||||
// Handle setting of <style> element contents in IE8
|
||||
!!style_element.styleSheet ? style_element.styleSheet.cssText = css_text : style_element.innerHTML = css_text;
|
||||
};
|
||||
|
||||
d.getElementsByTagName('HEAD')[0].appendChild(style_element);
|
||||
|
||||
// Using mousedown instead of mouseover, so that previously focused elements don't lose focus ring on mouse move
|
||||
add_event_listener('mousedown', function () {
|
||||
set_css(':focus{outline:0 !important}::-moz-focus-inner{border:0;}');
|
||||
});
|
||||
|
||||
add_event_listener('keydown', function () {
|
||||
set_css('');
|
||||
});
|
||||
}
|
||||
|
||||
outlineFixer();
|
@ -31,7 +31,6 @@ export class DashboardModel {
|
||||
revision: number;
|
||||
links: any;
|
||||
gnetId: any;
|
||||
editMode: boolean;
|
||||
folderId: number;
|
||||
panels: PanelModel[];
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import store from 'app/core/store';
|
||||
import _ from 'lodash';
|
||||
import config from 'app/core/config';
|
||||
|
@ -73,7 +73,6 @@ function(angular, _) {
|
||||
dash.time = 0;
|
||||
dash.refresh = 0;
|
||||
dash.schemaVersion = 0;
|
||||
dash.editMode = false;
|
||||
|
||||
// filter row and panels properties that should be ignored
|
||||
dash.rows = _.filter(dash.rows, function(row) {
|
||||
|
@ -147,7 +147,6 @@ function (angular, _, $, config) {
|
||||
ctrl.fullscreen = false;
|
||||
|
||||
this.dashboard.setViewMode(ctrl.panel, false, false);
|
||||
|
||||
this.$scope.appEvent('panel-fullscreen-exit', {panelId: ctrl.panel.id});
|
||||
|
||||
if (!render) { return false;}
|
||||
|
@ -158,13 +158,15 @@ export class DataSourceEditCtrl {
|
||||
}
|
||||
|
||||
if (this.current.id) {
|
||||
return this.backendSrv.put('/api/datasources/' + this.current.id, this.current).then(() => {
|
||||
return this.backendSrv.put('/api/datasources/' + this.current.id, this.current).then((result) => {
|
||||
this.current = result.datasource;
|
||||
this.updateFrontendSettings().then(() => {
|
||||
this.testDatasource();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return this.backendSrv.post('/api/datasources', this.current).then(result => {
|
||||
this.current = result.datasource;
|
||||
this.updateFrontendSettings();
|
||||
|
||||
datasourceCreated = true;
|
||||
|
@ -64,16 +64,21 @@ exposeToPlugin('lodash', _);
|
||||
exposeToPlugin('moment', moment);
|
||||
exposeToPlugin('jquery', jquery);
|
||||
exposeToPlugin('angular', angular);
|
||||
exposeToPlugin('d3', d3);
|
||||
exposeToPlugin('rxjs/Subject', Subject);
|
||||
exposeToPlugin('rxjs/Observable', Observable);
|
||||
exposeToPlugin('d3', d3);
|
||||
|
||||
// backward compatible path
|
||||
exposeToPlugin('vendor/npm/rxjs/Rx', {
|
||||
Subject: Subject,
|
||||
Observable: Observable
|
||||
});
|
||||
|
||||
exposeToPlugin('app/features/dashboard/impression_store', {
|
||||
impressions: impressions,
|
||||
__esModule: true
|
||||
});
|
||||
|
||||
|
||||
exposeToPlugin('app/plugins/sdk', sdk);
|
||||
exposeToPlugin('app/core/utils/datemath', datemath);
|
||||
exposeToPlugin('app/core/utils/file_export', fileExport);
|
||||
|
@ -5,6 +5,7 @@ import kbn from 'app/core/utils/kbn';
|
||||
import {Variable, assignModelProperties, variableTypes} from './variable';
|
||||
|
||||
export class IntervalVariable implements Variable {
|
||||
name: string;
|
||||
auto_count: number;
|
||||
auto_min: number;
|
||||
options: any;
|
||||
@ -50,10 +51,12 @@ export class IntervalVariable implements Variable {
|
||||
|
||||
// add auto option if missing
|
||||
if (this.options.length && this.options[0].text !== 'auto') {
|
||||
this.options.unshift({ text: 'auto', value: '$__auto_interval' });
|
||||
this.options.unshift({ text: 'auto', value: '$__auto_interval_' + this.name });
|
||||
}
|
||||
|
||||
var res = kbn.calculateInterval(this.timeSrv.timeRange(), this.auto_count, this.auto_min);
|
||||
this.templateSrv.setGrafanaVariable('$__auto_interval_' + this.name, res.interval);
|
||||
// for backward compatibility, to be removed eventually
|
||||
this.templateSrv.setGrafanaVariable('$__auto_interval', res.interval);
|
||||
}
|
||||
|
||||
|
@ -74,7 +74,7 @@
|
||||
<div class="grafana-info-box col-lg-8">
|
||||
<h5>What does templating do?</h5>
|
||||
<p>Templating allows for more interactive and dynamic dashboards. Instead of hard-coding things like server, application
|
||||
and sensor name in you metric queries you can use variables in their place. Variables are shown as dropdown select boxes at the top of
|
||||
and sensor name in your metric queries you can use variables in their place. Variables are shown as dropdown select boxes at the top of
|
||||
the dashboard. These dropdowns make it easy to change the data being displayed in your dashboard.
|
||||
<br>
|
||||
<br>
|
||||
|
@ -254,9 +254,9 @@ describe('templateSrv', function() {
|
||||
beforeEach(function() {
|
||||
initTemplateSrv([
|
||||
{type: 'query', name: 'server', current: { value: '{asd,asd2}', text: 'All' } },
|
||||
{type: 'interval', name: 'period', current: { value: '$__auto_interval', text: 'auto' } }
|
||||
{type: 'interval', name: 'period', current: { value: '$__auto_interval_interval', text: 'auto' } }
|
||||
]);
|
||||
_templateSrv.setGrafanaVariable('$__auto_interval', '13m');
|
||||
_templateSrv.setGrafanaVariable('$__auto_interval_interval', '13m');
|
||||
_templateSrv.updateTemplateData();
|
||||
});
|
||||
|
||||
|
@ -84,11 +84,19 @@ describe('VariableSrv', function() {
|
||||
it('should update options array', function() {
|
||||
expect(scenario.variable.options.length).to.be(5);
|
||||
expect(scenario.variable.options[0].text).to.be('auto');
|
||||
expect(scenario.variable.options[0].value).to.be('$__auto_interval');
|
||||
expect(scenario.variable.options[0].value).to.be('$__auto_interval_test');
|
||||
});
|
||||
|
||||
it('should set $__auto_interval_test', function() {
|
||||
var call = ctx.templateSrv.setGrafanaVariable.firstCall;
|
||||
expect(call.args[0]).to.be('$__auto_interval_test');
|
||||
expect(call.args[1]).to.be('12h');
|
||||
});
|
||||
|
||||
// updateAutoValue() gets called twice: once directly once via VariableSrv.validateVariableSelectionState()
|
||||
// So use lastCall instead of a specific call number
|
||||
it('should set $__auto_interval', function() {
|
||||
var call = ctx.templateSrv.setGrafanaVariable.getCall(0);
|
||||
var call = ctx.templateSrv.setGrafanaVariable.lastCall;
|
||||
expect(call.args[0]).to.be('$__auto_interval');
|
||||
expect(call.args[1]).to.be('12h');
|
||||
});
|
||||
@ -395,4 +403,68 @@ describe('VariableSrv', function() {
|
||||
expect(scenario.variable.options[1].value).to.be('hop');
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiple interval variables with auto', function() {
|
||||
var variable1, variable2;
|
||||
|
||||
beforeEach(function() {
|
||||
var range = {
|
||||
from: moment(new Date()).subtract(7, 'days').toDate(),
|
||||
to: new Date()
|
||||
};
|
||||
ctx.timeSrv.timeRange = sinon.stub().returns(range);
|
||||
ctx.templateSrv.setGrafanaVariable = sinon.spy();
|
||||
|
||||
var variableModel1 = {type: 'interval', query: '1s,2h,5h,1d', name: 'variable1', auto: true, auto_count: 10 };
|
||||
variable1 = ctx.variableSrv.createVariableFromModel(variableModel1);
|
||||
ctx.variableSrv.addVariable(variable1);
|
||||
|
||||
var variableModel2 = {type: 'interval', query: '1s,2h,5h', name: 'variable2', auto: true, auto_count: 1000 };
|
||||
variable2 = ctx.variableSrv.createVariableFromModel(variableModel2);
|
||||
ctx.variableSrv.addVariable(variable2);
|
||||
|
||||
ctx.variableSrv.updateOptions(variable1);
|
||||
ctx.variableSrv.updateOptions(variable2);
|
||||
ctx.$rootScope.$digest();
|
||||
});
|
||||
|
||||
it('should update options array', function() {
|
||||
expect(variable1.options.length).to.be(5);
|
||||
expect(variable1.options[0].text).to.be('auto');
|
||||
expect(variable1.options[0].value).to.be('$__auto_interval_variable1');
|
||||
expect(variable2.options.length).to.be(4);
|
||||
expect(variable2.options[0].text).to.be('auto');
|
||||
expect(variable2.options[0].value).to.be('$__auto_interval_variable2');
|
||||
});
|
||||
|
||||
it('should correctly set $__auto_interval_variableX', function() {
|
||||
var variable1Set, variable2Set, legacySet, unknownSet = false;
|
||||
// updateAutoValue() gets called repeatedly: once directly once via VariableSrv.validateVariableSelectionState()
|
||||
// So check that all calls are valid rather than expect a specific number and/or ordering of calls
|
||||
for (var i = 0; i < ctx.templateSrv.setGrafanaVariable.callCount; i++) {
|
||||
var call = ctx.templateSrv.setGrafanaVariable.getCall(i);
|
||||
switch (call.args[0]) {
|
||||
case '$__auto_interval_variable1':
|
||||
expect(call.args[1]).to.be('12h');
|
||||
variable1Set = true;
|
||||
break;
|
||||
case '$__auto_interval_variable2':
|
||||
expect(call.args[1]).to.be('10m');
|
||||
variable2Set = true;
|
||||
break;
|
||||
case '$__auto_interval':
|
||||
expect(call.args[1]).to.match(/^(12h|10m)$/);
|
||||
legacySet = true;
|
||||
break;
|
||||
default:
|
||||
unknownSet = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
expect(variable1Set).to.be.equal(true);
|
||||
expect(variable2Set).to.be.equal(true);
|
||||
expect(legacySet).to.be.equal(true);
|
||||
expect(unknownSet).to.be.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -17,7 +17,7 @@ export class MysqlDatasource {
|
||||
|
||||
interpolateVariable(value) {
|
||||
if (typeof value === 'string') {
|
||||
return '\'' + value + '\'';
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
|
@ -196,8 +196,8 @@ describe('MySQLDatasource', function() {
|
||||
|
||||
describe('When interpolating variables', () => {
|
||||
describe('and value is a string', () => {
|
||||
it('should return a quoted value', () => {
|
||||
expect(ctx.ds.interpolateVariable('abc')).to.eql('\'abc\'');
|
||||
it('should return an unquoted value', () => {
|
||||
expect(ctx.ds.interpolateVariable('abc')).to.eql('abc');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -17,11 +17,11 @@ export class PostgresDatasource {
|
||||
|
||||
interpolateVariable(value) {
|
||||
if (typeof value === 'string') {
|
||||
return '\'' + value + '\'';
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return value.toString();
|
||||
return value;
|
||||
}
|
||||
|
||||
var quotedValues = _.map(value, function(val) {
|
||||
|
@ -193,4 +193,24 @@ describe('PostgreSQLDatasource', function() {
|
||||
expect(results[0].value).to.be('same');
|
||||
});
|
||||
});
|
||||
|
||||
describe('When interpolating variables', () => {
|
||||
describe('and value is a string', () => {
|
||||
it('should return an unquoted value', () => {
|
||||
expect(ctx.ds.interpolateVariable('abc')).to.eql('abc');
|
||||
});
|
||||
});
|
||||
|
||||
describe('and value is a number', () => {
|
||||
it('should return an unquoted value', () => {
|
||||
expect(ctx.ds.interpolateVariable(1000)).to.eql(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and value is an array of strings', () => {
|
||||
it('should return comma separated quoted values', () => {
|
||||
expect(ctx.ds.interpolateVariable(['a', 'b', 'c'])).to.eql('\'a\',\'b\',\'c\'');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,3 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
@ -122,7 +120,7 @@ export class PrometheusDatasource {
|
||||
} else {
|
||||
for (let metricData of response.data.data.result) {
|
||||
if (response.data.data.resultType === 'matrix') {
|
||||
result.push(self.transformMetricData(metricData, activeTargets[index], start, end));
|
||||
result.push(self.transformMetricData(metricData, activeTargets[index], start, end, queries[index].step));
|
||||
} else if (response.data.data.resultType === 'vector') {
|
||||
result.push(self.transformInstantMetricData(metricData, activeTargets[index]));
|
||||
}
|
||||
@ -144,7 +142,6 @@ export class PrometheusDatasource {
|
||||
var intervalFactor = target.intervalFactor || 1;
|
||||
// Adjust the interval to take into account any specified minimum and interval factor plus Prometheus limits
|
||||
var adjustedInterval = this.adjustInterval(interval, minInterval, range, intervalFactor);
|
||||
|
||||
var scopedVars = options.scopedVars;
|
||||
// If the interval was adjusted, make a shallow copy of scopedVars with updated interval vars
|
||||
if (interval !== adjustedInterval) {
|
||||
@ -154,7 +151,7 @@ export class PrometheusDatasource {
|
||||
"__interval_ms": {text: interval * 1000, value: interval * 1000},
|
||||
});
|
||||
}
|
||||
target.step = query.step = interval;
|
||||
query.step = interval;
|
||||
|
||||
// Only replace vars in expression after having (possibly) updated interval vars
|
||||
query.expr = this.templateSrv.replace(target.expr, scopedVars, this.interpolateQueryExpr);
|
||||
@ -272,13 +269,13 @@ export class PrometheusDatasource {
|
||||
});
|
||||
}
|
||||
|
||||
transformMetricData(md, options, start, end) {
|
||||
transformMetricData(md, options, start, end, step) {
|
||||
var dps = [],
|
||||
metricLabel = null;
|
||||
|
||||
metricLabel = this.createMetricLabel(md.metric, options);
|
||||
|
||||
var stepMs = parseInt(options.step) * 1000;
|
||||
var stepMs = step * 1000;
|
||||
var baseTimestamp = start * 1000;
|
||||
for (let value of md.values) {
|
||||
var dp_value = parseFloat(value[1]);
|
||||
|
@ -4,7 +4,8 @@
|
||||
"id": "prometheus",
|
||||
|
||||
"includes": [
|
||||
{"type": "dashboard", "name": "Prometheus Stats", "path": "dashboards/prometheus_stats.json"}
|
||||
{"type": "dashboard", "name": "Prometheus Stats", "path": "dashboards/prometheus_stats.json"},
|
||||
{"type": "dashboard", "name": "Grafana Stats", "path": "dashboards/grafana_stats.json"}
|
||||
],
|
||||
|
||||
"metrics": true,
|
||||
|
@ -66,7 +66,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
thresholds: '',
|
||||
colorBackground: false,
|
||||
colorValue: false,
|
||||
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
|
||||
colors: ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"],
|
||||
sparkline: {
|
||||
show: false,
|
||||
full: false,
|
||||
|
7
public/vendor/flot/jquery.flot.js
vendored
7
public/vendor/flot/jquery.flot.js
vendored
@ -2962,8 +2962,11 @@ Licensed under the MIT license.
|
||||
}
|
||||
|
||||
function onClick(e) {
|
||||
triggerClickHoverEvent("plotclick", e,
|
||||
function (s) { return s["clickable"] != false; });
|
||||
if (plot.isSelecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerClickHoverEvent("plotclick", e, function (s) { return s["clickable"] != false; });
|
||||
}
|
||||
|
||||
// trigger click or hover event (they send the same parameters
|
||||
|
5
public/vendor/flot/jquery.flot.selection.js
vendored
5
public/vendor/flot/jquery.flot.selection.js
vendored
@ -152,6 +152,10 @@ The plugin allso adds the following methods to the plot object:
|
||||
plot.getPlaceholder().trigger("plotselecting", [ null ]);
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
plot.isSelecting = false;
|
||||
}, 10);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -218,6 +222,7 @@ The plugin allso adds the following methods to the plot object:
|
||||
|
||||
setSelectionPos(selection.second, pos);
|
||||
if (selectionIsSane()) {
|
||||
plot.isSelecting = true;
|
||||
selection.show = true;
|
||||
plot.triggerRedrawOverlay();
|
||||
}
|
||||
|
3
vendor/github.com/streadway/amqp/.gitignore
generated
vendored
3
vendor/github.com/streadway/amqp/.gitignore
generated
vendored
@ -1,3 +0,0 @@
|
||||
spec/spec
|
||||
examples/simple-consumer/simple-consumer
|
||||
examples/simple-producer/simple-producer
|
13
vendor/github.com/streadway/amqp/.travis.yml
generated
vendored
13
vendor/github.com/streadway/amqp/.travis.yml
generated
vendored
@ -1,13 +0,0 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.1
|
||||
- tip
|
||||
|
||||
services:
|
||||
- rabbitmq
|
||||
|
||||
env:
|
||||
- AMQP_URL=amqp://guest:guest@127.0.0.1:5672/ GOMAXPROCS=2
|
||||
|
||||
script: go test -tags integration ./...
|
23
vendor/github.com/streadway/amqp/LICENSE
generated
vendored
23
vendor/github.com/streadway/amqp/LICENSE
generated
vendored
@ -1,23 +0,0 @@
|
||||
Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
||||
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.
|
||||
|
||||
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 HOLDER 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.
|
76
vendor/github.com/streadway/amqp/README.md
generated
vendored
76
vendor/github.com/streadway/amqp/README.md
generated
vendored
@ -1,76 +0,0 @@
|
||||
# AMQP
|
||||
|
||||
AMQP 0.9.1 client with RabbitMQ extensions in Go.
|
||||
|
||||
# Status
|
||||
|
||||
*Beta*
|
||||
|
||||
[](http://travis-ci.org/streadway/amqp)
|
||||
|
||||
API changes unlikely and will be discussed on [Github
|
||||
issues](https://github.com/streadway/amqp/issues) along with any bugs or
|
||||
enhancements.
|
||||
|
||||
# Goals
|
||||
|
||||
Provide an functional interface that closely represents the AMQP 0.9.1 model
|
||||
targeted to RabbitMQ as a server. This includes the minimum necessary to
|
||||
interact the semantics of the protocol.
|
||||
|
||||
# Non-goals
|
||||
|
||||
Things not intended to be supported.
|
||||
|
||||
* Auto reconnect and re-synchronization of client and server topologies.
|
||||
* Reconnection would require understanding the error paths when the
|
||||
topology cannot be declared on reconnect. This would require a new set
|
||||
of types and code paths that are best suited at the call-site of this
|
||||
package. AMQP has a dynamic topology that needs all peers to agree. If
|
||||
this doesn't happen, the behavior is undefined. Instead of producing a
|
||||
possible interface with undefined behavior, this package is designed to
|
||||
be simple for the caller to implement the necessary connection-time
|
||||
topology declaration so that reconnection is trivial and encapsulated in
|
||||
the caller's application code.
|
||||
* AMQP Protocol negotiation for forward or backward compatibility.
|
||||
* 0.9.1 is stable and widely deployed. Versions 0.10 and 1.0 are divergent
|
||||
specifications that change the semantics and wire format of the protocol.
|
||||
We will accept patches for other protocol support but have no plans for
|
||||
implementation ourselves.
|
||||
* Anything other than PLAIN and EXTERNAL authentication mechanisms.
|
||||
* Keeping the mechanisms interface modular makes it possible to extend
|
||||
outside of this package. If other mechanisms prove to be popular, then
|
||||
we would accept patches to include them in this pacakge.
|
||||
|
||||
# Usage
|
||||
|
||||
See the 'examples' subdirectory for simple producers and consumers executables.
|
||||
If you have a use-case in mind which isn't well-represented by the examples,
|
||||
please file an issue.
|
||||
|
||||
# Documentation
|
||||
|
||||
Use [Godoc documentation](http://godoc.org/github.com/streadway/amqp) for
|
||||
reference and usage.
|
||||
|
||||
[RabbitMQ tutorials in
|
||||
Go](https://github.com/rabbitmq/rabbitmq-tutorials/tree/master/go) are also
|
||||
available.
|
||||
|
||||
# Contributing
|
||||
|
||||
Pull requests are very much welcomed. Create your pull request on a non-master
|
||||
branch, make sure a test or example is included that covers your change and
|
||||
your commits represent coherent changes that include a reason for the change.
|
||||
|
||||
To run the integration tests, make sure you have RabbitMQ running on any host,
|
||||
export the environment variable `AMQP_URL=amqp://host/` and run `go test -tags
|
||||
integration`. TravisCI will also run the integration tests.
|
||||
|
||||
Thanks to the [community of contributors](https://github.com/streadway/amqp/graphs/contributors).
|
||||
|
||||
# License
|
||||
|
||||
BSD 2 clause - see LICENSE for more details.
|
||||
|
||||
|
106
vendor/github.com/streadway/amqp/allocator.go
generated
vendored
106
vendor/github.com/streadway/amqp/allocator.go
generated
vendored
@ -1,106 +0,0 @@
|
||||
package amqp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
const (
|
||||
free = 0
|
||||
allocated = 1
|
||||
)
|
||||
|
||||
// allocator maintains a bitset of allocated numbers.
|
||||
type allocator struct {
|
||||
pool *big.Int
|
||||
last int
|
||||
low int
|
||||
high int
|
||||
}
|
||||
|
||||
// NewAllocator reserves and frees integers out of a range between low and
|
||||
// high.
|
||||
//
|
||||
// O(N) worst case space used, where N is maximum allocated, divided by
|
||||
// sizeof(big.Word)
|
||||
func newAllocator(low, high int) *allocator {
|
||||
return &allocator{
|
||||
pool: big.NewInt(0),
|
||||
last: low,
|
||||
low: low,
|
||||
high: high,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string describing the contents of the allocator like
|
||||
// "allocator[low..high] reserved..until"
|
||||
//
|
||||
// O(N) where N is high-low
|
||||
func (a allocator) String() string {
|
||||
b := &bytes.Buffer{}
|
||||
fmt.Fprintf(b, "allocator[%d..%d]", a.low, a.high)
|
||||
|
||||
for low := a.low; low <= a.high; low++ {
|
||||
high := low
|
||||
for a.reserved(high) && high <= a.high {
|
||||
high++
|
||||
}
|
||||
|
||||
if high > low+1 {
|
||||
fmt.Fprintf(b, " %d..%d", low, high-1)
|
||||
} else if high > low {
|
||||
fmt.Fprintf(b, " %d", high-1)
|
||||
}
|
||||
|
||||
low = high
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Next reserves and returns the next available number out of the range between
|
||||
// low and high. If no number is available, false is returned.
|
||||
//
|
||||
// O(N) worst case runtime where N is allocated, but usually O(1) due to a
|
||||
// rolling index into the oldest allocation.
|
||||
func (a *allocator) next() (int, bool) {
|
||||
wrapped := a.last
|
||||
|
||||
// Find trailing bit
|
||||
for ; a.last <= a.high; a.last++ {
|
||||
if a.reserve(a.last) {
|
||||
return a.last, true
|
||||
}
|
||||
}
|
||||
|
||||
// Find preceeding free'd pool
|
||||
a.last = a.low
|
||||
|
||||
for ; a.last < wrapped; a.last++ {
|
||||
if a.reserve(a.last) {
|
||||
return a.last, true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// reserve claims the bit if it is not already claimed, returning true if
|
||||
// succesfully claimed.
|
||||
func (a *allocator) reserve(n int) bool {
|
||||
if a.reserved(n) {
|
||||
return false
|
||||
}
|
||||
a.pool.SetBit(a.pool, n-a.low, allocated)
|
||||
return true
|
||||
}
|
||||
|
||||
// reserved returns true if the integer has been allocated
|
||||
func (a *allocator) reserved(n int) bool {
|
||||
return a.pool.Bit(n-a.low) == allocated
|
||||
}
|
||||
|
||||
// release frees the use of the number for another allocation
|
||||
func (a *allocator) release(n int) {
|
||||
a.pool.SetBit(a.pool, n-a.low, free)
|
||||
}
|
90
vendor/github.com/streadway/amqp/allocator_test.go
generated
vendored
90
vendor/github.com/streadway/amqp/allocator_test.go
generated
vendored
@ -1,90 +0,0 @@
|
||||
package amqp
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAllocatorFirstShouldBeTheLow(t *testing.T) {
|
||||
n, ok := newAllocator(1, 2).next()
|
||||
if !ok {
|
||||
t.Fatalf("expected to allocate between 1 and 2")
|
||||
}
|
||||
|
||||
if want, got := 1, n; want != got {
|
||||
t.Fatalf("expected to first allocation to be 1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocatorShouldBeBoundByHigh(t *testing.T) {
|
||||
a := newAllocator(1, 2)
|
||||
|
||||
if n, ok := a.next(); n != 1 || !ok {
|
||||
t.Fatalf("expected to allocate between 1 and 2, got %d, %v", n, ok)
|
||||
}
|
||||
if n, ok := a.next(); n != 2 || !ok {
|
||||
t.Fatalf("expected to allocate between 1 and 2, got %d, %v", n, ok)
|
||||
}
|
||||
if _, ok := a.next(); ok {
|
||||
t.Fatalf("expected not to allocate outside of 1 and 2")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocatorStringShouldIncludeAllocatedRanges(t *testing.T) {
|
||||
a := newAllocator(1, 10)
|
||||
a.reserve(1)
|
||||
a.reserve(2)
|
||||
a.reserve(3)
|
||||
a.reserve(5)
|
||||
a.reserve(6)
|
||||
a.reserve(8)
|
||||
a.reserve(10)
|
||||
|
||||
if want, got := "allocator[1..10] 1..3 5..6 8 10", a.String(); want != got {
|
||||
t.Fatalf("expected String of %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocatorShouldReuseReleased(t *testing.T) {
|
||||
a := newAllocator(1, 2)
|
||||
|
||||
first, _ := a.next()
|
||||
if want, got := 1, first; want != got {
|
||||
t.Fatalf("expected allocation to be %d, got: %d", want, got)
|
||||
}
|
||||
|
||||
second, _ := a.next()
|
||||
if want, got := 2, second; want != got {
|
||||
t.Fatalf("expected allocation to be %d, got: %d", want, got)
|
||||
}
|
||||
|
||||
a.release(first)
|
||||
|
||||
third, _ := a.next()
|
||||
if want, got := first, third; want != got {
|
||||
t.Fatalf("expected third allocation to be %d, got: %d", want, got)
|
||||
}
|
||||
|
||||
_, ok := a.next()
|
||||
if want, got := false, ok; want != got {
|
||||
t.Fatalf("expected fourth allocation to saturate the pool")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocatorReleasesKeepUpWithAllocationsForAllSizes(t *testing.T) {
|
||||
const runs = 5
|
||||
const max = 13
|
||||
|
||||
for lim := 1; lim < 2<<max; lim <<= 1 {
|
||||
a := newAllocator(0, lim)
|
||||
|
||||
for i := 0; i < runs*lim; i++ {
|
||||
if i >= lim { // fills the allocator
|
||||
a.release(int(rand.Int63n(int64(lim))))
|
||||
}
|
||||
if _, ok := a.next(); !ok {
|
||||
t.Fatalf("expected %d runs of random release of size %d not to fail on allocation %d", runs, lim, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
vendor/github.com/streadway/amqp/auth.go
generated
vendored
44
vendor/github.com/streadway/amqp/auth.go
generated
vendored
@ -1,44 +0,0 @@
|
||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Source code and contact info at http://github.com/streadway/amqp
|
||||
|
||||
package amqp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Authentication interface provides a means for different SASL authentication
|
||||
// mechanisms to be used during connection tuning.
|
||||
type Authentication interface {
|
||||
Mechanism() string
|
||||
Response() string
|
||||
}
|
||||
|
||||
// PlainAuth is a similar to Basic Auth in HTTP.
|
||||
type PlainAuth struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (me *PlainAuth) Mechanism() string {
|
||||
return "PLAIN"
|
||||
}
|
||||
|
||||
func (me *PlainAuth) Response() string {
|
||||
return fmt.Sprintf("\000%s\000%s", me.Username, me.Password)
|
||||
}
|
||||
|
||||
// Finds the first mechanism preferred by the client that the server supports.
|
||||
func pickSASLMechanism(client []Authentication, serverMechanisms []string) (auth Authentication, ok bool) {
|
||||
for _, auth = range client {
|
||||
for _, mech := range serverMechanisms {
|
||||
if auth.Mechanism() == mech {
|
||||
return auth, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
159
vendor/github.com/streadway/amqp/certs.sh
generated
vendored
159
vendor/github.com/streadway/amqp/certs.sh
generated
vendored
@ -1,159 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Creates the CA, server and client certs to be used by tls_test.go
|
||||
# http://www.rabbitmq.com/ssl.html
|
||||
#
|
||||
# Copy stdout into the const section of tls_test.go or use for RabbitMQ
|
||||
#
|
||||
root=$PWD/certs
|
||||
|
||||
if [ -f $root/ca/serial ]; then
|
||||
echo >&2 "Previous installation found"
|
||||
echo >&2 "Remove $root/ca and rerun to overwrite"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p $root/ca/private
|
||||
mkdir -p $root/ca/certs
|
||||
mkdir -p $root/server
|
||||
mkdir -p $root/client
|
||||
|
||||
cd $root/ca
|
||||
|
||||
chmod 700 private
|
||||
touch index.txt
|
||||
echo 'unique_subject = no' > index.txt.attr
|
||||
echo '01' > serial
|
||||
echo >openssl.cnf '
|
||||
[ ca ]
|
||||
default_ca = testca
|
||||
|
||||
[ testca ]
|
||||
dir = .
|
||||
certificate = $dir/cacert.pem
|
||||
database = $dir/index.txt
|
||||
new_certs_dir = $dir/certs
|
||||
private_key = $dir/private/cakey.pem
|
||||
serial = $dir/serial
|
||||
|
||||
default_crl_days = 7
|
||||
default_days = 3650
|
||||
default_md = sha1
|
||||
|
||||
policy = testca_policy
|
||||
x509_extensions = certificate_extensions
|
||||
|
||||
[ testca_policy ]
|
||||
commonName = supplied
|
||||
stateOrProvinceName = optional
|
||||
countryName = optional
|
||||
emailAddress = optional
|
||||
organizationName = optional
|
||||
organizationalUnitName = optional
|
||||
|
||||
[ certificate_extensions ]
|
||||
basicConstraints = CA:false
|
||||
|
||||
[ req ]
|
||||
default_bits = 2048
|
||||
default_keyfile = ./private/cakey.pem
|
||||
default_md = sha1
|
||||
prompt = yes
|
||||
distinguished_name = root_ca_distinguished_name
|
||||
x509_extensions = root_ca_extensions
|
||||
|
||||
[ root_ca_distinguished_name ]
|
||||
commonName = hostname
|
||||
|
||||
[ root_ca_extensions ]
|
||||
basicConstraints = CA:true
|
||||
keyUsage = keyCertSign, cRLSign
|
||||
|
||||
[ client_ca_extensions ]
|
||||
basicConstraints = CA:false
|
||||
keyUsage = digitalSignature
|
||||
extendedKeyUsage = 1.3.6.1.5.5.7.3.2
|
||||
|
||||
[ server_ca_extensions ]
|
||||
basicConstraints = CA:false
|
||||
keyUsage = keyEncipherment
|
||||
extendedKeyUsage = 1.3.6.1.5.5.7.3.1
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[ alt_names ]
|
||||
IP.1 = 127.0.0.1
|
||||
'
|
||||
|
||||
openssl req \
|
||||
-x509 \
|
||||
-nodes \
|
||||
-config openssl.cnf \
|
||||
-newkey rsa:2048 \
|
||||
-days 3650 \
|
||||
-subj "/CN=MyTestCA/" \
|
||||
-out cacert.pem \
|
||||
-outform PEM
|
||||
|
||||
openssl x509 \
|
||||
-in cacert.pem \
|
||||
-out cacert.cer \
|
||||
-outform DER
|
||||
|
||||
openssl genrsa -out $root/server/key.pem 2048
|
||||
openssl genrsa -out $root/client/key.pem 2048
|
||||
|
||||
openssl req \
|
||||
-new \
|
||||
-nodes \
|
||||
-config openssl.cnf \
|
||||
-subj "/CN=127.0.0.1/O=server/" \
|
||||
-key $root/server/key.pem \
|
||||
-out $root/server/req.pem \
|
||||
-outform PEM
|
||||
|
||||
openssl req \
|
||||
-new \
|
||||
-nodes \
|
||||
-config openssl.cnf \
|
||||
-subj "/CN=127.0.0.1/O=client/" \
|
||||
-key $root/client/key.pem \
|
||||
-out $root/client/req.pem \
|
||||
-outform PEM
|
||||
|
||||
openssl ca \
|
||||
-config openssl.cnf \
|
||||
-in $root/server/req.pem \
|
||||
-out $root/server/cert.pem \
|
||||
-notext \
|
||||
-batch \
|
||||
-extensions server_ca_extensions
|
||||
|
||||
openssl ca \
|
||||
-config openssl.cnf \
|
||||
-in $root/client/req.pem \
|
||||
-out $root/client/cert.pem \
|
||||
-notext \
|
||||
-batch \
|
||||
-extensions client_ca_extensions
|
||||
|
||||
cat <<-END
|
||||
const caCert = \`
|
||||
`cat $root/ca/cacert.pem`
|
||||
\`
|
||||
|
||||
const serverCert = \`
|
||||
`cat $root/server/cert.pem`
|
||||
\`
|
||||
|
||||
const serverKey = \`
|
||||
`cat $root/server/key.pem`
|
||||
\`
|
||||
|
||||
const clientCert = \`
|
||||
`cat $root/client/cert.pem`
|
||||
\`
|
||||
|
||||
const clientKey = \`
|
||||
`cat $root/client/key.pem`
|
||||
\`
|
||||
END
|
1589
vendor/github.com/streadway/amqp/channel.go
generated
vendored
1589
vendor/github.com/streadway/amqp/channel.go
generated
vendored
File diff suppressed because it is too large
Load Diff
559
vendor/github.com/streadway/amqp/client_test.go
generated
vendored
559
vendor/github.com/streadway/amqp/client_test.go
generated
vendored
@ -1,559 +0,0 @@
|
||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Source code and contact info at http://github.com/streadway/amqp
|
||||
|
||||
package amqp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
*testing.T
|
||||
r reader // framer <- client
|
||||
w writer // framer -> client
|
||||
S io.ReadWriteCloser // Server IO
|
||||
C io.ReadWriteCloser // Client IO
|
||||
|
||||
// captured client frames
|
||||
start connectionStartOk
|
||||
tune connectionTuneOk
|
||||
}
|
||||
|
||||
func defaultConfig() Config {
|
||||
return Config{SASL: []Authentication{&PlainAuth{"guest", "guest"}}, Vhost: "/"}
|
||||
}
|
||||
|
||||
func newSession(t *testing.T) (io.ReadWriteCloser, *server) {
|
||||
rs, wc := io.Pipe()
|
||||
rc, ws := io.Pipe()
|
||||
|
||||
rws := &logIO{t, "server", pipe{rs, ws}}
|
||||
rwc := &logIO{t, "client", pipe{rc, wc}}
|
||||
|
||||
server := server{
|
||||
T: t,
|
||||
r: reader{rws},
|
||||
w: writer{rws},
|
||||
S: rws,
|
||||
C: rwc,
|
||||
}
|
||||
|
||||
return rwc, &server
|
||||
}
|
||||
|
||||
func (t *server) expectBytes(b []byte) {
|
||||
in := make([]byte, len(b))
|
||||
if _, err := io.ReadFull(t.S, in); err != nil {
|
||||
t.Fatalf("io error expecting bytes: %v", err)
|
||||
}
|
||||
|
||||
if bytes.Compare(b, in) != 0 {
|
||||
t.Fatalf("failed bytes: expected: %s got: %s", string(b), string(in))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *server) send(channel int, m message) {
|
||||
defer time.AfterFunc(time.Second, func() { panic("send deadlock") }).Stop()
|
||||
|
||||
if err := t.w.WriteFrame(&methodFrame{
|
||||
ChannelId: uint16(channel),
|
||||
Method: m,
|
||||
}); err != nil {
|
||||
t.Fatalf("frame err, write: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// drops all but method frames expected on the given channel
|
||||
func (t *server) recv(channel int, m message) message {
|
||||
defer time.AfterFunc(time.Second, func() { panic("recv deadlock") }).Stop()
|
||||
|
||||
var remaining int
|
||||
var header *headerFrame
|
||||
var body []byte
|
||||
|
||||
for {
|
||||
frame, err := t.r.ReadFrame()
|
||||
if err != nil {
|
||||
t.Fatalf("frame err, read: %s", err)
|
||||
}
|
||||
|
||||
if frame.channel() != uint16(channel) {
|
||||
t.Fatalf("expected frame on channel %d, got channel %d", channel, frame.channel())
|
||||
}
|
||||
|
||||
switch f := frame.(type) {
|
||||
case *heartbeatFrame:
|
||||
// drop
|
||||
|
||||
case *headerFrame:
|
||||
// start content state
|
||||
header = f
|
||||
remaining = int(header.Size)
|
||||
if remaining == 0 {
|
||||
m.(messageWithContent).setContent(header.Properties, nil)
|
||||
return m
|
||||
}
|
||||
|
||||
case *bodyFrame:
|
||||
// continue until terminated
|
||||
body = append(body, f.Body...)
|
||||
remaining -= len(f.Body)
|
||||
if remaining <= 0 {
|
||||
m.(messageWithContent).setContent(header.Properties, body)
|
||||
return m
|
||||
}
|
||||
|
||||
case *methodFrame:
|
||||
if reflect.TypeOf(m) == reflect.TypeOf(f.Method) {
|
||||
wantv := reflect.ValueOf(m).Elem()
|
||||
havev := reflect.ValueOf(f.Method).Elem()
|
||||
wantv.Set(havev)
|
||||
if _, ok := m.(messageWithContent); !ok {
|
||||
return m
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("expected method type: %T, got: %T", m, f.Method)
|
||||
}
|
||||
|
||||
default:
|
||||
t.Fatalf("unexpected frame: %+v", f)
|
||||
}
|
||||
}
|
||||
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func (t *server) expectAMQP() {
|
||||
t.expectBytes([]byte{'A', 'M', 'Q', 'P', 0, 0, 9, 1})
|
||||
}
|
||||
|
||||
func (t *server) connectionStart() {
|
||||
t.send(0, &connectionStart{
|
||||
VersionMajor: 0,
|
||||
VersionMinor: 9,
|
||||
Mechanisms: "PLAIN",
|
||||
Locales: "en-us",
|
||||
})
|
||||
|
||||
t.recv(0, &t.start)
|
||||
}
|
||||
|
||||
func (t *server) connectionTune() {
|
||||
t.send(0, &connectionTune{
|
||||
ChannelMax: 11,
|
||||
FrameMax: 20000,
|
||||
Heartbeat: 10,
|
||||
})
|
||||
|
||||
t.recv(0, &t.tune)
|
||||
}
|
||||
|
||||
func (t *server) connectionOpen() {
|
||||
t.expectAMQP()
|
||||
t.connectionStart()
|
||||
t.connectionTune()
|
||||
|
||||
t.recv(0, &connectionOpen{})
|
||||
t.send(0, &connectionOpenOk{})
|
||||
}
|
||||
|
||||
func (t *server) connectionClose() {
|
||||
t.recv(0, &connectionClose{})
|
||||
t.send(0, &connectionCloseOk{})
|
||||
}
|
||||
|
||||
func (t *server) channelOpen(id int) {
|
||||
t.recv(id, &channelOpen{})
|
||||
t.send(id, &channelOpenOk{})
|
||||
}
|
||||
|
||||
func TestDefaultClientProperties(t *testing.T) {
|
||||
rwc, srv := newSession(t)
|
||||
|
||||
go func() {
|
||||
srv.connectionOpen()
|
||||
rwc.Close()
|
||||
}()
|
||||
|
||||
if c, err := Open(rwc, defaultConfig()); err != nil {
|
||||
t.Fatalf("could not create connection: %v (%s)", c, err)
|
||||
}
|
||||
|
||||
if want, got := defaultProduct, srv.start.ClientProperties["product"]; want != got {
|
||||
t.Errorf("expected product %s got: %s", want, got)
|
||||
}
|
||||
|
||||
if want, got := defaultVersion, srv.start.ClientProperties["version"]; want != got {
|
||||
t.Errorf("expected version %s got: %s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomClientProperties(t *testing.T) {
|
||||
rwc, srv := newSession(t)
|
||||
|
||||
config := defaultConfig()
|
||||
config.Properties = Table{
|
||||
"product": "foo",
|
||||
"version": "1.0",
|
||||
}
|
||||
|
||||
go func() {
|
||||
srv.connectionOpen()
|
||||
rwc.Close()
|
||||
}()
|
||||
|
||||
if c, err := Open(rwc, config); err != nil {
|
||||
t.Fatalf("could not create connection: %v (%s)", c, err)
|
||||
}
|
||||
|
||||
if want, got := config.Properties["product"], srv.start.ClientProperties["product"]; want != got {
|
||||
t.Errorf("expected product %s got: %s", want, got)
|
||||
}
|
||||
|
||||
if want, got := config.Properties["version"], srv.start.ClientProperties["version"]; want != got {
|
||||
t.Errorf("expected version %s got: %s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpen(t *testing.T) {
|
||||
rwc, srv := newSession(t)
|
||||
go func() {
|
||||
srv.connectionOpen()
|
||||
rwc.Close()
|
||||
}()
|
||||
|
||||
if c, err := Open(rwc, defaultConfig()); err != nil {
|
||||
t.Fatalf("could not create connection: %v (%s)", c, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelOpen(t *testing.T) {
|
||||
rwc, srv := newSession(t)
|
||||
|
||||
go func() {
|
||||
srv.connectionOpen()
|
||||
srv.channelOpen(1)
|
||||
|
||||
rwc.Close()
|
||||
}()
|
||||
|
||||
c, err := Open(rwc, defaultConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("could not create connection: %v (%s)", c, err)
|
||||
}
|
||||
|
||||
ch, err := c.Channel()
|
||||
if err != nil {
|
||||
t.Fatalf("could not open channel: %v (%s)", ch, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenFailedSASLUnsupportedMechanisms(t *testing.T) {
|
||||
rwc, srv := newSession(t)
|
||||
|
||||
go func() {
|
||||
srv.expectAMQP()
|
||||
srv.send(0, &connectionStart{
|
||||
VersionMajor: 0,
|
||||
VersionMinor: 9,
|
||||
Mechanisms: "KERBEROS NTLM",
|
||||
Locales: "en-us",
|
||||
})
|
||||
}()
|
||||
|
||||
c, err := Open(rwc, defaultConfig())
|
||||
if err != ErrSASL {
|
||||
t.Fatalf("expected ErrSASL got: %+v on %+v", err, c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenFailedCredentials(t *testing.T) {
|
||||
rwc, srv := newSession(t)
|
||||
|
||||
go func() {
|
||||
srv.expectAMQP()
|
||||
srv.connectionStart()
|
||||
// Now kill/timeout the connection indicating bad auth
|
||||
rwc.Close()
|
||||
}()
|
||||
|
||||
c, err := Open(rwc, defaultConfig())
|
||||
if err != ErrCredentials {
|
||||
t.Fatalf("expected ErrCredentials got: %+v on %+v", err, c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenFailedVhost(t *testing.T) {
|
||||
rwc, srv := newSession(t)
|
||||
|
||||
go func() {
|
||||
srv.expectAMQP()
|
||||
srv.connectionStart()
|
||||
srv.connectionTune()
|
||||
srv.recv(0, &connectionOpen{})
|
||||
|
||||
// Now kill/timeout the connection on bad Vhost
|
||||
rwc.Close()
|
||||
}()
|
||||
|
||||
c, err := Open(rwc, defaultConfig())
|
||||
if err != ErrVhost {
|
||||
t.Fatalf("expected ErrVhost got: %+v on %+v", err, c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfirmMultipleOrdersDeliveryTags(t *testing.T) {
|
||||
rwc, srv := newSession(t)
|
||||
defer rwc.Close()
|
||||
|
||||
go func() {
|
||||
srv.connectionOpen()
|
||||
srv.channelOpen(1)
|
||||
|
||||
srv.recv(1, &confirmSelect{})
|
||||
srv.send(1, &confirmSelectOk{})
|
||||
|
||||
srv.recv(1, &basicPublish{})
|
||||
srv.recv(1, &basicPublish{})
|
||||
srv.recv(1, &basicPublish{})
|
||||
srv.recv(1, &basicPublish{})
|
||||
|
||||
// Single tag, plus multiple, should produce
|
||||
// 2, 1, 3, 4
|
||||
srv.send(1, &basicAck{DeliveryTag: 2})
|
||||
srv.send(1, &basicAck{DeliveryTag: 4, Multiple: true})
|
||||
|
||||
srv.recv(1, &basicPublish{})
|
||||
srv.recv(1, &basicPublish{})
|
||||
srv.recv(1, &basicPublish{})
|
||||
srv.recv(1, &basicPublish{})
|
||||
|
||||
// And some more, but in reverse order, multiple then one
|
||||
// 5, 6, 7, 8
|
||||
srv.send(1, &basicAck{DeliveryTag: 6, Multiple: true})
|
||||
srv.send(1, &basicAck{DeliveryTag: 8})
|
||||
srv.send(1, &basicAck{DeliveryTag: 7})
|
||||
}()
|
||||
|
||||
c, err := Open(rwc, defaultConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("could not create connection: %v (%s)", c, err)
|
||||
}
|
||||
|
||||
ch, err := c.Channel()
|
||||
if err != nil {
|
||||
t.Fatalf("could not open channel: %v (%s)", ch, err)
|
||||
}
|
||||
|
||||
acks, _ := ch.NotifyConfirm(make(chan uint64), make(chan uint64))
|
||||
|
||||
ch.Confirm(false)
|
||||
|
||||
ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 1")})
|
||||
ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 2")})
|
||||
ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 3")})
|
||||
ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 4")})
|
||||
|
||||
for i, tag := range []uint64{2, 1, 3, 4} {
|
||||
if ack := <-acks; tag != ack {
|
||||
t.Fatalf("failed ack, expected ack#%d to be %d, got %d", i, tag, ack)
|
||||
}
|
||||
}
|
||||
|
||||
ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 5")})
|
||||
ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 6")})
|
||||
ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 7")})
|
||||
ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 8")})
|
||||
|
||||
for i, tag := range []uint64{5, 6, 8, 7} {
|
||||
if ack := <-acks; tag != ack {
|
||||
t.Fatalf("failed ack, expected ack#%d to be %d, got %d", i, tag, ack)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNotifyClosesReusedPublisherConfirmChan(t *testing.T) {
|
||||
rwc, srv := newSession(t)
|
||||
|
||||
go func() {
|
||||
srv.connectionOpen()
|
||||
srv.channelOpen(1)
|
||||
|
||||
srv.recv(1, &confirmSelect{})
|
||||
srv.send(1, &confirmSelectOk{})
|
||||
|
||||
srv.recv(0, &connectionClose{})
|
||||
srv.send(0, &connectionCloseOk{})
|
||||
}()
|
||||
|
||||
c, err := Open(rwc, defaultConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("could not create connection: %v (%s)", c, err)
|
||||
}
|
||||
|
||||
ch, err := c.Channel()
|
||||
if err != nil {
|
||||
t.Fatalf("could not open channel: %v (%s)", ch, err)
|
||||
}
|
||||
|
||||
ackAndNack := make(chan uint64)
|
||||
ch.NotifyConfirm(ackAndNack, ackAndNack)
|
||||
|
||||
if err := ch.Confirm(false); err != nil {
|
||||
t.Fatalf("expected to enter confirm mode: %v", err)
|
||||
}
|
||||
|
||||
if err := c.Close(); err != nil {
|
||||
t.Fatalf("could not close connection: %v (%s)", c, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotifyClosesAllChansAfterConnectionClose(t *testing.T) {
|
||||
rwc, srv := newSession(t)
|
||||
|
||||
go func() {
|
||||
srv.connectionOpen()
|
||||
srv.channelOpen(1)
|
||||
|
||||
srv.recv(0, &connectionClose{})
|
||||
srv.send(0, &connectionCloseOk{})
|
||||
}()
|
||||
|
||||
c, err := Open(rwc, defaultConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("could not create connection: %v (%s)", c, err)
|
||||
}
|
||||
|
||||
ch, err := c.Channel()
|
||||
if err != nil {
|
||||
t.Fatalf("could not open channel: %v (%s)", ch, err)
|
||||
}
|
||||
|
||||
if err := c.Close(); err != nil {
|
||||
t.Fatalf("could not close connection: %v (%s)", c, err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-c.NotifyClose(make(chan *Error)):
|
||||
case <-time.After(time.Millisecond):
|
||||
t.Errorf("expected to close NotifyClose chan after Connection.Close")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ch.NotifyClose(make(chan *Error)):
|
||||
case <-time.After(time.Millisecond):
|
||||
t.Errorf("expected to close Connection.NotifyClose chan after Connection.Close")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ch.NotifyFlow(make(chan bool)):
|
||||
case <-time.After(time.Millisecond):
|
||||
t.Errorf("expected to close Channel.NotifyFlow chan after Connection.Close")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ch.NotifyCancel(make(chan string)):
|
||||
case <-time.After(time.Millisecond):
|
||||
t.Errorf("expected to close Channel.NofityCancel chan after Connection.Close")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ch.NotifyReturn(make(chan Return)):
|
||||
case <-time.After(time.Millisecond):
|
||||
t.Errorf("expected to close Channel.NotifyReturn chan after Connection.Close")
|
||||
}
|
||||
|
||||
ack, nack := ch.NotifyConfirm(make(chan uint64), make(chan uint64))
|
||||
|
||||
select {
|
||||
case <-ack:
|
||||
case <-time.After(time.Millisecond):
|
||||
t.Errorf("expected to close acks on Channel.NotifyConfirm chan after Connection.Close")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-nack:
|
||||
case <-time.After(time.Millisecond):
|
||||
t.Errorf("expected to close nacks Channel.NotifyConfirm chan after Connection.Close")
|
||||
}
|
||||
}
|
||||
|
||||
// Should not panic when sending bodies split at differnet boundaries
|
||||
func TestPublishBodySliceIssue74(t *testing.T) {
|
||||
rwc, srv := newSession(t)
|
||||
defer rwc.Close()
|
||||
|
||||
const frameSize = 100
|
||||
const publishings = frameSize * 3
|
||||
|
||||
done := make(chan bool)
|
||||
base := make([]byte, publishings)
|
||||
|
||||
go func() {
|
||||
srv.connectionOpen()
|
||||
srv.channelOpen(1)
|
||||
|
||||
for i := 0; i < publishings; i++ {
|
||||
srv.recv(1, &basicPublish{})
|
||||
}
|
||||
|
||||
done <- true
|
||||
}()
|
||||
|
||||
cfg := defaultConfig()
|
||||
cfg.FrameSize = frameSize
|
||||
|
||||
c, err := Open(rwc, cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("could not create connection: %v (%s)", c, err)
|
||||
}
|
||||
|
||||
ch, err := c.Channel()
|
||||
if err != nil {
|
||||
t.Fatalf("could not open channel: %v (%s)", ch, err)
|
||||
}
|
||||
|
||||
for i := 0; i < publishings; i++ {
|
||||
go ch.Publish("", "q", false, false, Publishing{Body: base[0:i]})
|
||||
}
|
||||
|
||||
<-done
|
||||
}
|
||||
|
||||
func TestPublishAndShutdownDeadlockIssue84(t *testing.T) {
|
||||
rwc, srv := newSession(t)
|
||||
defer rwc.Close()
|
||||
|
||||
go func() {
|
||||
srv.connectionOpen()
|
||||
srv.channelOpen(1)
|
||||
srv.recv(1, &basicPublish{})
|
||||
// Mimic a broken io pipe so that Publish catches the error and goes into shutdown
|
||||
srv.S.Close()
|
||||
}()
|
||||
|
||||
c, err := Open(rwc, defaultConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't create connection: %v (%s)", c, err)
|
||||
}
|
||||
|
||||
ch, err := c.Channel()
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't open channel: %v (%s)", ch, err)
|
||||
}
|
||||
|
||||
defer time.AfterFunc(500*time.Millisecond, func() { panic("Publish deadlock") }).Stop()
|
||||
for {
|
||||
if err := ch.Publish("exchange", "q", false, false, Publishing{Body: []byte("test")}); err != nil {
|
||||
t.Log("successfully caught disconnect error", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
764
vendor/github.com/streadway/amqp/connection.go
generated
vendored
764
vendor/github.com/streadway/amqp/connection.go
generated
vendored
@ -1,764 +0,0 @@
|
||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Source code and contact info at http://github.com/streadway/amqp
|
||||
|
||||
package amqp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultHeartbeat = 10 * time.Second
|
||||
defaultConnectionTimeout = 30 * time.Second
|
||||
defaultProduct = "https://github.com/streadway/amqp"
|
||||
defaultVersion = "β"
|
||||
defaultChannelMax = (2 << 16) - 1
|
||||
)
|
||||
|
||||
// Config is used in DialConfig and Open to specify the desired tuning
|
||||
// parameters used during a connection open handshake. The negotiated tuning
|
||||
// will be stored in the returned connection's Config field.
|
||||
type Config struct {
|
||||
// The SASL mechanisms to try in the client request, and the successful
|
||||
// mechanism used on the Connection object.
|
||||
// If SASL is nil, PlainAuth from the URL is used.
|
||||
SASL []Authentication
|
||||
|
||||
// Vhost specifies the namespace of permissions, exchanges, queues and
|
||||
// bindings on the server. Dial sets this to the path parsed from the URL.
|
||||
Vhost string
|
||||
|
||||
ChannelMax int // 0 max channels means 2^16 - 1
|
||||
FrameSize int // 0 max bytes means unlimited
|
||||
Heartbeat time.Duration // less than 1s uses the server's interval
|
||||
|
||||
// TLSClientConfig specifies the client configuration of the TLS connection
|
||||
// when establishing a tls transport.
|
||||
// If the URL uses an amqps scheme, then an empty tls.Config with the
|
||||
// ServerName from the URL is used.
|
||||
TLSClientConfig *tls.Config
|
||||
|
||||
// Properties is table of properties that the client advertises to the server.
|
||||
// This is an optional setting - if the application does not set this,
|
||||
// the underlying library will use a generic set of client properties.
|
||||
Properties Table
|
||||
|
||||
// Dial returns a net.Conn prepared for a TLS handshake with TSLClientConfig,
|
||||
// then an AMQP connection handshake.
|
||||
// If Dial is nil, net.DialTimeout with a 30s connection and 30s read
|
||||
// deadline is used.
|
||||
Dial func(network, addr string) (net.Conn, error)
|
||||
}
|
||||
|
||||
// Connection manages the serialization and deserialization of frames from IO
|
||||
// and dispatches the frames to the appropriate channel. All RPC methods and
|
||||
// asyncronous Publishing, Delivery, Ack, Nack and Return messages are
|
||||
// multiplexed on this channel. There must always be active receivers for
|
||||
// every asynchronous message on this connection.
|
||||
type Connection struct {
|
||||
destructor sync.Once // shutdown once
|
||||
sendM sync.Mutex // conn writer mutex
|
||||
m sync.Mutex // struct field mutex
|
||||
|
||||
conn io.ReadWriteCloser
|
||||
|
||||
rpc chan message
|
||||
writer *writer
|
||||
sends chan time.Time // timestamps of each frame sent
|
||||
deadlines chan readDeadliner // heartbeater updates read deadlines
|
||||
|
||||
allocator *allocator // id generator valid after openTune
|
||||
channels map[uint16]*Channel
|
||||
|
||||
noNotify bool // true when we will never notify again
|
||||
closes []chan *Error
|
||||
blocks []chan Blocking
|
||||
|
||||
errors chan *Error
|
||||
|
||||
Config Config // The negotiated Config after connection.open
|
||||
|
||||
Major int // Server's major version
|
||||
Minor int // Server's minor version
|
||||
Properties Table // Server properties
|
||||
}
|
||||
|
||||
type readDeadliner interface {
|
||||
SetReadDeadline(time.Time) error
|
||||
}
|
||||
|
||||
type localNetAddr interface {
|
||||
LocalAddr() net.Addr
|
||||
}
|
||||
|
||||
// defaultDial establishes a connection when config.Dial is not provided
|
||||
func defaultDial(network, addr string) (net.Conn, error) {
|
||||
conn, err := net.DialTimeout(network, addr, defaultConnectionTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Heartbeating hasn't started yet, don't stall forever on a dead server.
|
||||
if err := conn.SetReadDeadline(time.Now().Add(defaultConnectionTimeout)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Dial accepts a string in the AMQP URI format and returns a new Connection
|
||||
// over TCP using PlainAuth. Defaults to a server heartbeat interval of 10
|
||||
// seconds and sets the initial read deadline to 30 seconds.
|
||||
//
|
||||
// Dial uses the zero value of tls.Config when it encounters an amqps://
|
||||
// scheme. It is equivalent to calling DialTLS(amqp, nil).
|
||||
func Dial(url string) (*Connection, error) {
|
||||
return DialConfig(url, Config{
|
||||
Heartbeat: defaultHeartbeat,
|
||||
})
|
||||
}
|
||||
|
||||
// DialTLS accepts a string in the AMQP URI format and returns a new Connection
|
||||
// over TCP using PlainAuth. Defaults to a server heartbeat interval of 10
|
||||
// seconds and sets the initial read deadline to 30 seconds.
|
||||
//
|
||||
// DialTLS uses the provided tls.Config when encountering an amqps:// scheme.
|
||||
func DialTLS(url string, amqps *tls.Config) (*Connection, error) {
|
||||
return DialConfig(url, Config{
|
||||
Heartbeat: defaultHeartbeat,
|
||||
TLSClientConfig: amqps,
|
||||
})
|
||||
}
|
||||
|
||||
// DialConfig accepts a string in the AMQP URI format and a configuration for
|
||||
// the transport and connection setup, returning a new Connection. Defaults to
|
||||
// a server heartbeat interval of 10 seconds and sets the initial read deadline
|
||||
// to 30 seconds.
|
||||
func DialConfig(url string, config Config) (*Connection, error) {
|
||||
var err error
|
||||
var conn net.Conn
|
||||
|
||||
uri, err := ParseURI(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.SASL == nil {
|
||||
config.SASL = []Authentication{uri.PlainAuth()}
|
||||
}
|
||||
|
||||
if config.Vhost == "" {
|
||||
config.Vhost = uri.Vhost
|
||||
}
|
||||
|
||||
if uri.Scheme == "amqps" && config.TLSClientConfig == nil {
|
||||
config.TLSClientConfig = new(tls.Config)
|
||||
}
|
||||
|
||||
addr := net.JoinHostPort(uri.Host, strconv.FormatInt(int64(uri.Port), 10))
|
||||
|
||||
dialer := config.Dial
|
||||
if dialer == nil {
|
||||
dialer = defaultDial
|
||||
}
|
||||
|
||||
conn, err = dialer("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.TLSClientConfig != nil {
|
||||
// Use the URI's host for hostname validation unless otherwise set. Make a
|
||||
// copy so not to modify the caller's reference when the caller reuses a
|
||||
// tls.Config for a different URL.
|
||||
if config.TLSClientConfig.ServerName == "" {
|
||||
c := *config.TLSClientConfig
|
||||
c.ServerName = uri.Host
|
||||
config.TLSClientConfig = &c
|
||||
}
|
||||
|
||||
client := tls.Client(conn, config.TLSClientConfig)
|
||||
if err := client.Handshake(); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn = client
|
||||
}
|
||||
|
||||
return Open(conn, config)
|
||||
}
|
||||
|
||||
/*
|
||||
Open accepts an already established connection, or other io.ReadWriteCloser as
|
||||
a transport. Use this method if you have established a TLS connection or wish
|
||||
to use your own custom transport.
|
||||
|
||||
*/
|
||||
func Open(conn io.ReadWriteCloser, config Config) (*Connection, error) {
|
||||
me := &Connection{
|
||||
conn: conn,
|
||||
writer: &writer{bufio.NewWriter(conn)},
|
||||
channels: make(map[uint16]*Channel),
|
||||
rpc: make(chan message),
|
||||
sends: make(chan time.Time),
|
||||
errors: make(chan *Error, 1),
|
||||
deadlines: make(chan readDeadliner, 1),
|
||||
}
|
||||
go me.reader(conn)
|
||||
return me, me.open(config)
|
||||
}
|
||||
|
||||
/*
|
||||
LocalAddr returns the local TCP peer address, or ":0" (the zero value of net.TCPAddr)
|
||||
as a fallback default value if the underlying transport does not support LocalAddr().
|
||||
*/
|
||||
func (me *Connection) LocalAddr() net.Addr {
|
||||
if c, ok := me.conn.(localNetAddr); ok {
|
||||
return c.LocalAddr()
|
||||
}
|
||||
return &net.TCPAddr{}
|
||||
}
|
||||
|
||||
/*
|
||||
NotifyClose registers a listener for close events either initiated by an error
|
||||
accompaning a connection.close method or by a normal shutdown.
|
||||
|
||||
On normal shutdowns, the chan will be closed.
|
||||
|
||||
To reconnect after a transport or protocol error, register a listener here and
|
||||
re-run your setup process.
|
||||
|
||||
*/
|
||||
func (me *Connection) NotifyClose(c chan *Error) chan *Error {
|
||||
me.m.Lock()
|
||||
defer me.m.Unlock()
|
||||
|
||||
if me.noNotify {
|
||||
close(c)
|
||||
} else {
|
||||
me.closes = append(me.closes, c)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
/*
|
||||
NotifyBlock registers a listener for RabbitMQ specific TCP flow control method
|
||||
extensions connection.blocked and connection.unblocked. Flow control is active
|
||||
with a reason when Blocking.Blocked is true. When a Connection is blocked, all
|
||||
methods will block across all connections until server resources become free
|
||||
again.
|
||||
|
||||
This optional extension is supported by the server when the
|
||||
"connection.blocked" server capability key is true.
|
||||
|
||||
*/
|
||||
func (me *Connection) NotifyBlocked(c chan Blocking) chan Blocking {
|
||||
me.m.Lock()
|
||||
defer me.m.Unlock()
|
||||
|
||||
if me.noNotify {
|
||||
close(c)
|
||||
} else {
|
||||
me.blocks = append(me.blocks, c)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
/*
|
||||
Close requests and waits for the response to close the AMQP connection.
|
||||
|
||||
It's advisable to use this message when publishing to ensure all kernel buffers
|
||||
have been flushed on the server and client before exiting.
|
||||
|
||||
An error indicates that server may not have received this request to close but
|
||||
the connection should be treated as closed regardless.
|
||||
|
||||
After returning from this call, all resources associated with this connection,
|
||||
including the underlying io, Channels, Notify listeners and Channel consumers
|
||||
will also be closed.
|
||||
*/
|
||||
func (me *Connection) Close() error {
|
||||
defer me.shutdown(nil)
|
||||
return me.call(
|
||||
&connectionClose{
|
||||
ReplyCode: replySuccess,
|
||||
ReplyText: "kthxbai",
|
||||
},
|
||||
&connectionCloseOk{},
|
||||
)
|
||||
}
|
||||
|
||||
func (me *Connection) closeWith(err *Error) error {
|
||||
defer me.shutdown(err)
|
||||
return me.call(
|
||||
&connectionClose{
|
||||
ReplyCode: uint16(err.Code),
|
||||
ReplyText: err.Reason,
|
||||
},
|
||||
&connectionCloseOk{},
|
||||
)
|
||||
}
|
||||
|
||||
func (me *Connection) send(f frame) error {
|
||||
me.sendM.Lock()
|
||||
err := me.writer.WriteFrame(f)
|
||||
me.sendM.Unlock()
|
||||
|
||||
if err != nil {
|
||||
// shutdown could be re-entrant from signaling notify chans
|
||||
go me.shutdown(&Error{
|
||||
Code: FrameError,
|
||||
Reason: err.Error(),
|
||||
})
|
||||
} else {
|
||||
// Broadcast we sent a frame, reducing heartbeats, only
|
||||
// if there is something that can receive - like a non-reentrant
|
||||
// call or if the heartbeater isn't running
|
||||
select {
|
||||
case me.sends <- time.Now():
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (me *Connection) shutdown(err *Error) {
|
||||
me.destructor.Do(func() {
|
||||
if err != nil {
|
||||
for _, c := range me.closes {
|
||||
c <- err
|
||||
}
|
||||
}
|
||||
|
||||
for _, ch := range me.channels {
|
||||
me.closeChannel(ch, err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
me.errors <- err
|
||||
}
|
||||
|
||||
me.conn.Close()
|
||||
|
||||
for _, c := range me.closes {
|
||||
close(c)
|
||||
}
|
||||
|
||||
for _, c := range me.blocks {
|
||||
close(c)
|
||||
}
|
||||
|
||||
me.m.Lock()
|
||||
me.noNotify = true
|
||||
me.m.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
// All methods sent to the connection channel should be synchronous so we
|
||||
// can handle them directly without a framing component
|
||||
func (me *Connection) demux(f frame) {
|
||||
if f.channel() == 0 {
|
||||
me.dispatch0(f)
|
||||
} else {
|
||||
me.dispatchN(f)
|
||||
}
|
||||
}
|
||||
|
||||
func (me *Connection) dispatch0(f frame) {
|
||||
switch mf := f.(type) {
|
||||
case *methodFrame:
|
||||
switch m := mf.Method.(type) {
|
||||
case *connectionClose:
|
||||
// Send immediately as shutdown will close our side of the writer.
|
||||
me.send(&methodFrame{
|
||||
ChannelId: 0,
|
||||
Method: &connectionCloseOk{},
|
||||
})
|
||||
|
||||
me.shutdown(newError(m.ReplyCode, m.ReplyText))
|
||||
case *connectionBlocked:
|
||||
for _, c := range me.blocks {
|
||||
c <- Blocking{Active: true, Reason: m.Reason}
|
||||
}
|
||||
case *connectionUnblocked:
|
||||
for _, c := range me.blocks {
|
||||
c <- Blocking{Active: false}
|
||||
}
|
||||
default:
|
||||
me.rpc <- m
|
||||
}
|
||||
case *heartbeatFrame:
|
||||
// kthx - all reads reset our deadline. so we can drop this
|
||||
default:
|
||||
// lolwat - channel0 only responds to methods and heartbeats
|
||||
me.closeWith(ErrUnexpectedFrame)
|
||||
}
|
||||
}
|
||||
|
||||
func (me *Connection) dispatchN(f frame) {
|
||||
me.m.Lock()
|
||||
channel := me.channels[f.channel()]
|
||||
me.m.Unlock()
|
||||
|
||||
if channel != nil {
|
||||
channel.recv(channel, f)
|
||||
} else {
|
||||
me.dispatchClosed(f)
|
||||
}
|
||||
}
|
||||
|
||||
// section 2.3.7: "When a peer decides to close a channel or connection, it
|
||||
// sends a Close method. The receiving peer MUST respond to a Close with a
|
||||
// Close-Ok, and then both parties can close their channel or connection. Note
|
||||
// that if peers ignore Close, deadlock can happen when both peers send Close
|
||||
// at the same time."
|
||||
//
|
||||
// When we don't have a channel, so we must respond with close-ok on a close
|
||||
// method. This can happen between a channel exception on an asynchronous
|
||||
// method like basic.publish and a synchronous close with channel.close.
|
||||
// In that case, we'll get both a channel.close and channel.close-ok in any
|
||||
// order.
|
||||
func (me *Connection) dispatchClosed(f frame) {
|
||||
// Only consider method frames, drop content/header frames
|
||||
if mf, ok := f.(*methodFrame); ok {
|
||||
switch mf.Method.(type) {
|
||||
case *channelClose:
|
||||
me.send(&methodFrame{
|
||||
ChannelId: f.channel(),
|
||||
Method: &channelCloseOk{},
|
||||
})
|
||||
case *channelCloseOk:
|
||||
// we are already closed, so do nothing
|
||||
default:
|
||||
// unexpected method on closed channel
|
||||
me.closeWith(ErrClosed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reads each frame off the IO and hand off to the connection object that
|
||||
// will demux the streams and dispatch to one of the opened channels or
|
||||
// handle on channel 0 (the connection channel).
|
||||
func (me *Connection) reader(r io.Reader) {
|
||||
buf := bufio.NewReader(r)
|
||||
frames := &reader{buf}
|
||||
conn, haveDeadliner := r.(readDeadliner)
|
||||
|
||||
for {
|
||||
frame, err := frames.ReadFrame()
|
||||
|
||||
if err != nil {
|
||||
me.shutdown(&Error{Code: FrameError, Reason: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
me.demux(frame)
|
||||
|
||||
if haveDeadliner {
|
||||
me.deadlines <- conn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensures that at least one frame is being sent at the tuned interval with a
|
||||
// jitter tolerance of 1s
|
||||
func (me *Connection) heartbeater(interval time.Duration, done chan *Error) {
|
||||
const maxServerHeartbeatsInFlight = 3
|
||||
|
||||
var sendTicks <-chan time.Time
|
||||
if interval > 0 {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
sendTicks = ticker.C
|
||||
}
|
||||
|
||||
lastSent := time.Now()
|
||||
|
||||
for {
|
||||
select {
|
||||
case at, stillSending := <-me.sends:
|
||||
// When actively sending, depend on sent frames to reset server timer
|
||||
if stillSending {
|
||||
lastSent = at
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
case at := <-sendTicks:
|
||||
// When idle, fill the space with a heartbeat frame
|
||||
if at.Sub(lastSent) > interval-time.Second {
|
||||
if err := me.send(&heartbeatFrame{}); err != nil {
|
||||
// send heartbeats even after close/closeOk so we
|
||||
// tick until the connection starts erroring
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
case conn := <-me.deadlines:
|
||||
// When reading, reset our side of the deadline, if we've negotiated one with
|
||||
// a deadline that covers at least 2 server heartbeats
|
||||
if interval > 0 {
|
||||
conn.SetReadDeadline(time.Now().Add(maxServerHeartbeatsInFlight * interval))
|
||||
}
|
||||
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience method to inspect the Connection.Properties["capabilities"]
|
||||
// Table for server identified capabilities like "basic.ack" or
|
||||
// "confirm.select".
|
||||
func (me *Connection) isCapable(featureName string) bool {
|
||||
capabilities, _ := me.Properties["capabilities"].(Table)
|
||||
hasFeature, _ := capabilities[featureName].(bool)
|
||||
return hasFeature
|
||||
}
|
||||
|
||||
// allocateChannel records but does not open a new channel with a unique id.
|
||||
// This method is the initial part of the channel lifecycle and paired with
|
||||
// releaseChannel
|
||||
func (me *Connection) allocateChannel() (*Channel, error) {
|
||||
me.m.Lock()
|
||||
defer me.m.Unlock()
|
||||
|
||||
id, ok := me.allocator.next()
|
||||
if !ok {
|
||||
return nil, ErrChannelMax
|
||||
}
|
||||
|
||||
ch := newChannel(me, uint16(id))
|
||||
me.channels[uint16(id)] = ch
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// releaseChannel removes a channel from the registry as the final part of the
|
||||
// channel lifecycle
|
||||
func (me *Connection) releaseChannel(id uint16) {
|
||||
me.m.Lock()
|
||||
defer me.m.Unlock()
|
||||
|
||||
delete(me.channels, id)
|
||||
me.allocator.release(int(id))
|
||||
}
|
||||
|
||||
// openChannel allocates and opens a channel, must be paired with closeChannel
|
||||
func (me *Connection) openChannel() (*Channel, error) {
|
||||
ch, err := me.allocateChannel()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := ch.open(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// closeChannel releases and initiates a shutdown of the channel. All channel
|
||||
// closures should be initiated here for proper channel lifecycle management on
|
||||
// this connection.
|
||||
func (me *Connection) closeChannel(ch *Channel, e *Error) {
|
||||
ch.shutdown(e)
|
||||
me.releaseChannel(ch.id)
|
||||
}
|
||||
|
||||
/*
|
||||
Channel opens a unique, concurrent server channel to process the bulk of AMQP
|
||||
messages. Any error from methods on this receiver will render the receiver
|
||||
invalid and a new Channel should be opened.
|
||||
|
||||
*/
|
||||
func (me *Connection) Channel() (*Channel, error) {
|
||||
return me.openChannel()
|
||||
}
|
||||
|
||||
func (me *Connection) call(req message, res ...message) error {
|
||||
// Special case for when the protocol header frame is sent insted of a
|
||||
// request method
|
||||
if req != nil {
|
||||
if err := me.send(&methodFrame{ChannelId: 0, Method: req}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case err := <-me.errors:
|
||||
return err
|
||||
|
||||
case msg := <-me.rpc:
|
||||
// Try to match one of the result types
|
||||
for _, try := range res {
|
||||
if reflect.TypeOf(msg) == reflect.TypeOf(try) {
|
||||
// *res = *msg
|
||||
vres := reflect.ValueOf(try).Elem()
|
||||
vmsg := reflect.ValueOf(msg).Elem()
|
||||
vres.Set(vmsg)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrCommandInvalid
|
||||
}
|
||||
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Connection = open-Connection *use-Connection close-Connection
|
||||
// open-Connection = C:protocol-header
|
||||
// S:START C:START-OK
|
||||
// *challenge
|
||||
// S:TUNE C:TUNE-OK
|
||||
// C:OPEN S:OPEN-OK
|
||||
// challenge = S:SECURE C:SECURE-OK
|
||||
// use-Connection = *channel
|
||||
// close-Connection = C:CLOSE S:CLOSE-OK
|
||||
// / S:CLOSE C:CLOSE-OK
|
||||
func (me *Connection) open(config Config) error {
|
||||
if err := me.send(&protocolHeader{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return me.openStart(config)
|
||||
}
|
||||
|
||||
func (me *Connection) openStart(config Config) error {
|
||||
start := &connectionStart{}
|
||||
|
||||
if err := me.call(nil, start); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
me.Major = int(start.VersionMajor)
|
||||
me.Minor = int(start.VersionMinor)
|
||||
me.Properties = Table(start.ServerProperties)
|
||||
|
||||
// eventually support challenge/response here by also responding to
|
||||
// connectionSecure.
|
||||
auth, ok := pickSASLMechanism(config.SASL, strings.Split(start.Mechanisms, " "))
|
||||
if !ok {
|
||||
return ErrSASL
|
||||
}
|
||||
|
||||
// Save this mechanism off as the one we chose
|
||||
me.Config.SASL = []Authentication{auth}
|
||||
|
||||
return me.openTune(config, auth)
|
||||
}
|
||||
|
||||
func (me *Connection) openTune(config Config, auth Authentication) error {
|
||||
if len(config.Properties) == 0 {
|
||||
config.Properties = Table{
|
||||
"product": defaultProduct,
|
||||
"version": defaultVersion,
|
||||
}
|
||||
}
|
||||
|
||||
config.Properties["capabilities"] = Table{
|
||||
"connection.blocked": true,
|
||||
"consumer_cancel_notify": true,
|
||||
}
|
||||
|
||||
ok := &connectionStartOk{
|
||||
Mechanism: auth.Mechanism(),
|
||||
Response: auth.Response(),
|
||||
ClientProperties: config.Properties,
|
||||
}
|
||||
tune := &connectionTune{}
|
||||
|
||||
if err := me.call(ok, tune); err != nil {
|
||||
// per spec, a connection can only be closed when it has been opened
|
||||
// so at this point, we know it's an auth error, but the socket
|
||||
// was closed instead. Return a meaningful error.
|
||||
return ErrCredentials
|
||||
}
|
||||
|
||||
// When the server and client both use default 0, then the max channel is
|
||||
// only limited by uint16.
|
||||
me.Config.ChannelMax = pick(config.ChannelMax, int(tune.ChannelMax))
|
||||
if me.Config.ChannelMax == 0 {
|
||||
me.Config.ChannelMax = defaultChannelMax
|
||||
}
|
||||
|
||||
// Frame size includes headers and end byte (len(payload)+8), even if
|
||||
// this is less than FrameMinSize, use what the server sends because the
|
||||
// alternative is to stop the handshake here.
|
||||
me.Config.FrameSize = pick(config.FrameSize, int(tune.FrameMax))
|
||||
|
||||
// Save this off for resetDeadline()
|
||||
me.Config.Heartbeat = time.Second * time.Duration(pick(
|
||||
int(config.Heartbeat/time.Second),
|
||||
int(tune.Heartbeat)))
|
||||
|
||||
// "The client should start sending heartbeats after receiving a
|
||||
// Connection.Tune method"
|
||||
go me.heartbeater(me.Config.Heartbeat, me.NotifyClose(make(chan *Error, 1)))
|
||||
|
||||
if err := me.send(&methodFrame{
|
||||
ChannelId: 0,
|
||||
Method: &connectionTuneOk{
|
||||
ChannelMax: uint16(me.Config.ChannelMax),
|
||||
FrameMax: uint32(me.Config.FrameSize),
|
||||
Heartbeat: uint16(me.Config.Heartbeat / time.Second),
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return me.openVhost(config)
|
||||
}
|
||||
|
||||
func (me *Connection) openVhost(config Config) error {
|
||||
req := &connectionOpen{VirtualHost: config.Vhost}
|
||||
res := &connectionOpenOk{}
|
||||
|
||||
if err := me.call(req, res); err != nil {
|
||||
// Cannot be closed yet, but we know it's a vhost problem
|
||||
return ErrVhost
|
||||
}
|
||||
|
||||
me.Config.Vhost = config.Vhost
|
||||
|
||||
return me.openComplete()
|
||||
}
|
||||
|
||||
// openComplete performs any final Connection initialization dependent on the
|
||||
// connection handshake.
|
||||
func (me *Connection) openComplete() error {
|
||||
me.allocator = newAllocator(1, me.Config.ChannelMax)
|
||||
return nil
|
||||
}
|
||||
|
||||
func pick(client, server int) int {
|
||||
if client == 0 || server == 0 {
|
||||
// max
|
||||
if client > server {
|
||||
return client
|
||||
} else {
|
||||
return server
|
||||
}
|
||||
} else {
|
||||
// min
|
||||
if client > server {
|
||||
return server
|
||||
} else {
|
||||
return client
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
118
vendor/github.com/streadway/amqp/consumers.go
generated
vendored
118
vendor/github.com/streadway/amqp/consumers.go
generated
vendored
@ -1,118 +0,0 @@
|
||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Source code and contact info at http://github.com/streadway/amqp
|
||||
|
||||
package amqp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var consumerSeq uint64
|
||||
|
||||
func uniqueConsumerTag() string {
|
||||
return fmt.Sprintf("ctag-%s-%d", os.Args[0], atomic.AddUint64(&consumerSeq, 1))
|
||||
}
|
||||
|
||||
type consumerBuffers map[string]chan *Delivery
|
||||
|
||||
// Concurrent type that manages the consumerTag ->
|
||||
// ingress consumerBuffer mapping
|
||||
type consumers struct {
|
||||
sync.Mutex
|
||||
chans consumerBuffers
|
||||
}
|
||||
|
||||
func makeConsumers() *consumers {
|
||||
return &consumers{chans: make(consumerBuffers)}
|
||||
}
|
||||
|
||||
func bufferDeliveries(in chan *Delivery, out chan Delivery) {
|
||||
var queue []*Delivery
|
||||
var queueIn = in
|
||||
|
||||
for delivery := range in {
|
||||
select {
|
||||
case out <- *delivery:
|
||||
// delivered immediately while the consumer chan can receive
|
||||
default:
|
||||
queue = append(queue, delivery)
|
||||
}
|
||||
|
||||
for len(queue) > 0 {
|
||||
select {
|
||||
case out <- *queue[0]:
|
||||
queue = queue[1:]
|
||||
case delivery, open := <-queueIn:
|
||||
if open {
|
||||
queue = append(queue, delivery)
|
||||
} else {
|
||||
// stop receiving to drain the queue
|
||||
queueIn = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(out)
|
||||
}
|
||||
|
||||
// On key conflict, close the previous channel.
|
||||
func (me *consumers) add(tag string, consumer chan Delivery) {
|
||||
me.Lock()
|
||||
defer me.Unlock()
|
||||
|
||||
if prev, found := me.chans[tag]; found {
|
||||
close(prev)
|
||||
}
|
||||
|
||||
in := make(chan *Delivery)
|
||||
go bufferDeliveries(in, consumer)
|
||||
|
||||
me.chans[tag] = in
|
||||
}
|
||||
|
||||
func (me *consumers) close(tag string) (found bool) {
|
||||
me.Lock()
|
||||
defer me.Unlock()
|
||||
|
||||
ch, found := me.chans[tag]
|
||||
|
||||
if found {
|
||||
delete(me.chans, tag)
|
||||
close(ch)
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
func (me *consumers) closeAll() {
|
||||
me.Lock()
|
||||
defer me.Unlock()
|
||||
|
||||
for _, ch := range me.chans {
|
||||
close(ch)
|
||||
}
|
||||
|
||||
me.chans = make(consumerBuffers)
|
||||
}
|
||||
|
||||
// Sends a delivery to a the consumer identified by `tag`.
|
||||
// If unbuffered channels are used for Consume this method
|
||||
// could block all deliveries until the consumer
|
||||
// receives on the other end of the channel.
|
||||
func (me *consumers) send(tag string, msg *Delivery) bool {
|
||||
me.Lock()
|
||||
defer me.Unlock()
|
||||
|
||||
buffer, found := me.chans[tag]
|
||||
if found {
|
||||
buffer <- msg
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
173
vendor/github.com/streadway/amqp/delivery.go
generated
vendored
173
vendor/github.com/streadway/amqp/delivery.go
generated
vendored
@ -1,173 +0,0 @@
|
||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Source code and contact info at http://github.com/streadway/amqp
|
||||
|
||||
package amqp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
var errDeliveryNotInitialized = errors.New("delivery not initialized")
|
||||
|
||||
// Acknowledger notifies the server of successful or failed consumption of
|
||||
// delivieries via identifier found in the Delivery.DeliveryTag field.
|
||||
//
|
||||
// Applications can provide mock implementations in tests of Delivery handlers.
|
||||
type Acknowledger interface {
|
||||
Ack(tag uint64, multiple bool) error
|
||||
Nack(tag uint64, multiple bool, requeue bool) error
|
||||
Reject(tag uint64, requeue bool) error
|
||||
}
|
||||
|
||||
// Delivery captures the fields for a previously delivered message resident in
|
||||
// a queue to be delivered by the server to a consumer from Channel.Consume or
|
||||
// Channel.Get.
|
||||
type Delivery struct {
|
||||
Acknowledger Acknowledger // the channel from which this delivery arrived
|
||||
|
||||
Headers Table // Application or header exchange table
|
||||
|
||||
// Properties
|
||||
ContentType string // MIME content type
|
||||
ContentEncoding string // MIME content encoding
|
||||
DeliveryMode uint8 // queue implemention use - non-persistent (1) or persistent (2)
|
||||
Priority uint8 // queue implementation use - 0 to 9
|
||||
CorrelationId string // application use - correlation identifier
|
||||
ReplyTo string // application use - address to to reply to (ex: RPC)
|
||||
Expiration string // implementation use - message expiration spec
|
||||
MessageId string // application use - message identifier
|
||||
Timestamp time.Time // application use - message timestamp
|
||||
Type string // application use - message type name
|
||||
UserId string // application use - creating user - should be authenticated user
|
||||
AppId string // application use - creating application id
|
||||
|
||||
// Valid only with Channel.Consume
|
||||
ConsumerTag string
|
||||
|
||||
// Valid only with Channel.Get
|
||||
MessageCount uint32
|
||||
|
||||
DeliveryTag uint64
|
||||
Redelivered bool
|
||||
Exchange string // basic.publish exhange
|
||||
RoutingKey string // basic.publish routing key
|
||||
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func newDelivery(channel *Channel, msg messageWithContent) *Delivery {
|
||||
props, body := msg.getContent()
|
||||
|
||||
delivery := Delivery{
|
||||
Acknowledger: channel,
|
||||
|
||||
Headers: props.Headers,
|
||||
ContentType: props.ContentType,
|
||||
ContentEncoding: props.ContentEncoding,
|
||||
DeliveryMode: props.DeliveryMode,
|
||||
Priority: props.Priority,
|
||||
CorrelationId: props.CorrelationId,
|
||||
ReplyTo: props.ReplyTo,
|
||||
Expiration: props.Expiration,
|
||||
MessageId: props.MessageId,
|
||||
Timestamp: props.Timestamp,
|
||||
Type: props.Type,
|
||||
UserId: props.UserId,
|
||||
AppId: props.AppId,
|
||||
|
||||
Body: body,
|
||||
}
|
||||
|
||||
// Properties for the delivery types
|
||||
switch m := msg.(type) {
|
||||
case *basicDeliver:
|
||||
delivery.ConsumerTag = m.ConsumerTag
|
||||
delivery.DeliveryTag = m.DeliveryTag
|
||||
delivery.Redelivered = m.Redelivered
|
||||
delivery.Exchange = m.Exchange
|
||||
delivery.RoutingKey = m.RoutingKey
|
||||
|
||||
case *basicGetOk:
|
||||
delivery.MessageCount = m.MessageCount
|
||||
delivery.DeliveryTag = m.DeliveryTag
|
||||
delivery.Redelivered = m.Redelivered
|
||||
delivery.Exchange = m.Exchange
|
||||
delivery.RoutingKey = m.RoutingKey
|
||||
}
|
||||
|
||||
return &delivery
|
||||
}
|
||||
|
||||
/*
|
||||
Ack delegates an acknowledgement through the Acknowledger interface that the
|
||||
client or server has finished work on a delivery.
|
||||
|
||||
All deliveries in AMQP must be acknowledged. If you called Channel.Consume
|
||||
with autoAck true then the server will be automatically ack each message and
|
||||
this method should not be called. Otherwise, you must call Delivery.Ack after
|
||||
you have successfully processed this delivery.
|
||||
|
||||
When multiple is true, this delivery and all prior unacknowledged deliveries
|
||||
on the same channel will be acknowledged. This is useful for batch processing
|
||||
of deliveries.
|
||||
|
||||
An error will indicate that the acknowledge could not be delivered to the
|
||||
channel it was sent from.
|
||||
|
||||
Either Delivery.Ack, Delivery.Reject or Delivery.Nack must be called for every
|
||||
delivery that is not automatically acknowledged.
|
||||
*/
|
||||
func (me Delivery) Ack(multiple bool) error {
|
||||
if me.Acknowledger == nil {
|
||||
return errDeliveryNotInitialized
|
||||
}
|
||||
return me.Acknowledger.Ack(me.DeliveryTag, multiple)
|
||||
}
|
||||
|
||||
/*
|
||||
Reject delegates a negatively acknowledgement through the Acknowledger interface.
|
||||
|
||||
When requeue is true, queue this message to be delivered to a consumer on a
|
||||
different channel. When requeue is false or the server is unable to queue this
|
||||
message, it will be dropped.
|
||||
|
||||
If you are batch processing deliveries, and your server supports it, prefer
|
||||
Delivery.Nack.
|
||||
|
||||
Either Delivery.Ack, Delivery.Reject or Delivery.Nack must be called for every
|
||||
delivery that is not automatically acknowledged.
|
||||
*/
|
||||
func (me Delivery) Reject(requeue bool) error {
|
||||
if me.Acknowledger == nil {
|
||||
return errDeliveryNotInitialized
|
||||
}
|
||||
return me.Acknowledger.Reject(me.DeliveryTag, requeue)
|
||||
}
|
||||
|
||||
/*
|
||||
Nack negatively acknowledge the delivery of message(s) identified by the
|
||||
delivery tag from either the client or server.
|
||||
|
||||
When multiple is true, nack messages up to and including delivered messages up
|
||||
until the delivery tag delivered on the same channel.
|
||||
|
||||
When requeue is true, request the server to deliver this message to a different
|
||||
consumer. If it is not possible or requeue is false, the message will be
|
||||
dropped or delivered to a server configured dead-letter queue.
|
||||
|
||||
This method must not be used to select or requeue messages the client wishes
|
||||
not to handle, rather it is to inform the server that the client is incapable
|
||||
of handling this message at this time.
|
||||
|
||||
Either Delivery.Ack, Delivery.Reject or Delivery.Nack must be called for every
|
||||
delivery that is not automatically acknowledged.
|
||||
*/
|
||||
func (me Delivery) Nack(multiple, requeue bool) error {
|
||||
if me.Acknowledger == nil {
|
||||
return errDeliveryNotInitialized
|
||||
}
|
||||
return me.Acknowledger.Nack(me.DeliveryTag, multiple, requeue)
|
||||
}
|
33
vendor/github.com/streadway/amqp/delivery_test.go
generated
vendored
33
vendor/github.com/streadway/amqp/delivery_test.go
generated
vendored
@ -1,33 +0,0 @@
|
||||
package amqp
|
||||
|
||||
import "testing"
|
||||
|
||||
func shouldNotPanic(t *testing.T) {
|
||||
if err := recover(); err != nil {
|
||||
t.Fatalf("should not panic, got: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// A closed delivery chan could produce zero value. Ack/Nack/Reject on these
|
||||
// deliveries can produce a nil pointer panic. Instead return an error when
|
||||
// the method can never be successful.
|
||||
func TestAckZeroValueAcknowledgerDoesNotPanic(t *testing.T) {
|
||||
defer shouldNotPanic(t)
|
||||
if err := (Delivery{}).Ack(false); err == nil {
|
||||
t.Errorf("expected Delivery{}.Ack to error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNackZeroValueAcknowledgerDoesNotPanic(t *testing.T) {
|
||||
defer shouldNotPanic(t)
|
||||
if err := (Delivery{}).Nack(false, false); err == nil {
|
||||
t.Errorf("expected Delivery{}.Ack to error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectZeroValueAcknowledgerDoesNotPanic(t *testing.T) {
|
||||
defer shouldNotPanic(t)
|
||||
if err := (Delivery{}).Reject(false); err == nil {
|
||||
t.Errorf("expected Delivery{}.Ack to error")
|
||||
}
|
||||
}
|
108
vendor/github.com/streadway/amqp/doc.go
generated
vendored
108
vendor/github.com/streadway/amqp/doc.go
generated
vendored
@ -1,108 +0,0 @@
|
||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Source code and contact info at http://github.com/streadway/amqp
|
||||
|
||||
/*
|
||||
AMQP 0.9.1 client with RabbitMQ extensions
|
||||
|
||||
Understand the AMQP 0.9.1 messaging model by reviewing these links first. Much
|
||||
of the terminology in this library directly relates to AMQP concepts.
|
||||
|
||||
Resources
|
||||
|
||||
http://www.rabbitmq.com/tutorials/amqp-concepts.html
|
||||
http://www.rabbitmq.com/getstarted.html
|
||||
http://www.rabbitmq.com/amqp-0-9-1-reference.html
|
||||
|
||||
Design
|
||||
|
||||
Most other broker clients publish to queues, but in AMQP, clients publish
|
||||
Exchanges instead. AMQP is programmable, meaning that both the producers and
|
||||
consumers agree on the configuration of the broker, instead requiring an
|
||||
operator or system configuration that declares the logical topology in the
|
||||
broker. The routing between producers and consumer queues is via Bindings.
|
||||
These bindings form the logical topology of the broker.
|
||||
|
||||
In this library, a message sent from publisher is called a "Publishing" and a
|
||||
message received to a consumer is called a "Delivery". The fields of
|
||||
Publishings and Deliveries are close but not exact mappings to the underlying
|
||||
wire format to maintain stronger types. Many other libraries will combine
|
||||
message properties with message headers. In this library, the message well
|
||||
known properties are strongly typed fields on the Publishings and Deliveries,
|
||||
whereas the user defined headers are in the Headers field.
|
||||
|
||||
The method naming closely matches the protocol's method name with positional
|
||||
parameters mapping to named protocol message fields. The motivation here is to
|
||||
present a comprehensive view over all possible interactions with the server.
|
||||
|
||||
Generally, methods that map to protocol methods of the "basic" class will be
|
||||
elided in this interface, and "select" methods of various channel mode selectors
|
||||
will be elided for example Channel.Confirm and Channel.Tx.
|
||||
|
||||
The library is intentionally designed to be synchronous, where responses for
|
||||
each protocol message are required to be received in an RPC manner. Some
|
||||
methods have a noWait parameter like Channel.QueueDeclare, and some methods are
|
||||
asynchronous like Channel.Publish. The error values should still be checked for
|
||||
these methods as they will indicate IO failures like when the underlying
|
||||
connection closes.
|
||||
|
||||
Asynchronous Events
|
||||
|
||||
Clients of this library may be interested in receiving some of the protocol
|
||||
messages other than Deliveries like basic.ack methods while a channel is in
|
||||
confirm mode.
|
||||
|
||||
The Notify* methods with Connection and Channel receivers model the pattern of
|
||||
asynchronous events like closes due to exceptions, or messages that are sent out
|
||||
of band from an RPC call like basic.ack or basic.flow.
|
||||
|
||||
Any asynchronous events, including Deliveries and Publishings must always have
|
||||
a receiver until the corresponding chans are closed. Without asynchronous
|
||||
receivers, the sychronous methods will block.
|
||||
|
||||
Use Case
|
||||
|
||||
It's important as a client to an AMQP topology to ensure the state of the
|
||||
broker matches your expectations. For both publish and consume use cases,
|
||||
make sure you declare the queues, exchanges and bindings you expect to exist
|
||||
prior to calling Channel.Publish or Channel.Consume.
|
||||
|
||||
// Connections start with amqp.Dial() typically from a command line argument
|
||||
// or environment variable.
|
||||
connection, err := amqp.Dial(os.Getenv("AMQP_URL"))
|
||||
|
||||
// To cleanly shutdown by flushing kernel buffers, make sure to close and
|
||||
// wait for the response.
|
||||
defer connection.Close()
|
||||
|
||||
// Most operations happen on a channel. If any error is returned on a
|
||||
// channel, the channel will no longer be valid, throw it away and try with
|
||||
// a different channel. If you use many channels, it's useful for the
|
||||
// server to
|
||||
channel, err := connection.Channel()
|
||||
|
||||
// Declare your topology here, if it doesn't exist, it will be created, if
|
||||
// it existed already and is not what you expect, then that's considered an
|
||||
// error.
|
||||
|
||||
// Use your connection on this topology with either Publish or Consume, or
|
||||
// inspect your queues with QueueInspect. It's unwise to mix Publish and
|
||||
// Consume to let TCP do its job well.
|
||||
|
||||
SSL/TLS - Secure connections
|
||||
|
||||
When Dial encounters an amqps:// scheme, it will use the zero value of a
|
||||
tls.Config. This will only perform server certificate and host verification.
|
||||
|
||||
Use DialTLS when you wish to provide a client certificate (recommended),
|
||||
include a private certificate authority's certificate in the cert chain for
|
||||
server validity, or run insecure by not verifying the server certificate dial
|
||||
your own connection. DialTLS will use the provided tls.Config when it
|
||||
encounters an amqps:// scheme and will dial a plain connection when it
|
||||
encounters an amqp:// scheme.
|
||||
|
||||
SSL/TLS in RabbitMQ is documented here: http://www.rabbitmq.com/ssl.html
|
||||
|
||||
*/
|
||||
package amqp
|
395
vendor/github.com/streadway/amqp/examples_test.go
generated
vendored
395
vendor/github.com/streadway/amqp/examples_test.go
generated
vendored
@ -1,395 +0,0 @@
|
||||
package amqp_test
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"github.com/streadway/amqp"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleConfig_timeout() {
|
||||
// Provide your own anonymous Dial function that delgates to net.DialTimout
|
||||
// for custom timeouts
|
||||
|
||||
conn, err := amqp.DialConfig("amqp:///", amqp.Config{
|
||||
Dial: func(network, addr string) (net.Conn, error) {
|
||||
return net.DialTimeout(network, addr, 2*time.Second)
|
||||
},
|
||||
})
|
||||
|
||||
log.Printf("conn: %v, err: %v", conn, err)
|
||||
}
|
||||
|
||||
func ExampleDialTLS() {
|
||||
// To get started with SSL/TLS follow the instructions for adding SSL/TLS
|
||||
// support in RabbitMQ with a private certificate authority here:
|
||||
//
|
||||
// http://www.rabbitmq.com/ssl.html
|
||||
//
|
||||
// Then in your rabbitmq.config, disable the plain AMQP port, verify clients
|
||||
// and fail if no certificate is presented with the following:
|
||||
//
|
||||
// [
|
||||
// {rabbit, [
|
||||
// {tcp_listeners, []}, % listens on 127.0.0.1:5672
|
||||
// {ssl_listeners, [5671]}, % listens on 0.0.0.0:5671
|
||||
// {ssl_options, [{cacertfile,"/path/to/your/testca/cacert.pem"},
|
||||
// {certfile,"/path/to/your/server/cert.pem"},
|
||||
// {keyfile,"/path/to/your/server/key.pem"},
|
||||
// {verify,verify_peer},
|
||||
// {fail_if_no_peer_cert,true}]}
|
||||
// ]}
|
||||
// ].
|
||||
|
||||
cfg := new(tls.Config)
|
||||
|
||||
// The self-signing certificate authority's certificate must be included in
|
||||
// the RootCAs to be trusted so that the server certificate can be verified.
|
||||
//
|
||||
// Alternatively to adding it to the tls.Config you can add the CA's cert to
|
||||
// your system's root CAs. The tls package will use the system roots
|
||||
// specific to each support OS. Under OS X, add (drag/drop) your cacert.pem
|
||||
// file to the 'Certificates' section of KeyChain.app to add and always
|
||||
// trust.
|
||||
//
|
||||
// Or with the command line add and trust the DER encoded certificate:
|
||||
//
|
||||
// security add-certificate testca/cacert.cer
|
||||
// security add-trusted-cert testca/cacert.cer
|
||||
//
|
||||
// If you depend on the system root CAs, then use nil for the RootCAs field
|
||||
// so the system roots will be loaded.
|
||||
|
||||
cfg.RootCAs = x509.NewCertPool()
|
||||
|
||||
if ca, err := ioutil.ReadFile("testca/cacert.pem"); err == nil {
|
||||
cfg.RootCAs.AppendCertsFromPEM(ca)
|
||||
}
|
||||
|
||||
// Move the client cert and key to a location specific to your application
|
||||
// and load them here.
|
||||
|
||||
if cert, err := tls.LoadX509KeyPair("client/cert.pem", "client/key.pem"); err == nil {
|
||||
cfg.Certificates = append(cfg.Certificates, cert)
|
||||
}
|
||||
|
||||
// Server names are validated by the crypto/tls package, so the server
|
||||
// certificate must be made for the hostname in the URL. Find the commonName
|
||||
// (CN) and make sure the hostname in the URL matches this common name. Per
|
||||
// the RabbitMQ instructions for a self-signed cert, this defautls to the
|
||||
// current hostname.
|
||||
//
|
||||
// openssl x509 -noout -in server/cert.pem -subject
|
||||
//
|
||||
// If your server name in your certificate is different than the host you are
|
||||
// connecting to, set the hostname used for verification in
|
||||
// ServerName field of the tls.Config struct.
|
||||
|
||||
conn, err := amqp.DialTLS("amqps://server-name-from-certificate/", cfg)
|
||||
|
||||
log.Printf("conn: %v, err: %v", conn, err)
|
||||
}
|
||||
|
||||
func ExampleChannel_Confirm_bridge() {
|
||||
// This example acts as a bridge, shoveling all messages sent from the source
|
||||
// exchange "log" to destination exchange "log".
|
||||
|
||||
// Confirming publishes can help from overproduction and ensure every message
|
||||
// is delivered.
|
||||
|
||||
// Setup the source of the store and forward
|
||||
source, err := amqp.Dial("amqp://source/")
|
||||
if err != nil {
|
||||
log.Fatalf("connection.open source: %s", err)
|
||||
}
|
||||
defer source.Close()
|
||||
|
||||
chs, err := source.Channel()
|
||||
if err != nil {
|
||||
log.Fatalf("channel.open source: %s", err)
|
||||
}
|
||||
|
||||
if err := chs.ExchangeDeclare("log", "topic", true, false, false, false, nil); err != nil {
|
||||
log.Fatalf("exchange.declare destination: %s", err)
|
||||
}
|
||||
|
||||
if _, err := chs.QueueDeclare("remote-tee", true, true, false, false, nil); err != nil {
|
||||
log.Fatalf("queue.declare source: %s", err)
|
||||
}
|
||||
|
||||
if err := chs.QueueBind("remote-tee", "#", "logs", false, nil); err != nil {
|
||||
log.Fatalf("queue.bind source: %s", err)
|
||||
}
|
||||
|
||||
shovel, err := chs.Consume("remote-tee", "shovel", false, false, false, false, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("basic.consume source: %s", err)
|
||||
}
|
||||
|
||||
// Setup the destination of the store and forward
|
||||
destination, err := amqp.Dial("amqp://destination/")
|
||||
if err != nil {
|
||||
log.Fatalf("connection.open destination: %s", err)
|
||||
}
|
||||
defer destination.Close()
|
||||
|
||||
chd, err := destination.Channel()
|
||||
if err != nil {
|
||||
log.Fatalf("channel.open destination: %s", err)
|
||||
}
|
||||
|
||||
if err := chd.ExchangeDeclare("log", "topic", true, false, false, false, nil); err != nil {
|
||||
log.Fatalf("exchange.declare destination: %s", err)
|
||||
}
|
||||
|
||||
// Buffer of 1 for our single outstanding publishing
|
||||
pubAcks, pubNacks := chd.NotifyConfirm(make(chan uint64, 1), make(chan uint64, 1))
|
||||
|
||||
if err := chd.Confirm(false); err != nil {
|
||||
log.Fatalf("confirm.select destination: %s", err)
|
||||
}
|
||||
|
||||
// Now pump the messages, one by one, a smarter implementation
|
||||
// would batch the deliveries and use multiple ack/nacks
|
||||
for {
|
||||
msg, ok := <-shovel
|
||||
if !ok {
|
||||
log.Fatalf("source channel closed, see the reconnect example for handling this")
|
||||
}
|
||||
|
||||
err = chd.Publish("logs", msg.RoutingKey, false, false, amqp.Publishing{
|
||||
// Copy all the properties
|
||||
ContentType: msg.ContentType,
|
||||
ContentEncoding: msg.ContentEncoding,
|
||||
DeliveryMode: msg.DeliveryMode,
|
||||
Priority: msg.Priority,
|
||||
CorrelationId: msg.CorrelationId,
|
||||
ReplyTo: msg.ReplyTo,
|
||||
Expiration: msg.Expiration,
|
||||
MessageId: msg.MessageId,
|
||||
Timestamp: msg.Timestamp,
|
||||
Type: msg.Type,
|
||||
UserId: msg.UserId,
|
||||
AppId: msg.AppId,
|
||||
|
||||
// Custom headers
|
||||
Headers: msg.Headers,
|
||||
|
||||
// And the body
|
||||
Body: msg.Body,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
msg.Nack(false, false)
|
||||
log.Fatalf("basic.publish destination: %s", msg)
|
||||
}
|
||||
|
||||
// only ack the source delivery when the destination acks the publishing
|
||||
// here you could check for delivery order by keeping a local state of
|
||||
// expected delivery tags
|
||||
select {
|
||||
case <-pubAcks:
|
||||
msg.Ack(false)
|
||||
case <-pubNacks:
|
||||
msg.Nack(false, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleChannel_Consume() {
|
||||
// Connects opens an AMQP connection from the credentials in the URL.
|
||||
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
|
||||
if err != nil {
|
||||
log.Fatalf("connection.open: %s", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c, err := conn.Channel()
|
||||
if err != nil {
|
||||
log.Fatalf("channel.open: %s", err)
|
||||
}
|
||||
|
||||
// We declare our topology on both the publisher and consumer to ensure they
|
||||
// are the same. This is part of AMQP being a programmable messaging model.
|
||||
//
|
||||
// See the Channel.Publish example for the complimentary declare.
|
||||
err = c.ExchangeDeclare("logs", "topic", true, false, false, false, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("exchange.declare: %s", err)
|
||||
}
|
||||
|
||||
// Establish our queue topologies that we are responsible for
|
||||
type bind struct {
|
||||
queue string
|
||||
key string
|
||||
}
|
||||
|
||||
bindings := []bind{
|
||||
bind{"page", "alert"},
|
||||
bind{"email", "info"},
|
||||
bind{"firehose", "#"},
|
||||
}
|
||||
|
||||
for _, b := range bindings {
|
||||
_, err = c.QueueDeclare(b.queue, true, false, false, false, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("queue.declare: %v", err)
|
||||
}
|
||||
|
||||
err = c.QueueBind(b.queue, b.key, "logs", false, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("queue.bind: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Set our quality of service. Since we're sharing 3 consumers on the same
|
||||
// channel, we want at least 3 messages in flight.
|
||||
err = c.Qos(3, 0, false)
|
||||
if err != nil {
|
||||
log.Fatalf("basic.qos: %v", err)
|
||||
}
|
||||
|
||||
// Establish our consumers that have different responsibilities. Our first
|
||||
// two queues do not ack the messages on the server, so require to be acked
|
||||
// on the client.
|
||||
|
||||
pages, err := c.Consume("page", "pager", false, false, false, false, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("basic.consume: %v", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for log := range pages {
|
||||
// ... this consumer is responsible for sending pages per log
|
||||
log.Ack(false)
|
||||
}
|
||||
}()
|
||||
|
||||
// Notice how the concern for which messages arrive here are in the AMQP
|
||||
// topology and not in the queue. We let the server pick a consumer tag this
|
||||
// time.
|
||||
|
||||
emails, err := c.Consume("email", "", false, false, false, false, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("basic.consume: %v", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for log := range emails {
|
||||
// ... this consumer is responsible for sending emails per log
|
||||
log.Ack(false)
|
||||
}
|
||||
}()
|
||||
|
||||
// This consumer requests that every message is acknowledged as soon as it's
|
||||
// delivered.
|
||||
|
||||
firehose, err := c.Consume("firehose", "", true, false, false, false, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("basic.consume: %v", err)
|
||||
}
|
||||
|
||||
// To show how to process the items in parallel, we'll use a work pool.
|
||||
for i := 0; i < runtime.NumCPU(); i++ {
|
||||
go func(work <-chan amqp.Delivery) {
|
||||
for _ = range work {
|
||||
// ... this consumer pulls from the firehose and doesn't need to acknowledge
|
||||
}
|
||||
}(firehose)
|
||||
}
|
||||
|
||||
// Wait until you're ready to finish, could be a signal handler here.
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
// Cancelling a consumer by name will finish the range and gracefully end the
|
||||
// goroutine
|
||||
err = c.Cancel("pager", false)
|
||||
if err != nil {
|
||||
log.Fatalf("basic.cancel: %v", err)
|
||||
}
|
||||
|
||||
// deferred closing the Connection will also finish the consumer's ranges of
|
||||
// their delivery chans. If you need every delivery to be processed, make
|
||||
// sure to wait for all consumers goroutines to finish before exiting your
|
||||
// process.
|
||||
}
|
||||
|
||||
func ExampleChannel_Publish() {
|
||||
// Connects opens an AMQP connection from the credentials in the URL.
|
||||
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
|
||||
if err != nil {
|
||||
log.Fatalf("connection.open: %s", err)
|
||||
}
|
||||
|
||||
// This waits for a server acknowledgment which means the sockets will have
|
||||
// flushed all outbound publishings prior to returning. It's important to
|
||||
// block on Close to not lose any publishings.
|
||||
defer conn.Close()
|
||||
|
||||
c, err := conn.Channel()
|
||||
if err != nil {
|
||||
log.Fatalf("channel.open: %s", err)
|
||||
}
|
||||
|
||||
// We declare our topology on both the publisher and consumer to ensure they
|
||||
// are the same. This is part of AMQP being a programmable messaging model.
|
||||
//
|
||||
// See the Channel.Consume example for the complimentary declare.
|
||||
err = c.ExchangeDeclare("logs", "topic", true, false, false, false, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("exchange.declare: %v", err)
|
||||
}
|
||||
|
||||
// Prepare this message to be persistent. Your publishing requirements may
|
||||
// be different.
|
||||
msg := amqp.Publishing{
|
||||
DeliveryMode: amqp.Persistent,
|
||||
Timestamp: time.Now(),
|
||||
ContentType: "text/plain",
|
||||
Body: []byte("Go Go AMQP!"),
|
||||
}
|
||||
|
||||
// This is not a mandatory delivery, so it will be dropped if there are no
|
||||
// queues bound to the logs exchange.
|
||||
err = c.Publish("logs", "info", false, false, msg)
|
||||
if err != nil {
|
||||
// Since publish is asynchronous this can happen if the network connection
|
||||
// is reset or if the server has run out of resources.
|
||||
log.Fatalf("basic.publish: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func publishAllTheThings(conn *amqp.Connection) {
|
||||
// ... snarf snarf, barf barf
|
||||
}
|
||||
|
||||
func ExampleConnection_NotifyBlocked() {
|
||||
// Simply logs when the server throttles the TCP connection for publishers
|
||||
|
||||
// Test this by tuning your server to have a low memory watermark:
|
||||
// rabbitmqctl set_vm_memory_high_watermark 0.00000001
|
||||
|
||||
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
|
||||
if err != nil {
|
||||
log.Fatalf("connection.open: %s", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
blockings := conn.NotifyBlocked(make(chan amqp.Blocking))
|
||||
go func() {
|
||||
for b := range blockings {
|
||||
if b.Active {
|
||||
log.Printf("TCP blocked: %q", b.Reason)
|
||||
} else {
|
||||
log.Printf("TCP unblocked")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Your application domain channel setup publishings
|
||||
publishAllTheThings(conn)
|
||||
}
|
2
vendor/github.com/streadway/amqp/gen.sh
generated
vendored
2
vendor/github.com/streadway/amqp/gen.sh
generated
vendored
@ -1,2 +0,0 @@
|
||||
#!/bin/sh
|
||||
go run spec/gen.go < spec/amqp0-9-1.stripped.extended.xml | gofmt > spec091.go
|
1772
vendor/github.com/streadway/amqp/integration_test.go
generated
vendored
1772
vendor/github.com/streadway/amqp/integration_test.go
generated
vendored
File diff suppressed because it is too large
Load Diff
444
vendor/github.com/streadway/amqp/read.go
generated
vendored
444
vendor/github.com/streadway/amqp/read.go
generated
vendored
@ -1,444 +0,0 @@
|
||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Source code and contact info at http://github.com/streadway/amqp
|
||||
|
||||
package amqp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
Reads a frame from an input stream and returns an interface that can be cast into
|
||||
one of the following:
|
||||
|
||||
methodFrame
|
||||
PropertiesFrame
|
||||
bodyFrame
|
||||
heartbeatFrame
|
||||
|
||||
2.3.5 frame Details
|
||||
|
||||
All frames consist of a header (7 octets), a payload of arbitrary size, and a
|
||||
'frame-end' octet that detects malformed frames:
|
||||
|
||||
0 1 3 7 size+7 size+8
|
||||
+------+---------+-------------+ +------------+ +-----------+
|
||||
| type | channel | size | | payload | | frame-end |
|
||||
+------+---------+-------------+ +------------+ +-----------+
|
||||
octet short long size octets octet
|
||||
|
||||
To read a frame, we:
|
||||
1. Read the header and check the frame type and channel.
|
||||
2. Depending on the frame type, we read the payload and process it.
|
||||
3. Read the frame end octet.
|
||||
|
||||
In realistic implementations where performance is a concern, we would use
|
||||
“read-ahead buffering” or
|
||||
|
||||
“gathering reads” to avoid doing three separate system calls to read a frame.
|
||||
*/
|
||||
func (me *reader) ReadFrame() (frame frame, err error) {
|
||||
var scratch [7]byte
|
||||
|
||||
if _, err = io.ReadFull(me.r, scratch[:7]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
typ := uint8(scratch[0])
|
||||
channel := binary.BigEndian.Uint16(scratch[1:3])
|
||||
size := binary.BigEndian.Uint32(scratch[3:7])
|
||||
|
||||
switch typ {
|
||||
case frameMethod:
|
||||
if frame, err = me.parseMethodFrame(channel, size); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case frameHeader:
|
||||
if frame, err = me.parseHeaderFrame(channel, size); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case frameBody:
|
||||
if frame, err = me.parseBodyFrame(channel, size); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case frameHeartbeat:
|
||||
if frame, err = me.parseHeartbeatFrame(channel, size); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, ErrFrame
|
||||
}
|
||||
|
||||
if _, err = io.ReadFull(me.r, scratch[:1]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if scratch[0] != frameEnd {
|
||||
return nil, ErrFrame
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func readShortstr(r io.Reader) (v string, err error) {
|
||||
var length uint8
|
||||
if err = binary.Read(r, binary.BigEndian, &length); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
bytes := make([]byte, length)
|
||||
if _, err = io.ReadFull(r, bytes); err != nil {
|
||||
return
|
||||
}
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
func readLongstr(r io.Reader) (v string, err error) {
|
||||
var length uint32
|
||||
if err = binary.Read(r, binary.BigEndian, &length); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
bytes := make([]byte, length)
|
||||
if _, err = io.ReadFull(r, bytes); err != nil {
|
||||
return
|
||||
}
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
func readDecimal(r io.Reader) (v Decimal, err error) {
|
||||
if err = binary.Read(r, binary.BigEndian, &v.Scale); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Read(r, binary.BigEndian, &v.Value); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func readFloat32(r io.Reader) (v float32, err error) {
|
||||
if err = binary.Read(r, binary.BigEndian, &v); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func readFloat64(r io.Reader) (v float64, err error) {
|
||||
if err = binary.Read(r, binary.BigEndian, &v); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func readTimestamp(r io.Reader) (v time.Time, err error) {
|
||||
var sec int64
|
||||
if err = binary.Read(r, binary.BigEndian, &sec); err != nil {
|
||||
return
|
||||
}
|
||||
return time.Unix(sec, 0), nil
|
||||
}
|
||||
|
||||
/*
|
||||
'A': []interface{}
|
||||
'D': Decimal
|
||||
'F': Table
|
||||
'I': int32
|
||||
'S': string
|
||||
'T': time.Time
|
||||
'V': nil
|
||||
'b': byte
|
||||
'd': float64
|
||||
'f': float32
|
||||
'l': int64
|
||||
's': int16
|
||||
't': bool
|
||||
'x': []byte
|
||||
*/
|
||||
func readField(r io.Reader) (v interface{}, err error) {
|
||||
var typ byte
|
||||
if err = binary.Read(r, binary.BigEndian, &typ); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case 't':
|
||||
var value uint8
|
||||
if err = binary.Read(r, binary.BigEndian, &value); err != nil {
|
||||
return
|
||||
}
|
||||
return (value != 0), nil
|
||||
|
||||
case 'b':
|
||||
var value [1]byte
|
||||
if _, err = io.ReadFull(r, value[0:1]); err != nil {
|
||||
return
|
||||
}
|
||||
return value[0], nil
|
||||
|
||||
case 's':
|
||||
var value int16
|
||||
if err = binary.Read(r, binary.BigEndian, &value); err != nil {
|
||||
return
|
||||
}
|
||||
return value, nil
|
||||
|
||||
case 'I':
|
||||
var value int32
|
||||
if err = binary.Read(r, binary.BigEndian, &value); err != nil {
|
||||
return
|
||||
}
|
||||
return value, nil
|
||||
|
||||
case 'l':
|
||||
var value int64
|
||||
if err = binary.Read(r, binary.BigEndian, &value); err != nil {
|
||||
return
|
||||
}
|
||||
return value, nil
|
||||
|
||||
case 'f':
|
||||
var value float32
|
||||
if err = binary.Read(r, binary.BigEndian, &value); err != nil {
|
||||
return
|
||||
}
|
||||
return value, nil
|
||||
|
||||
case 'd':
|
||||
var value float64
|
||||
if err = binary.Read(r, binary.BigEndian, &value); err != nil {
|
||||
return
|
||||
}
|
||||
return value, nil
|
||||
|
||||
case 'D':
|
||||
return readDecimal(r)
|
||||
|
||||
case 'S':
|
||||
return readLongstr(r)
|
||||
|
||||
case 'A':
|
||||
return readArray(r)
|
||||
|
||||
case 'T':
|
||||
return readTimestamp(r)
|
||||
|
||||
case 'F':
|
||||
return readTable(r)
|
||||
|
||||
case 'x':
|
||||
var len int32
|
||||
if err = binary.Read(r, binary.BigEndian, &len); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
value := make([]byte, len)
|
||||
if _, err = io.ReadFull(r, value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return value, err
|
||||
|
||||
case 'V':
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, ErrSyntax
|
||||
}
|
||||
|
||||
/*
|
||||
Field tables are long strings that contain packed name-value pairs. The
|
||||
name-value pairs are encoded as short string defining the name, and octet
|
||||
defining the values type and then the value itself. The valid field types for
|
||||
tables are an extension of the native integer, bit, string, and timestamp
|
||||
types, and are shown in the grammar. Multi-octet integer fields are always
|
||||
held in network byte order.
|
||||
*/
|
||||
func readTable(r io.Reader) (table Table, err error) {
|
||||
var nested bytes.Buffer
|
||||
var str string
|
||||
|
||||
if str, err = readLongstr(r); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
nested.Write([]byte(str))
|
||||
|
||||
table = make(Table)
|
||||
|
||||
for nested.Len() > 0 {
|
||||
var key string
|
||||
var value interface{}
|
||||
|
||||
if key, err = readShortstr(&nested); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if value, err = readField(&nested); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
table[key] = value
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func readArray(r io.Reader) ([]interface{}, error) {
|
||||
var size uint32
|
||||
var err error
|
||||
|
||||
if err = binary.Read(r, binary.BigEndian, &size); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lim := &io.LimitedReader{R: r, N: int64(size)}
|
||||
arr := make([]interface{}, 0)
|
||||
var field interface{}
|
||||
|
||||
for {
|
||||
if field, err = readField(lim); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
arr = append(arr, field)
|
||||
}
|
||||
|
||||
return arr, nil
|
||||
}
|
||||
|
||||
// Checks if this bit mask matches the flags bitset
|
||||
func hasProperty(mask uint16, prop int) bool {
|
||||
return int(mask)&prop > 0
|
||||
}
|
||||
|
||||
func (me *reader) parseHeaderFrame(channel uint16, size uint32) (frame frame, err error) {
|
||||
hf := &headerFrame{
|
||||
ChannelId: channel,
|
||||
}
|
||||
|
||||
if err = binary.Read(me.r, binary.BigEndian, &hf.ClassId); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = binary.Read(me.r, binary.BigEndian, &hf.weight); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = binary.Read(me.r, binary.BigEndian, &hf.Size); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var flags uint16
|
||||
|
||||
if err = binary.Read(me.r, binary.BigEndian, &flags); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if hasProperty(flags, flagContentType) {
|
||||
if hf.Properties.ContentType, err = readShortstr(me.r); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagContentEncoding) {
|
||||
if hf.Properties.ContentEncoding, err = readShortstr(me.r); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagHeaders) {
|
||||
if hf.Properties.Headers, err = readTable(me.r); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagDeliveryMode) {
|
||||
if err = binary.Read(me.r, binary.BigEndian, &hf.Properties.DeliveryMode); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagPriority) {
|
||||
if err = binary.Read(me.r, binary.BigEndian, &hf.Properties.Priority); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagCorrelationId) {
|
||||
if hf.Properties.CorrelationId, err = readShortstr(me.r); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagReplyTo) {
|
||||
if hf.Properties.ReplyTo, err = readShortstr(me.r); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagExpiration) {
|
||||
if hf.Properties.Expiration, err = readShortstr(me.r); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagMessageId) {
|
||||
if hf.Properties.MessageId, err = readShortstr(me.r); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagTimestamp) {
|
||||
if hf.Properties.Timestamp, err = readTimestamp(me.r); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagType) {
|
||||
if hf.Properties.Type, err = readShortstr(me.r); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagUserId) {
|
||||
if hf.Properties.UserId, err = readShortstr(me.r); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagAppId) {
|
||||
if hf.Properties.AppId, err = readShortstr(me.r); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(flags, flagReserved1) {
|
||||
if hf.Properties.reserved1, err = readShortstr(me.r); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return hf, nil
|
||||
}
|
||||
|
||||
func (me *reader) parseBodyFrame(channel uint16, size uint32) (frame frame, err error) {
|
||||
bf := &bodyFrame{
|
||||
ChannelId: channel,
|
||||
Body: make([]byte, size),
|
||||
}
|
||||
|
||||
if _, err = io.ReadFull(me.r, bf.Body); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return bf, nil
|
||||
}
|
||||
|
||||
func (me *reader) parseHeartbeatFrame(channel uint16, size uint32) (frame frame, err error) {
|
||||
hf := &heartbeatFrame{
|
||||
ChannelId: channel,
|
||||
}
|
||||
|
||||
if size > 0 {
|
||||
panic("Heartbeats should not have a payload")
|
||||
}
|
||||
|
||||
return hf, nil
|
||||
}
|
113
vendor/github.com/streadway/amqp/reconnect_test.go
generated
vendored
113
vendor/github.com/streadway/amqp/reconnect_test.go
generated
vendored
@ -1,113 +0,0 @@
|
||||
package amqp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/streadway/amqp"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Every connection should declare the topology they expect
|
||||
func setup(url, queue string) (*amqp.Connection, *amqp.Channel, error) {
|
||||
conn, err := amqp.Dial(url)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ch, err := conn.Channel()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if _, err := ch.QueueDeclare(queue, false, true, false, false, nil); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return conn, ch, nil
|
||||
}
|
||||
|
||||
func consume(url, queue string) (*amqp.Connection, <-chan amqp.Delivery, error) {
|
||||
conn, ch, err := setup(url, queue)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Indicate we only want 1 message to acknowledge at a time.
|
||||
if err := ch.Qos(1, 0, false); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Exclusive consumer
|
||||
deliveries, err := ch.Consume(queue, "", false, true, false, false, nil)
|
||||
|
||||
return conn, deliveries, err
|
||||
}
|
||||
|
||||
func ExampleConnection_reconnect() {
|
||||
if url := os.Getenv("AMQP_URL"); url != "" {
|
||||
queue := "example.reconnect"
|
||||
|
||||
// The connection/channel for publishing to interleave the ingress messages
|
||||
// between reconnects, shares the same topology as the consumer. If we rather
|
||||
// sent all messages up front, the first consumer would receive every message.
|
||||
// We would rather show how the messages are not lost between reconnects.
|
||||
_, pub, err := setup(url, queue)
|
||||
if err != nil {
|
||||
fmt.Println("err publisher setup:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Purge the queue from the publisher side to establish initial state
|
||||
if _, err := pub.QueuePurge(queue, false); err != nil {
|
||||
fmt.Println("err purge:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Reconnect simulation, should be for { ... } in production
|
||||
for i := 1; i <= 3; i++ {
|
||||
fmt.Println("connect")
|
||||
|
||||
conn, deliveries, err := consume(url, queue)
|
||||
if err != nil {
|
||||
fmt.Println("err consume:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Simulate a producer on a different connection showing that consumers
|
||||
// continue where they were left off after each reconnect.
|
||||
if err := pub.Publish("", queue, false, false, amqp.Publishing{
|
||||
Body: []byte(fmt.Sprintf("%d", i)),
|
||||
}); err != nil {
|
||||
fmt.Println("err publish:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Simulates a consumer that when the range finishes, will setup a new
|
||||
// session and begin ranging over the deliveries again.
|
||||
for msg := range deliveries {
|
||||
fmt.Println(string(msg.Body))
|
||||
msg.Ack(false)
|
||||
|
||||
// Simulate an error like a server restart, loss of route or operator
|
||||
// intervention that results in the connection terminating
|
||||
go conn.Close()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// pass with expected output when not running in an integration
|
||||
// environment.
|
||||
fmt.Println("connect")
|
||||
fmt.Println("1")
|
||||
fmt.Println("connect")
|
||||
fmt.Println("2")
|
||||
fmt.Println("connect")
|
||||
fmt.Println("3")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// connect
|
||||
// 1
|
||||
// connect
|
||||
// 2
|
||||
// connect
|
||||
// 3
|
||||
}
|
64
vendor/github.com/streadway/amqp/return.go
generated
vendored
64
vendor/github.com/streadway/amqp/return.go
generated
vendored
@ -1,64 +0,0 @@
|
||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Source code and contact info at http://github.com/streadway/amqp
|
||||
|
||||
package amqp
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Return captures a flattened struct of fields returned by the server when a
|
||||
// Publishing is unable to be delivered either due to the `mandatory` flag set
|
||||
// and no route found, or `immediate` flag set and no free consumer.
|
||||
type Return struct {
|
||||
ReplyCode uint16 // reason
|
||||
ReplyText string // description
|
||||
Exchange string // basic.publish exchange
|
||||
RoutingKey string // basic.publish routing key
|
||||
|
||||
// Properties
|
||||
ContentType string // MIME content type
|
||||
ContentEncoding string // MIME content encoding
|
||||
Headers Table // Application or header exchange table
|
||||
DeliveryMode uint8 // queue implemention use - non-persistent (1) or persistent (2)
|
||||
Priority uint8 // queue implementation use - 0 to 9
|
||||
CorrelationId string // application use - correlation identifier
|
||||
ReplyTo string // application use - address to to reply to (ex: RPC)
|
||||
Expiration string // implementation use - message expiration spec
|
||||
MessageId string // application use - message identifier
|
||||
Timestamp time.Time // application use - message timestamp
|
||||
Type string // application use - message type name
|
||||
UserId string // application use - creating user id
|
||||
AppId string // application use - creating application
|
||||
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func newReturn(msg basicReturn) *Return {
|
||||
props, body := msg.getContent()
|
||||
|
||||
return &Return{
|
||||
ReplyCode: msg.ReplyCode,
|
||||
ReplyText: msg.ReplyText,
|
||||
Exchange: msg.Exchange,
|
||||
RoutingKey: msg.RoutingKey,
|
||||
|
||||
Headers: props.Headers,
|
||||
ContentType: props.ContentType,
|
||||
ContentEncoding: props.ContentEncoding,
|
||||
DeliveryMode: props.DeliveryMode,
|
||||
Priority: props.Priority,
|
||||
CorrelationId: props.CorrelationId,
|
||||
ReplyTo: props.ReplyTo,
|
||||
Expiration: props.Expiration,
|
||||
MessageId: props.MessageId,
|
||||
Timestamp: props.Timestamp,
|
||||
Type: props.Type,
|
||||
UserId: props.UserId,
|
||||
AppId: props.AppId,
|
||||
|
||||
Body: body,
|
||||
}
|
||||
}
|
71
vendor/github.com/streadway/amqp/shared_test.go
generated
vendored
71
vendor/github.com/streadway/amqp/shared_test.go
generated
vendored
@ -1,71 +0,0 @@
|
||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Source code and contact info at http://github.com/streadway/amqp
|
||||
|
||||
package amqp
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type pipe struct {
|
||||
r *io.PipeReader
|
||||
w *io.PipeWriter
|
||||
}
|
||||
|
||||
func (p pipe) Read(b []byte) (int, error) {
|
||||
return p.r.Read(b)
|
||||
}
|
||||
|
||||
func (p pipe) Write(b []byte) (int, error) {
|
||||
return p.w.Write(b)
|
||||
}
|
||||
|
||||
func (p pipe) Close() error {
|
||||
p.r.Close()
|
||||
p.w.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
type logIO struct {
|
||||
t *testing.T
|
||||
prefix string
|
||||
proxy io.ReadWriteCloser
|
||||
}
|
||||
|
||||
func (me *logIO) Read(p []byte) (n int, err error) {
|
||||
me.t.Logf("%s reading %d\n", me.prefix, len(p))
|
||||
n, err = me.proxy.Read(p)
|
||||
if err != nil {
|
||||
me.t.Logf("%s read %x: %v\n", me.prefix, p[0:n], err)
|
||||
} else {
|
||||
me.t.Logf("%s read:\n%s\n", me.prefix, hex.Dump(p[0:n]))
|
||||
//fmt.Printf("%s read:\n%s\n", me.prefix, hex.Dump(p[0:n]))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (me *logIO) Write(p []byte) (n int, err error) {
|
||||
me.t.Logf("%s writing %d\n", me.prefix, len(p))
|
||||
n, err = me.proxy.Write(p)
|
||||
if err != nil {
|
||||
me.t.Logf("%s write %d, %x: %v\n", me.prefix, len(p), p[0:n], err)
|
||||
} else {
|
||||
me.t.Logf("%s write %d:\n%s", me.prefix, len(p), hex.Dump(p[0:n]))
|
||||
//fmt.Printf("%s write %d:\n%s", me.prefix, len(p), hex.Dump(p[0:n]))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (me *logIO) Close() (err error) {
|
||||
err = me.proxy.Close()
|
||||
if err != nil {
|
||||
me.t.Logf("%s close : %v\n", me.prefix, err)
|
||||
} else {
|
||||
me.t.Logf("%s close\n", me.prefix, err)
|
||||
}
|
||||
return
|
||||
}
|
537
vendor/github.com/streadway/amqp/spec/amqp0-9-1.stripped.extended.xml
generated
vendored
537
vendor/github.com/streadway/amqp/spec/amqp0-9-1.stripped.extended.xml
generated
vendored
@ -1,537 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<!--
|
||||
WARNING: Modified from the official 0-9-1 specification XML by
|
||||
the addition of:
|
||||
confirm.select and confirm.select-ok,
|
||||
exchange.bind and exchange.bind-ok,
|
||||
exchange.unbind and exchange.unbind-ok,
|
||||
basic.nack,
|
||||
the ability for the Server to send basic.ack, basic.nack and
|
||||
basic.cancel to the client, and
|
||||
the un-deprecation of exchange.declare{auto-delete} and exchange.declare{internal}
|
||||
|
||||
Modifications are (c) 2010-2013 VMware, Inc. and may be distributed
|
||||
under the same BSD license as below.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Copyright (c) 2009 AMQP Working Group.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. 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.
|
||||
3. The name of the author may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
|
||||
-->
|
||||
<amqp major="0" minor="9" revision="1" port="5672">
|
||||
<constant name="frame-method" value="1"/>
|
||||
<constant name="frame-header" value="2"/>
|
||||
<constant name="frame-body" value="3"/>
|
||||
<constant name="frame-heartbeat" value="8"/>
|
||||
<constant name="frame-min-size" value="4096"/>
|
||||
<constant name="frame-end" value="206"/>
|
||||
<constant name="reply-success" value="200"/>
|
||||
<constant name="content-too-large" value="311" class="soft-error"/>
|
||||
<constant name="no-route" value="312" class = "soft-error">
|
||||
<doc>
|
||||
Errata: Section 1.2 ought to define an exception 312 "No route", which used to
|
||||
exist in 0-9 and is what RabbitMQ sends back with 'basic.return' when a
|
||||
'mandatory' message cannot be delivered to any queue.
|
||||
</doc>
|
||||
</constant>
|
||||
<constant name="no-consumers" value="313" class="soft-error"/>
|
||||
<constant name="connection-forced" value="320" class="hard-error"/>
|
||||
<constant name="invalid-path" value="402" class="hard-error"/>
|
||||
<constant name="access-refused" value="403" class="soft-error"/>
|
||||
<constant name="not-found" value="404" class="soft-error"/>
|
||||
<constant name="resource-locked" value="405" class="soft-error"/>
|
||||
<constant name="precondition-failed" value="406" class="soft-error"/>
|
||||
<constant name="frame-error" value="501" class="hard-error"/>
|
||||
<constant name="syntax-error" value="502" class="hard-error"/>
|
||||
<constant name="command-invalid" value="503" class="hard-error"/>
|
||||
<constant name="channel-error" value="504" class="hard-error"/>
|
||||
<constant name="unexpected-frame" value="505" class="hard-error"/>
|
||||
<constant name="resource-error" value="506" class="hard-error"/>
|
||||
<constant name="not-allowed" value="530" class="hard-error"/>
|
||||
<constant name="not-implemented" value="540" class="hard-error"/>
|
||||
<constant name="internal-error" value="541" class="hard-error"/>
|
||||
<domain name="class-id" type="short"/>
|
||||
<domain name="consumer-tag" type="shortstr"/>
|
||||
<domain name="delivery-tag" type="longlong"/>
|
||||
<domain name="exchange-name" type="shortstr">
|
||||
<assert check="length" value="127"/>
|
||||
<assert check="regexp" value="^[a-zA-Z0-9-_.:]*$"/>
|
||||
</domain>
|
||||
<domain name="method-id" type="short"/>
|
||||
<domain name="no-ack" type="bit"/>
|
||||
<domain name="no-local" type="bit"/>
|
||||
<domain name="no-wait" type="bit"/>
|
||||
<domain name="path" type="shortstr">
|
||||
<assert check="notnull"/>
|
||||
<assert check="length" value="127"/>
|
||||
</domain>
|
||||
<domain name="peer-properties" type="table"/>
|
||||
<domain name="queue-name" type="shortstr">
|
||||
<assert check="length" value="127"/>
|
||||
<assert check="regexp" value="^[a-zA-Z0-9-_.:]*$"/>
|
||||
</domain>
|
||||
<domain name="redelivered" type="bit"/>
|
||||
<domain name="message-count" type="long"/>
|
||||
<domain name="reply-code" type="short">
|
||||
<assert check="notnull"/>
|
||||
</domain>
|
||||
<domain name="reply-text" type="shortstr">
|
||||
<assert check="notnull"/>
|
||||
</domain>
|
||||
<domain name="bit" type="bit"/>
|
||||
<domain name="octet" type="octet"/>
|
||||
<domain name="short" type="short"/>
|
||||
<domain name="long" type="long"/>
|
||||
<domain name="longlong" type="longlong"/>
|
||||
<domain name="shortstr" type="shortstr"/>
|
||||
<domain name="longstr" type="longstr"/>
|
||||
<domain name="timestamp" type="timestamp"/>
|
||||
<domain name="table" type="table"/>
|
||||
<class name="connection" handler="connection" index="10">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<method name="start" synchronous="1" index="10">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<response name="start-ok"/>
|
||||
<field name="version-major" domain="octet"/>
|
||||
<field name="version-minor" domain="octet"/>
|
||||
<field name="server-properties" domain="peer-properties"/>
|
||||
<field name="mechanisms" domain="longstr">
|
||||
<assert check="notnull"/>
|
||||
</field>
|
||||
<field name="locales" domain="longstr">
|
||||
<assert check="notnull"/>
|
||||
</field>
|
||||
</method>
|
||||
<method name="start-ok" synchronous="1" index="11">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<field name="client-properties" domain="peer-properties"/>
|
||||
<field name="mechanism" domain="shortstr">
|
||||
<assert check="notnull"/>
|
||||
</field>
|
||||
<field name="response" domain="longstr">
|
||||
<assert check="notnull"/>
|
||||
</field>
|
||||
<field name="locale" domain="shortstr">
|
||||
<assert check="notnull"/>
|
||||
</field>
|
||||
</method>
|
||||
<method name="secure" synchronous="1" index="20">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<response name="secure-ok"/>
|
||||
<field name="challenge" domain="longstr"/>
|
||||
</method>
|
||||
<method name="secure-ok" synchronous="1" index="21">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<field name="response" domain="longstr">
|
||||
<assert check="notnull"/>
|
||||
</field>
|
||||
</method>
|
||||
<method name="tune" synchronous="1" index="30">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<response name="tune-ok"/>
|
||||
<field name="channel-max" domain="short"/>
|
||||
<field name="frame-max" domain="long"/>
|
||||
<field name="heartbeat" domain="short"/>
|
||||
</method>
|
||||
<method name="tune-ok" synchronous="1" index="31">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<field name="channel-max" domain="short">
|
||||
<assert check="notnull"/>
|
||||
<assert check="le" method="tune" field="channel-max"/>
|
||||
</field>
|
||||
<field name="frame-max" domain="long"/>
|
||||
<field name="heartbeat" domain="short"/>
|
||||
</method>
|
||||
<method name="open" synchronous="1" index="40">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="open-ok"/>
|
||||
<field name="virtual-host" domain="path"/>
|
||||
<field name="reserved-1" type="shortstr" reserved="1"/>
|
||||
<field name="reserved-2" type="bit" reserved="1"/>
|
||||
</method>
|
||||
<method name="open-ok" synchronous="1" index="41">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<field name="reserved-1" type="shortstr" reserved="1"/>
|
||||
</method>
|
||||
<method name="close" synchronous="1" index="50">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="close-ok"/>
|
||||
<field name="reply-code" domain="reply-code"/>
|
||||
<field name="reply-text" domain="reply-text"/>
|
||||
<field name="class-id" domain="class-id"/>
|
||||
<field name="method-id" domain="method-id"/>
|
||||
</method>
|
||||
<method name="close-ok" synchronous="1" index="51">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<chassis name="server" implement="MUST"/>
|
||||
</method>
|
||||
<method name="blocked" index="60">
|
||||
<chassis name="server" implement="MAY"/>
|
||||
<field name="reason" type="shortstr"/>
|
||||
</method>
|
||||
<method name="unblocked" index="61">
|
||||
<chassis name="server" implement="MAY"/>
|
||||
</method>
|
||||
</class>
|
||||
<class name="channel" handler="channel" index="20">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<method name="open" synchronous="1" index="10">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="open-ok"/>
|
||||
<field name="reserved-1" type="shortstr" reserved="1"/>
|
||||
</method>
|
||||
<method name="open-ok" synchronous="1" index="11">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<field name="reserved-1" type="longstr" reserved="1"/>
|
||||
</method>
|
||||
<method name="flow" synchronous="1" index="20">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<response name="flow-ok"/>
|
||||
<field name="active" domain="bit"/>
|
||||
</method>
|
||||
<method name="flow-ok" index="21">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<field name="active" domain="bit"/>
|
||||
</method>
|
||||
<method name="close" synchronous="1" index="40">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="close-ok"/>
|
||||
<field name="reply-code" domain="reply-code"/>
|
||||
<field name="reply-text" domain="reply-text"/>
|
||||
<field name="class-id" domain="class-id"/>
|
||||
<field name="method-id" domain="method-id"/>
|
||||
</method>
|
||||
<method name="close-ok" synchronous="1" index="41">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<chassis name="server" implement="MUST"/>
|
||||
</method>
|
||||
</class>
|
||||
<class name="exchange" handler="channel" index="40">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<method name="declare" synchronous="1" index="10">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="declare-ok"/>
|
||||
<field name="reserved-1" type="short" reserved="1"/>
|
||||
<field name="exchange" domain="exchange-name">
|
||||
<assert check="notnull"/>
|
||||
</field>
|
||||
<field name="type" domain="shortstr"/>
|
||||
<field name="passive" domain="bit"/>
|
||||
<field name="durable" domain="bit"/>
|
||||
<field name="auto-delete" domain="bit"/>
|
||||
<field name="internal" domain="bit"/>
|
||||
<field name="no-wait" domain="no-wait"/>
|
||||
<field name="arguments" domain="table"/>
|
||||
</method>
|
||||
<method name="declare-ok" synchronous="1" index="11">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
</method>
|
||||
<method name="delete" synchronous="1" index="20">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="delete-ok"/>
|
||||
<field name="reserved-1" type="short" reserved="1"/>
|
||||
<field name="exchange" domain="exchange-name">
|
||||
<assert check="notnull"/>
|
||||
</field>
|
||||
<field name="if-unused" domain="bit"/>
|
||||
<field name="no-wait" domain="no-wait"/>
|
||||
</method>
|
||||
<method name="delete-ok" synchronous="1" index="21">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
</method>
|
||||
<method name="bind" synchronous="1" index="30">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="bind-ok"/>
|
||||
<field name="reserved-1" type="short" reserved="1"/>
|
||||
<field name="destination" domain="exchange-name"/>
|
||||
<field name="source" domain="exchange-name"/>
|
||||
<field name="routing-key" domain="shortstr"/>
|
||||
<field name="no-wait" domain="no-wait"/>
|
||||
<field name="arguments" domain="table"/>
|
||||
</method>
|
||||
<method name="bind-ok" synchronous="1" index="31">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
</method>
|
||||
<method name="unbind" synchronous="1" index="40">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="unbind-ok"/>
|
||||
<field name="reserved-1" type="short" reserved="1"/>
|
||||
<field name="destination" domain="exchange-name"/>
|
||||
<field name="source" domain="exchange-name"/>
|
||||
<field name="routing-key" domain="shortstr"/>
|
||||
<field name="no-wait" domain="no-wait"/>
|
||||
<field name="arguments" domain="table"/>
|
||||
</method>
|
||||
<method name="unbind-ok" synchronous="1" index="51">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
</method>
|
||||
</class>
|
||||
<class name="queue" handler="channel" index="50">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<method name="declare" synchronous="1" index="10">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="declare-ok"/>
|
||||
<field name="reserved-1" type="short" reserved="1"/>
|
||||
<field name="queue" domain="queue-name"/>
|
||||
<field name="passive" domain="bit"/>
|
||||
<field name="durable" domain="bit"/>
|
||||
<field name="exclusive" domain="bit"/>
|
||||
<field name="auto-delete" domain="bit"/>
|
||||
<field name="no-wait" domain="no-wait"/>
|
||||
<field name="arguments" domain="table"/>
|
||||
</method>
|
||||
<method name="declare-ok" synchronous="1" index="11">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<field name="queue" domain="queue-name">
|
||||
<assert check="notnull"/>
|
||||
</field>
|
||||
<field name="message-count" domain="message-count"/>
|
||||
<field name="consumer-count" domain="long"/>
|
||||
</method>
|
||||
<method name="bind" synchronous="1" index="20">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="bind-ok"/>
|
||||
<field name="reserved-1" type="short" reserved="1"/>
|
||||
<field name="queue" domain="queue-name"/>
|
||||
<field name="exchange" domain="exchange-name"/>
|
||||
<field name="routing-key" domain="shortstr"/>
|
||||
<field name="no-wait" domain="no-wait"/>
|
||||
<field name="arguments" domain="table"/>
|
||||
</method>
|
||||
<method name="bind-ok" synchronous="1" index="21">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
</method>
|
||||
<method name="unbind" synchronous="1" index="50">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="unbind-ok"/>
|
||||
<field name="reserved-1" type="short" reserved="1"/>
|
||||
<field name="queue" domain="queue-name"/>
|
||||
<field name="exchange" domain="exchange-name"/>
|
||||
<field name="routing-key" domain="shortstr"/>
|
||||
<field name="arguments" domain="table"/>
|
||||
</method>
|
||||
<method name="unbind-ok" synchronous="1" index="51">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
</method>
|
||||
<method name="purge" synchronous="1" index="30">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="purge-ok"/>
|
||||
<field name="reserved-1" type="short" reserved="1"/>
|
||||
<field name="queue" domain="queue-name"/>
|
||||
<field name="no-wait" domain="no-wait"/>
|
||||
</method>
|
||||
<method name="purge-ok" synchronous="1" index="31">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<field name="message-count" domain="message-count"/>
|
||||
</method>
|
||||
<method name="delete" synchronous="1" index="40">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="delete-ok"/>
|
||||
<field name="reserved-1" type="short" reserved="1"/>
|
||||
<field name="queue" domain="queue-name"/>
|
||||
<field name="if-unused" domain="bit"/>
|
||||
<field name="if-empty" domain="bit"/>
|
||||
<field name="no-wait" domain="no-wait"/>
|
||||
</method>
|
||||
<method name="delete-ok" synchronous="1" index="41">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<field name="message-count" domain="message-count"/>
|
||||
</method>
|
||||
</class>
|
||||
<class name="basic" handler="channel" index="60">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<chassis name="client" implement="MAY"/>
|
||||
<field name="content-type" domain="shortstr"/>
|
||||
<field name="content-encoding" domain="shortstr"/>
|
||||
<field name="headers" domain="table"/>
|
||||
<field name="delivery-mode" domain="octet"/>
|
||||
<field name="priority" domain="octet"/>
|
||||
<field name="correlation-id" domain="shortstr"/>
|
||||
<field name="reply-to" domain="shortstr"/>
|
||||
<field name="expiration" domain="shortstr"/>
|
||||
<field name="message-id" domain="shortstr"/>
|
||||
<field name="timestamp" domain="timestamp"/>
|
||||
<field name="type" domain="shortstr"/>
|
||||
<field name="user-id" domain="shortstr"/>
|
||||
<field name="app-id" domain="shortstr"/>
|
||||
<field name="reserved" domain="shortstr"/>
|
||||
<method name="qos" synchronous="1" index="10">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="qos-ok"/>
|
||||
<field name="prefetch-size" domain="long"/>
|
||||
<field name="prefetch-count" domain="short"/>
|
||||
<field name="global" domain="bit"/>
|
||||
</method>
|
||||
<method name="qos-ok" synchronous="1" index="11">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
</method>
|
||||
<method name="consume" synchronous="1" index="20">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="consume-ok"/>
|
||||
<field name="reserved-1" type="short" reserved="1"/>
|
||||
<field name="queue" domain="queue-name"/>
|
||||
<field name="consumer-tag" domain="consumer-tag"/>
|
||||
<field name="no-local" domain="no-local"/>
|
||||
<field name="no-ack" domain="no-ack"/>
|
||||
<field name="exclusive" domain="bit"/>
|
||||
<field name="no-wait" domain="no-wait"/>
|
||||
<field name="arguments" domain="table"/>
|
||||
</method>
|
||||
<method name="consume-ok" synchronous="1" index="21">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<field name="consumer-tag" domain="consumer-tag"/>
|
||||
</method>
|
||||
<method name="cancel" synchronous="1" index="30">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<chassis name="client" implement="SHOULD"/>
|
||||
<response name="cancel-ok"/>
|
||||
<field name="consumer-tag" domain="consumer-tag"/>
|
||||
<field name="no-wait" domain="no-wait"/>
|
||||
</method>
|
||||
<method name="cancel-ok" synchronous="1" index="31">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<chassis name="server" implement="MAY"/>
|
||||
<field name="consumer-tag" domain="consumer-tag"/>
|
||||
</method>
|
||||
<method name="publish" content="1" index="40">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<field name="reserved-1" type="short" reserved="1"/>
|
||||
<field name="exchange" domain="exchange-name"/>
|
||||
<field name="routing-key" domain="shortstr"/>
|
||||
<field name="mandatory" domain="bit"/>
|
||||
<field name="immediate" domain="bit"/>
|
||||
</method>
|
||||
<method name="return" content="1" index="50">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<field name="reply-code" domain="reply-code"/>
|
||||
<field name="reply-text" domain="reply-text"/>
|
||||
<field name="exchange" domain="exchange-name"/>
|
||||
<field name="routing-key" domain="shortstr"/>
|
||||
</method>
|
||||
<method name="deliver" content="1" index="60">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<field name="consumer-tag" domain="consumer-tag"/>
|
||||
<field name="delivery-tag" domain="delivery-tag"/>
|
||||
<field name="redelivered" domain="redelivered"/>
|
||||
<field name="exchange" domain="exchange-name"/>
|
||||
<field name="routing-key" domain="shortstr"/>
|
||||
</method>
|
||||
<method name="get" synchronous="1" index="70">
|
||||
<response name="get-ok"/>
|
||||
<response name="get-empty"/>
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<field name="reserved-1" type="short" reserved="1"/>
|
||||
<field name="queue" domain="queue-name"/>
|
||||
<field name="no-ack" domain="no-ack"/>
|
||||
</method>
|
||||
<method name="get-ok" synchronous="1" content="1" index="71">
|
||||
<chassis name="client" implement="MAY"/>
|
||||
<field name="delivery-tag" domain="delivery-tag"/>
|
||||
<field name="redelivered" domain="redelivered"/>
|
||||
<field name="exchange" domain="exchange-name"/>
|
||||
<field name="routing-key" domain="shortstr"/>
|
||||
<field name="message-count" domain="message-count"/>
|
||||
</method>
|
||||
<method name="get-empty" synchronous="1" index="72">
|
||||
<chassis name="client" implement="MAY"/>
|
||||
<field name="reserved-1" type="shortstr" reserved="1"/>
|
||||
</method>
|
||||
<method name="ack" index="80">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<field name="delivery-tag" domain="delivery-tag"/>
|
||||
<field name="multiple" domain="bit"/>
|
||||
</method>
|
||||
<method name="reject" index="90">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<field name="delivery-tag" domain="delivery-tag"/>
|
||||
<field name="requeue" domain="bit"/>
|
||||
</method>
|
||||
<method name="recover-async" index="100" deprecated="1">
|
||||
<chassis name="server" implement="MAY"/>
|
||||
<field name="requeue" domain="bit"/>
|
||||
</method>
|
||||
<method name="recover" synchronous="1" index="110">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<field name="requeue" domain="bit"/>
|
||||
</method>
|
||||
<method name="recover-ok" synchronous="1" index="111">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
</method>
|
||||
<method name="nack" index="120">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<chassis name="client" implement="MUST"/>
|
||||
<field name="delivery-tag" domain="delivery-tag"/>
|
||||
<field name="multiple" domain="bit"/>
|
||||
<field name="requeue" domain="bit"/>
|
||||
</method>
|
||||
</class>
|
||||
<class name="tx" handler="channel" index="90">
|
||||
<chassis name="server" implement="SHOULD"/>
|
||||
<chassis name="client" implement="MAY"/>
|
||||
<method name="select" synchronous="1" index="10">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="select-ok"/>
|
||||
</method>
|
||||
<method name="select-ok" synchronous="1" index="11">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
</method>
|
||||
<method name="commit" synchronous="1" index="20">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="commit-ok"/>
|
||||
</method>
|
||||
<method name="commit-ok" synchronous="1" index="21">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
</method>
|
||||
<method name="rollback" synchronous="1" index="30">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="rollback-ok"/>
|
||||
</method>
|
||||
<method name="rollback-ok" synchronous="1" index="31">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
</method>
|
||||
</class>
|
||||
<class name="confirm" handler="channel" index="85">
|
||||
<chassis name="server" implement="SHOULD"/>
|
||||
<chassis name="client" implement="MAY"/>
|
||||
<method name="select" synchronous="1" index="10">
|
||||
<chassis name="server" implement="MUST"/>
|
||||
<response name="select-ok"/>
|
||||
<field name="nowait" type="bit"/>
|
||||
</method>
|
||||
<method name="select-ok" synchronous="1" index="11">
|
||||
<chassis name="client" implement="MUST"/>
|
||||
</method>
|
||||
</class>
|
||||
</amqp>
|
536
vendor/github.com/streadway/amqp/spec/gen.go
generated
vendored
536
vendor/github.com/streadway/amqp/spec/gen.go
generated
vendored
@ -1,536 +0,0 @@
|
||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Source code and contact info at http://github.com/streadway/amqp
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnknownType = errors.New("Unknown field type in gen")
|
||||
ErrUnknownDomain = errors.New("Unknown domain type in gen")
|
||||
)
|
||||
|
||||
var amqpTypeToNative = map[string]string{
|
||||
"bit": "bool",
|
||||
"octet": "byte",
|
||||
"shortshort": "uint8",
|
||||
"short": "uint16",
|
||||
"long": "uint32",
|
||||
"longlong": "uint64",
|
||||
"timestamp": "time.Time",
|
||||
"table": "Table",
|
||||
"shortstr": "string",
|
||||
"longstr": "string",
|
||||
}
|
||||
|
||||
type Rule struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Docs []string `xml:"doc"`
|
||||
}
|
||||
|
||||
type Doc struct {
|
||||
Type string `xml:"type,attr"`
|
||||
Body string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
type Chassis struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Implement string `xml:"implement,attr"`
|
||||
}
|
||||
|
||||
type Assert struct {
|
||||
Check string `xml:"check,attr"`
|
||||
Value string `xml:"value,attr"`
|
||||
Method string `xml:"method,attr"`
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Domain string `xml:"domain,attr"`
|
||||
Type string `xml:"type,attr"`
|
||||
Label string `xml:"label,attr"`
|
||||
Reserved bool `xml:"reserved,attr"`
|
||||
Docs []Doc `xml:"doc"`
|
||||
Asserts []Assert `xml:"assert"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Name string `xml:"name,attr"`
|
||||
}
|
||||
|
||||
type Method struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Response Response `xml:"response"`
|
||||
Synchronous bool `xml:"synchronous,attr"`
|
||||
Content bool `xml:"content,attr"`
|
||||
Index string `xml:"index,attr"`
|
||||
Label string `xml:"label,attr"`
|
||||
Docs []Doc `xml:"doc"`
|
||||
Rules []Rule `xml:"rule"`
|
||||
Fields []Field `xml:"field"`
|
||||
Chassis []Chassis `xml:"chassis"`
|
||||
}
|
||||
|
||||
type Class struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Handler string `xml:"handler,attr"`
|
||||
Index string `xml:"index,attr"`
|
||||
Label string `xml:"label,attr"`
|
||||
Docs []Doc `xml:"doc"`
|
||||
Methods []Method `xml:"method"`
|
||||
Chassis []Chassis `xml:"chassis"`
|
||||
}
|
||||
|
||||
type Domain struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Type string `xml:"type,attr"`
|
||||
Label string `xml:"label,attr"`
|
||||
Rules []Rule `xml:"rule"`
|
||||
Docs []Doc `xml:"doc"`
|
||||
}
|
||||
|
||||
type Constant struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Value int `xml:"value,attr"`
|
||||
Class string `xml:"class,attr"`
|
||||
Doc string `xml:"doc"`
|
||||
}
|
||||
|
||||
type Amqp struct {
|
||||
Major int `xml:"major,attr"`
|
||||
Minor int `xml:"minor,attr"`
|
||||
Port int `xml:"port,attr"`
|
||||
Comment string `xml:"comment,attr"`
|
||||
|
||||
Constants []Constant `xml:"constant"`
|
||||
Domains []Domain `xml:"domain"`
|
||||
Classes []Class `xml:"class"`
|
||||
}
|
||||
|
||||
type renderer struct {
|
||||
Root Amqp
|
||||
bitcounter int
|
||||
}
|
||||
|
||||
type fieldset struct {
|
||||
AmqpType string
|
||||
NativeType string
|
||||
Fields []Field
|
||||
*renderer
|
||||
}
|
||||
|
||||
var (
|
||||
helpers = template.FuncMap{
|
||||
"public": public,
|
||||
"private": private,
|
||||
"clean": clean,
|
||||
}
|
||||
|
||||
packageTemplate = template.Must(template.New("package").Funcs(helpers).Parse(`
|
||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Source code and contact info at http://github.com/streadway/amqp
|
||||
|
||||
/* GENERATED FILE - DO NOT EDIT */
|
||||
/* Rebuild from the spec/gen.go tool */
|
||||
|
||||
{{with .Root}}
|
||||
package amqp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Error codes that can be sent from the server during a connection or
|
||||
// channel exception or used by the client to indicate a class of error like
|
||||
// ErrCredentials. The text of the error is likely more interesting than
|
||||
// these constants.
|
||||
const (
|
||||
{{range $c := .Constants}}
|
||||
{{if $c.IsError}}{{.Name | public}}{{else}}{{.Name | private}}{{end}} = {{.Value}}{{end}}
|
||||
)
|
||||
|
||||
func isSoftExceptionCode(code int) bool {
|
||||
switch code {
|
||||
{{range $c := .Constants}} {{if $c.IsSoftError}} case {{$c.Value}}:
|
||||
return true
|
||||
{{end}}{{end}}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
{{range .Classes}}
|
||||
{{$class := .}}
|
||||
{{range .Methods}}
|
||||
{{$method := .}}
|
||||
{{$struct := $.StructName $class.Name $method.Name}}
|
||||
{{if .Docs}}/* {{range .Docs}} {{.Body | clean}} {{end}} */{{end}}
|
||||
type {{$struct}} struct {
|
||||
{{range .Fields}}
|
||||
{{$.FieldName .}} {{$.FieldType . | $.NativeType}} {{if .Label}}// {{.Label}}{{end}}{{end}}
|
||||
{{if .Content}}Properties properties
|
||||
Body []byte{{end}}
|
||||
}
|
||||
|
||||
func (me *{{$struct}}) id() (uint16, uint16) {
|
||||
return {{$class.Index}}, {{$method.Index}}
|
||||
}
|
||||
|
||||
func (me *{{$struct}}) wait() (bool) {
|
||||
return {{.Synchronous}}{{if $.HasField "NoWait" .}} && !me.NoWait{{end}}
|
||||
}
|
||||
|
||||
{{if .Content}}
|
||||
func (me *{{$struct}}) getContent() (properties, []byte) {
|
||||
return me.Properties, me.Body
|
||||
}
|
||||
|
||||
func (me *{{$struct}}) setContent(props properties, body []byte) {
|
||||
me.Properties, me.Body = props, body
|
||||
}
|
||||
{{end}}
|
||||
func (me *{{$struct}}) write(w io.Writer) (err error) {
|
||||
{{if $.HasType "bit" $method}}var bits byte{{end}}
|
||||
{{.Fields | $.Fieldsets | $.Partial "enc-"}}
|
||||
return
|
||||
}
|
||||
|
||||
func (me *{{$struct}}) read(r io.Reader) (err error) {
|
||||
{{if $.HasType "bit" $method}}var bits byte{{end}}
|
||||
{{.Fields | $.Fieldsets | $.Partial "dec-"}}
|
||||
return
|
||||
}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
func (me *reader) parseMethodFrame(channel uint16, size uint32) (f frame, err error) {
|
||||
mf := &methodFrame {
|
||||
ChannelId: channel,
|
||||
}
|
||||
|
||||
if err = binary.Read(me.r, binary.BigEndian, &mf.ClassId); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = binary.Read(me.r, binary.BigEndian, &mf.MethodId); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch mf.ClassId {
|
||||
{{range .Classes}}
|
||||
{{$class := .}}
|
||||
case {{.Index}}: // {{.Name}}
|
||||
switch mf.MethodId {
|
||||
{{range .Methods}}
|
||||
case {{.Index}}: // {{$class.Name}} {{.Name}}
|
||||
//fmt.Println("NextMethod: class:{{$class.Index}} method:{{.Index}}")
|
||||
method := &{{$.StructName $class.Name .Name}}{}
|
||||
if err = method.read(me.r); err != nil {
|
||||
return
|
||||
}
|
||||
mf.Method = method
|
||||
{{end}}
|
||||
default:
|
||||
return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId)
|
||||
}
|
||||
{{end}}
|
||||
default:
|
||||
return nil, fmt.Errorf("Bad method frame, unknown class %d", mf.ClassId)
|
||||
}
|
||||
|
||||
return mf, nil
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{define "enc-bit"}}
|
||||
{{range $off, $field := .Fields}}
|
||||
if me.{{$field | $.FieldName}} { bits |= 1 << {{$off}} }
|
||||
{{end}}
|
||||
if err = binary.Write(w, binary.BigEndian, bits); err != nil { return }
|
||||
{{end}}
|
||||
{{define "enc-octet"}}
|
||||
{{range .Fields}} if err = binary.Write(w, binary.BigEndian, me.{{. | $.FieldName}}); err != nil { return }
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{define "enc-shortshort"}}
|
||||
{{range .Fields}} if err = binary.Write(w, binary.BigEndian, me.{{. | $.FieldName}}); err != nil { return }
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{define "enc-short"}}
|
||||
{{range .Fields}} if err = binary.Write(w, binary.BigEndian, me.{{. | $.FieldName}}); err != nil { return }
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{define "enc-long"}}
|
||||
{{range .Fields}} if err = binary.Write(w, binary.BigEndian, me.{{. | $.FieldName}}); err != nil { return }
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{define "enc-longlong"}}
|
||||
{{range .Fields}} if err = binary.Write(w, binary.BigEndian, me.{{. | $.FieldName}}); err != nil { return }
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{define "enc-timestamp"}}
|
||||
{{range .Fields}} if err = writeTimestamp(w, me.{{. | $.FieldName}}); err != nil { return }
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{define "enc-shortstr"}}
|
||||
{{range .Fields}} if err = writeShortstr(w, me.{{. | $.FieldName}}); err != nil { return }
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{define "enc-longstr"}}
|
||||
{{range .Fields}} if err = writeLongstr(w, me.{{. | $.FieldName}}); err != nil { return }
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{define "enc-table"}}
|
||||
{{range .Fields}} if err = writeTable(w, me.{{. | $.FieldName}}); err != nil { return }
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "dec-bit"}}
|
||||
if err = binary.Read(r, binary.BigEndian, &bits); err != nil {
|
||||
return
|
||||
}
|
||||
{{range $off, $field := .Fields}} me.{{$field | $.FieldName}} = (bits & (1 << {{$off}}) > 0)
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{define "dec-octet"}}
|
||||
{{range .Fields}} if err = binary.Read(r, binary.BigEndian, &me.{{. | $.FieldName}}); err != nil { return }
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{define "dec-shortshort"}}
|
||||
{{range .Fields}} if err = binary.Read(r, binary.BigEndian, &me.{{. | $.FieldName}}); err != nil { return }
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{define "dec-short"}}
|
||||
{{range .Fields}} if err = binary.Read(r, binary.BigEndian, &me.{{. | $.FieldName}}); err != nil { return }
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{define "dec-long"}}
|
||||
{{range .Fields}} if err = binary.Read(r, binary.BigEndian, &me.{{. | $.FieldName}}); err != nil { return }
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{define "dec-longlong"}}
|
||||
{{range .Fields}} if err = binary.Read(r, binary.BigEndian, &me.{{. | $.FieldName}}); err != nil { return }
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{define "dec-timestamp"}}
|
||||
{{range .Fields}} if me.{{. | $.FieldName}}, err = readTimestamp(r); err != nil { return }
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{define "dec-shortstr"}}
|
||||
{{range .Fields}} if me.{{. | $.FieldName}}, err = readShortstr(r); err != nil { return }
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{define "dec-longstr"}}
|
||||
{{range .Fields}} if me.{{. | $.FieldName}}, err = readLongstr(r); err != nil { return }
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{define "dec-table"}}
|
||||
{{range .Fields}} if me.{{. | $.FieldName}}, err = readTable(r); err != nil { return }
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
`))
|
||||
)
|
||||
|
||||
func (me *Constant) IsError() bool {
|
||||
return strings.Contains(me.Class, "error")
|
||||
}
|
||||
|
||||
func (me *Constant) IsSoftError() bool {
|
||||
return me.Class == "soft-error"
|
||||
}
|
||||
|
||||
func (me *renderer) Partial(prefix string, fields []fieldset) (s string, err error) {
|
||||
var buf bytes.Buffer
|
||||
for _, set := range fields {
|
||||
name := prefix + set.AmqpType
|
||||
t := packageTemplate.Lookup(name)
|
||||
if t == nil {
|
||||
return "", errors.New(fmt.Sprintf("Missing template: %s", name))
|
||||
}
|
||||
if err = t.Execute(&buf, set); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return string(buf.Bytes()), nil
|
||||
}
|
||||
|
||||
// Groups the fields so that the right encoder/decoder can be called
|
||||
func (me *renderer) Fieldsets(fields []Field) (f []fieldset, err error) {
|
||||
if len(fields) > 0 {
|
||||
for _, field := range fields {
|
||||
cur := fieldset{}
|
||||
cur.AmqpType, err = me.FieldType(field)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cur.NativeType, err = me.NativeType(cur.AmqpType)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cur.Fields = append(cur.Fields, field)
|
||||
f = append(f, cur)
|
||||
}
|
||||
|
||||
i, j := 0, 1
|
||||
for j < len(f) {
|
||||
if f[i].AmqpType == f[j].AmqpType {
|
||||
f[i].Fields = append(f[i].Fields, f[j].Fields...)
|
||||
} else {
|
||||
i++
|
||||
f[i] = f[j]
|
||||
}
|
||||
j++
|
||||
}
|
||||
return f[:i+1], nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (me *renderer) HasType(typ string, method Method) bool {
|
||||
for _, f := range method.Fields {
|
||||
name, _ := me.FieldType(f)
|
||||
if name == typ {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (me *renderer) HasField(field string, method Method) bool {
|
||||
for _, f := range method.Fields {
|
||||
name := me.FieldName(f)
|
||||
if name == field {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (me *renderer) Domain(field Field) (domain Domain, err error) {
|
||||
for _, domain = range me.Root.Domains {
|
||||
if field.Domain == domain.Name {
|
||||
return
|
||||
}
|
||||
}
|
||||
return domain, nil
|
||||
//return domain, ErrUnknownDomain
|
||||
}
|
||||
|
||||
func (me *renderer) FieldName(field Field) (t string) {
|
||||
t = public(field.Name)
|
||||
|
||||
if field.Reserved {
|
||||
t = strings.ToLower(t)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (me *renderer) FieldType(field Field) (t string, err error) {
|
||||
t = field.Type
|
||||
|
||||
if t == "" {
|
||||
var domain Domain
|
||||
domain, err = me.Domain(field)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
t = domain.Type
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (me *renderer) NativeType(amqpType string) (t string, err error) {
|
||||
if t, ok := amqpTypeToNative[amqpType]; ok {
|
||||
return t, nil
|
||||
}
|
||||
return "", ErrUnknownType
|
||||
}
|
||||
|
||||
func (me *renderer) Tag(d Domain) string {
|
||||
label := "`"
|
||||
|
||||
label += `domain:"` + d.Name + `"`
|
||||
|
||||
if len(d.Type) > 0 {
|
||||
label += `,type:"` + d.Type + `"`
|
||||
}
|
||||
|
||||
label += "`"
|
||||
|
||||
return label
|
||||
}
|
||||
|
||||
func (me *renderer) StructName(parts ...string) string {
|
||||
return parts[0] + public(parts[1:]...)
|
||||
}
|
||||
|
||||
func clean(body string) (res string) {
|
||||
return strings.Replace(body, "\r", "", -1)
|
||||
}
|
||||
|
||||
func private(parts ...string) string {
|
||||
return export(regexp.MustCompile(`[-_]\w`), parts...)
|
||||
}
|
||||
|
||||
func public(parts ...string) string {
|
||||
return export(regexp.MustCompile(`^\w|[-_]\w`), parts...)
|
||||
}
|
||||
|
||||
func export(delim *regexp.Regexp, parts ...string) (res string) {
|
||||
for _, in := range parts {
|
||||
|
||||
res += delim.ReplaceAllStringFunc(in, func(match string) string {
|
||||
switch len(match) {
|
||||
case 1:
|
||||
return strings.ToUpper(match)
|
||||
case 2:
|
||||
return strings.ToUpper(match[1:])
|
||||
}
|
||||
panic("unreachable")
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
var r renderer
|
||||
|
||||
spec, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
log.Fatalln("Please pass spec on stdin", err)
|
||||
}
|
||||
|
||||
err = xml.Unmarshal(spec, &r.Root)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalln("Could not parse XML:", err)
|
||||
}
|
||||
|
||||
if err = packageTemplate.Execute(os.Stdout, &r); err != nil {
|
||||
log.Fatalln("Generate error: ", err)
|
||||
}
|
||||
}
|
3306
vendor/github.com/streadway/amqp/spec091.go
generated
vendored
3306
vendor/github.com/streadway/amqp/spec091.go
generated
vendored
File diff suppressed because it is too large
Load Diff
218
vendor/github.com/streadway/amqp/tls_test.go
generated
vendored
218
vendor/github.com/streadway/amqp/tls_test.go
generated
vendored
@ -1,218 +0,0 @@
|
||||
package amqp_test
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"github.com/streadway/amqp"
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type tlsServer struct {
|
||||
net.Listener
|
||||
URL string
|
||||
Config *tls.Config
|
||||
Header chan []byte
|
||||
}
|
||||
|
||||
// Captures the header for each accepted connection
|
||||
func (s *tlsServer) Serve() {
|
||||
for {
|
||||
c, err := s.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
header := make([]byte, 4)
|
||||
io.ReadFull(c, header)
|
||||
s.Header <- header
|
||||
c.Write([]byte{'A', 'M', 'Q', 'P', 0, 0, 0, 0})
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func tlsConfig() *tls.Config {
|
||||
cfg := new(tls.Config)
|
||||
|
||||
cfg.ClientCAs = x509.NewCertPool()
|
||||
cfg.ClientCAs.AppendCertsFromPEM([]byte(caCert))
|
||||
|
||||
cert, err := tls.X509KeyPair([]byte(serverCert), []byte(serverKey))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cfg.Certificates = append(cfg.Certificates, cert)
|
||||
cfg.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func startTlsServer() tlsServer {
|
||||
cfg := tlsConfig()
|
||||
|
||||
l, err := tls.Listen("tcp", "127.0.0.1:0", cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
s := tlsServer{
|
||||
Listener: l,
|
||||
Config: cfg,
|
||||
URL: fmt.Sprintf("amqps://%s/", l.Addr().String()),
|
||||
Header: make(chan []byte, 1),
|
||||
}
|
||||
|
||||
go s.Serve()
|
||||
return s
|
||||
}
|
||||
|
||||
// Tests that the server has handshaked the connection and seen the client
|
||||
// protocol announcement. Does not nest that the connection.open is successful.
|
||||
func TestTLSHandshake(t *testing.T) {
|
||||
srv := startTlsServer()
|
||||
defer srv.Close()
|
||||
|
||||
cfg := new(tls.Config)
|
||||
cfg.RootCAs = x509.NewCertPool()
|
||||
cfg.RootCAs.AppendCertsFromPEM([]byte(caCert))
|
||||
|
||||
cert, _ := tls.X509KeyPair([]byte(clientCert), []byte(clientKey))
|
||||
cfg.Certificates = append(cfg.Certificates, cert)
|
||||
|
||||
_, err := amqp.DialTLS(srv.URL, cfg)
|
||||
|
||||
select {
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
t.Fatalf("did not succeed to handshake the TLS connection after 10ms")
|
||||
case header := <-srv.Header:
|
||||
if string(header) != "AMQP" {
|
||||
t.Fatalf("expected to handshake a TLS connection, got err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const caCert = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICxjCCAa6gAwIBAgIJANWuMWMQSxvdMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV
|
||||
BAMTCE15VGVzdENBMB4XDTE0MDEyNzE5NTIyMloXDTI0MDEyNTE5NTIyMlowEzER
|
||||
MA8GA1UEAxMITXlUZXN0Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||
AQDBsIrkW4ob9Z/gzR2/Maa2stbutry6/vvz8eiJwIKIbaHGwqtFOUGiWeKw7H76
|
||||
IH3SjTAhNQY2hoKPyH41D36sDJkYBRyHFJTK/6ffvOhpyLnuXJAnoS62eKPSNUAx
|
||||
5i/lkHj42ESutYAH9qbHCI/gBm9G4WmhGAyA16xzC1n07JObl6KFoY1PqHKl823z
|
||||
mvF47I24DzemEfjdwC9nAAX/pGYOg9FA9nQv7NnhlsJMxueCx55RNU1ADRoqsbfE
|
||||
T0CQTOT4ryugGrUp9J4Cwen6YbXZrS6+Kff5SQCAns0Qu8/bwj0DKkuBGLF+Mnwe
|
||||
mq9bMzyZPUrPM3Gu48ao8YAfAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYDVR0P
|
||||
BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQCBwXGblRxIEOlEP6ANZ1C8AHWyG8lR
|
||||
CQduFclc0tmyCCz5fnyLK0aGu9LhXXe6/HSKqgs4mJqeqYOojdjkfOme/YdwDzjK
|
||||
WIf0kRYQHcB6NeyEZwW8C7subTP1Xw6zbAmjvQrtCGvRM+fi3/cs1sSSkd/EoRk4
|
||||
7GM9qQl/JIIoCOGncninf2NQm5YSpbit6/mOQD7EhqXsw+bX+IRh3DHC1Apv/PoA
|
||||
HlDNeM4vjWaBxsmvRSndrIvew1czboFM18oRSSIqAkU7dKZ0SbC11grzmNxMG2aD
|
||||
f9y8FIG6RK/SEaOZuc+uBGXx7tj7dczpE/2puqYcaVGwcv4kkrC/ZuRm
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
const serverCert = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC8zCCAdugAwIBAgIBATANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDEwhNeVRl
|
||||
c3RDQTAeFw0xNDAxMjcxOTUyMjNaFw0yNDAxMjUxOTUyMjNaMCUxEjAQBgNVBAMT
|
||||
CTEyNy4wLjAuMTEPMA0GA1UEChMGc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOC
|
||||
AQ8AMIIBCgKCAQEAxYAKbeGyg0gP0xwVsZsufzk/SUCtD44Gp3lQYQ9QumQ1IVZu
|
||||
PmZWwPWrzI93a1Abruz6ZhXaB3jcL5QPAy1N44IiFgVN45CZXBsqkpJe/abzRFOV
|
||||
DRnHxattPDHdgwML5d3nURKGUM/7+ACj5E4pZEDlM3RIjIKVd+doJsL7n6myO8FE
|
||||
tIpt4vTz1MFp3F+ntPnHU3BZ/VZ1UjSlFWnCjT0CR0tnXsPmlIaC98HThS8x5zNB
|
||||
fvvSN+Zln8RWdNLnEVHVdqYtOQ828QbCx8s1HfClGgaVoSDrzz+qQgtZFO4wW264
|
||||
2CWkNd8DSJUJ/HlPNXmbXsrRMgvGaL7YUz2yRQIDAQABo0AwPjAJBgNVHRMEAjAA
|
||||
MAsGA1UdDwQEAwIFIDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/
|
||||
AAABMA0GCSqGSIb3DQEBBQUAA4IBAQAE2g+wAFf9Xg5svcnb7+mfseYV16k9l5WG
|
||||
onrmR3FLsbTxfbr4PZJMHrswPbi2NRk0+ETPUpcv1RP7pUB7wSEvuS1NPGcU92iP
|
||||
58ycP3dYtLzmuu6BkgToZqwsCU8fC2zM0wt3+ifzPpDMffWWOioVuA3zdM9WPQYz
|
||||
+Ofajd0XaZwFZS8uTI5WXgObz7Xqfmln4tF3Sq1CTyuJ44qK4p83XOKFq+L04aD0
|
||||
d0c8w3YQNUENny/vMP9mDu3FQ3SnDz2GKl1LSjGe2TUnkoMkDfdk4wSzndTz/ecb
|
||||
QiCPKijwVPWNOWV3NDE2edMxDPxDoKoEm5F4UGfGjxSRnYCIoZLh
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
const serverKey = `
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAxYAKbeGyg0gP0xwVsZsufzk/SUCtD44Gp3lQYQ9QumQ1IVZu
|
||||
PmZWwPWrzI93a1Abruz6ZhXaB3jcL5QPAy1N44IiFgVN45CZXBsqkpJe/abzRFOV
|
||||
DRnHxattPDHdgwML5d3nURKGUM/7+ACj5E4pZEDlM3RIjIKVd+doJsL7n6myO8FE
|
||||
tIpt4vTz1MFp3F+ntPnHU3BZ/VZ1UjSlFWnCjT0CR0tnXsPmlIaC98HThS8x5zNB
|
||||
fvvSN+Zln8RWdNLnEVHVdqYtOQ828QbCx8s1HfClGgaVoSDrzz+qQgtZFO4wW264
|
||||
2CWkNd8DSJUJ/HlPNXmbXsrRMgvGaL7YUz2yRQIDAQABAoIBAGsyEvcPAGg3DbfE
|
||||
z5WFp9gPx2TIAOanbL8rnlAAEw4H47qDgfTGcSHsdeHioKuTYGMyZrpP8/YISGJe
|
||||
l0NfLJ5mfH+9Q0hXrJWMfS/u2DYOjo0wXH8u1fpZEEISwqsgVS3fonSjfFmSea1j
|
||||
E5GQRvEONBkYbWQuYFgjNqmLPS2r5lKbWCQvc1MB/vvVBwOTiO0ON7m/EkM5RKt9
|
||||
cDT5ZhhVjBpdmd9HpVbKTdBj8Q0l5/ZHZUEgZA6FDZEwYxTd9l87Z4YT+5SR0z9t
|
||||
k8/Z0CHd3x3Rv891t7m66ZJkaOda8NC65/432MQEQwJltmrKnc22dS8yI26rrmpp
|
||||
g3tcbSUCgYEA5nMXdQKS4vF+Kp10l/HqvGz2sU8qQaWYZQIg7Th3QJPo6N52po/s
|
||||
nn3UF0P5mT1laeZ5ZQJKx4gnmuPnIZ2ZtJQDyFhIbRPcZ+2hSNSuLYVcrumOC3EP
|
||||
3OZyFtFE1THO73aFe5e1jEdtoOne3Bds/Hq6NF45fkVdL+M9e8pfXIsCgYEA22W8
|
||||
zGjbWyrFOYvKknMQVtHnMx8BJEtsvWRknP6CWAv/8WyeZpE128Pve1m441AQnopS
|
||||
CuOF5wFK0iUXBFbS3Pe1/1j3em6yfVznuUHqJ7Qc+dNzxVvkTK8jGB6x+vm+M9Hg
|
||||
muHUM726IUxckoSNXbPNAVPIZab1NdSxam7F9m8CgYEAx55QZmIJXJ41XLKxqWC7
|
||||
peZ5NpPNlbncrTpPzUzJN94ntXfmrVckbxGt401VayEctMQYyZ9XqUlOjUP3FU5Q
|
||||
M3S3Zhba/eljVX8o406fZf0MkNLs4QpZ5E6V6x/xEP+pMhKng6yhbVb+JpIPIvUD
|
||||
yhyBKRWplbB+DRo5Sv685gsCgYA7l5m9h+m1DJv/cnn2Z2yTuHXtC8namuYRV1iA
|
||||
0ByFX9UINXGc+GpBpCnDPm6ax5+MAJQiQwSW52H0TIDA+/hQbrQvhHHL/o9av8Zt
|
||||
Kns4h5KrRQUYIUqUjamhnozHV9iS6LnyN87Usv8AlmY6oehoADN53dD702qdUYVT
|
||||
HH2G3wKBgCdvqyw78FR/n8cUWesTPnxx5HCeWJ1J+2BESnUnPmKZ71CV1H7uweja
|
||||
vPUxuuuGLKfNx84OKCfRDbtOgMOeyh9T1RmXry6Srz/7/udjlF0qmFiRXfBNAgoR
|
||||
tNb0+Ri/vY0AHrQ7UnCbl12qPVaqhEXLr+kCGNEPFqpMJPPEeMK0
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`
|
||||
|
||||
const clientCert = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC4jCCAcqgAwIBAgIBAjANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDEwhNeVRl
|
||||
c3RDQTAeFw0xNDAxMjcxOTUyMjNaFw0yNDAxMjUxOTUyMjNaMCUxEjAQBgNVBAMT
|
||||
CTEyNy4wLjAuMTEPMA0GA1UEChMGY2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOC
|
||||
AQ8AMIIBCgKCAQEAu7LMqd+agoH168Bsi0WJ36ulYqDypq+GZPF7uWOo2pE0raKH
|
||||
B++31/hjnkt6yC5kLKVZZ0EfolBa9q4Cy6swfGaEMafy44ZCRneLnt1azL1N6Kfz
|
||||
+U0KsOqyQDoMxYJG1gVTEZN19/U/ew2eazcxKyERI3oGCQ4SbpkxBTbfxtAFk49e
|
||||
xIB3obsuMVUrmtXE4FkUkvG7NgpPUgrhp0yxYpj9zruZGzGGT1zNhcarbQ/4i7It
|
||||
ZMbnv6pqQWtYDgnGX2TDRcEiXGeO+KrzhfpTRLfO3K4np8e8cmTyXM+4lMlWUgma
|
||||
KrRdu1QXozGqRs47u2prGKGdSQWITpqNVCY8fQIDAQABoy8wLTAJBgNVHRMEAjAA
|
||||
MAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQUF
|
||||
AAOCAQEAhCuBCLznPc4O96hT3P8Fx19L3ltrWbc/pWrx8JjxUaGk8kNmjMjY+/Mt
|
||||
JBbjUBx2kJwaY0EHMAfw7D1f1wcCeNycx/0dyb0E6xzhmPw5fY15GGNg8rzWwqSY
|
||||
+i/1iqU0IRkmRHV7XCF+trd2H0Ec+V1Fd/61E2ccJfOL5aSAyWbMCUtWxS3QMnqH
|
||||
FBfKdVEiY9WNht5hnvsXQBRaNhowJ6Cwa7/1/LZjmhcXiJ0xrc1Hggj3cvS+4vll
|
||||
Ew+20a0tPKjD/v/2oSQL+qkeYKV4fhCGkaBHCpPlSJrqorb7B6NmPy3nS26ETKE/
|
||||
o2UCfZc5g2MU1ENa31kT1iuhKZapsA==
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
const clientKey = `
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAu7LMqd+agoH168Bsi0WJ36ulYqDypq+GZPF7uWOo2pE0raKH
|
||||
B++31/hjnkt6yC5kLKVZZ0EfolBa9q4Cy6swfGaEMafy44ZCRneLnt1azL1N6Kfz
|
||||
+U0KsOqyQDoMxYJG1gVTEZN19/U/ew2eazcxKyERI3oGCQ4SbpkxBTbfxtAFk49e
|
||||
xIB3obsuMVUrmtXE4FkUkvG7NgpPUgrhp0yxYpj9zruZGzGGT1zNhcarbQ/4i7It
|
||||
ZMbnv6pqQWtYDgnGX2TDRcEiXGeO+KrzhfpTRLfO3K4np8e8cmTyXM+4lMlWUgma
|
||||
KrRdu1QXozGqRs47u2prGKGdSQWITpqNVCY8fQIDAQABAoIBAGSEn3hFyEAmCyYi
|
||||
2b5IEksXaC2GlgxQKb/7Vs/0oCPU6YonZPsKFMFzQx4tu+ZiecEzF8rlJGTPdbdv
|
||||
fw3FcuTcHeVd1QSmDO4h7UK5tnu40XVMJKsY6CXQun8M13QajYbmORNLjjypOULU
|
||||
C0fNueYoAj6mhX7p61MRdSAev/5+0+bVQQG/tSVDQzdngvKpaCunOphiB2VW2Aa0
|
||||
7aYPOFCoPB2uo0DwUmBB0yfx9x4hXX9ovQI0YFou7bq6iYJ0vlZBvYQ9YrVdxjKL
|
||||
avcz1N5xM3WFAkZJSVT/Ho5+uTbZx4RrJ8b5T+t2spOKmXyAjwS2rL/XMAh8YRZ1
|
||||
u44duoECgYEA4jpK2qshgQ0t49rjVHEDKX5x7ElEZefl0rHZ/2X/uHUDKpKj2fTq
|
||||
3TQzHquiQ4Aof7OEB9UE3DGrtpvo/j/PYxL5Luu5VR4AIEJm+CA8GYuE96+uIL0Z
|
||||
M2r3Lux6Bp30Z47Eit2KiY4fhrWs59WB3NHHoFxgzHSVbnuA02gcX2ECgYEA1GZw
|
||||
iXIVYaK07ED+q/0ObyS5hD1cMhJ7ifSN9BxuG0qUpSigbkTGj09fUDS4Fqsz9dvz
|
||||
F0P93fZvyia242TIfDUwJEsDQCgHk7SGa4Rx/p/3x/obIEERk7K76Hdg93U5NXhV
|
||||
NvczvgL0HYxnb+qtumwMgGPzncB4lGcTnRyOfp0CgYBTIsDnYwRI/KLknUf1fCKB
|
||||
WSpcfwBXwsS+jQVjygQTsUyclI8KResZp1kx6DkVPT+kzj+y8SF8GfTUgq844BJC
|
||||
gnJ4P8A3+3JoaH6WqKHtcUxICZOgDF36e1CjOdwOGnX6qIipz4hdzJDhXFpSSDAV
|
||||
CjKmR8x61k0j8NcC2buzgQKBgFr7eo9VwBTvpoJhIPY5UvqHB7S+uAR26FZi3H/J
|
||||
wdyM6PmKWpaBfXCb9l8cBhMnyP0y94FqzY9L5fz48nSbkkmqWvHg9AaCXySFOuNJ
|
||||
e68vhOszlnUNimLzOAzPPkkh/JyL7Cy8XXyyNTGHGDPXmg12BTDmH8/eR4iCUuOE
|
||||
/QD9AoGBALQ/SkvfO3D5+k9e/aTHRuMJ0+PWdLUMTZ39oJQxUx+qj7/xpjDvWTBn
|
||||
eDmF/wjnIAg+020oXyBYo6plEZfDz3EYJQZ+3kLLEU+O/A7VxCakPYPwCr7N/InL
|
||||
Ccg/TVSIXxw/6uJnojoAjMIEU45NoP6RMp0mWYYb2OlteEv08Ovp
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`
|
382
vendor/github.com/streadway/amqp/types.go
generated
vendored
382
vendor/github.com/streadway/amqp/types.go
generated
vendored
@ -1,382 +0,0 @@
|
||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Source code and contact info at http://github.com/streadway/amqp
|
||||
|
||||
package amqp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// Errors that this library could return/emit from a channel or connection
|
||||
ErrClosed = &Error{Code: ChannelError, Reason: "channel/connection is not open"}
|
||||
ErrChannelMax = &Error{Code: ChannelError, Reason: "channel id space exhausted"}
|
||||
ErrSASL = &Error{Code: AccessRefused, Reason: "SASL could not negotiate a shared mechanism"}
|
||||
ErrCredentials = &Error{Code: AccessRefused, Reason: "username or password not allowed"}
|
||||
ErrVhost = &Error{Code: AccessRefused, Reason: "no access to this vhost"}
|
||||
ErrSyntax = &Error{Code: SyntaxError, Reason: "invalid field or value inside of a frame"}
|
||||
ErrFrame = &Error{Code: FrameError, Reason: "frame could not be parsed"}
|
||||
ErrCommandInvalid = &Error{Code: CommandInvalid, Reason: "unexpected command received"}
|
||||
ErrUnexpectedFrame = &Error{Code: UnexpectedFrame, Reason: "unexpected frame received"}
|
||||
ErrFieldType = &Error{Code: SyntaxError, Reason: "unsupported table field type"}
|
||||
)
|
||||
|
||||
// Error captures the code and reason a channel or connection has been closed
|
||||
// by the server.
|
||||
type Error struct {
|
||||
Code int // constant code from the specification
|
||||
Reason string // description of the error
|
||||
Server bool // true when initiated from the server, false when from this library
|
||||
Recover bool // true when this error can be recovered by retrying later or with differnet parameters
|
||||
}
|
||||
|
||||
func newError(code uint16, text string) *Error {
|
||||
return &Error{
|
||||
Code: int(code),
|
||||
Reason: text,
|
||||
Recover: isSoftExceptionCode(int(code)),
|
||||
Server: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (me Error) Error() string {
|
||||
return fmt.Sprintf("Exception (%d) Reason: %q", me.Code, me.Reason)
|
||||
}
|
||||
|
||||
// Used by header frames to capture routing and header information
|
||||
type properties struct {
|
||||
ContentType string // MIME content type
|
||||
ContentEncoding string // MIME content encoding
|
||||
Headers Table // Application or header exchange table
|
||||
DeliveryMode uint8 // queue implemention use - Transient (1) or Persistent (2)
|
||||
Priority uint8 // queue implementation use - 0 to 9
|
||||
CorrelationId string // application use - correlation identifier
|
||||
ReplyTo string // application use - address to to reply to (ex: RPC)
|
||||
Expiration string // implementation use - message expiration spec
|
||||
MessageId string // application use - message identifier
|
||||
Timestamp time.Time // application use - message timestamp
|
||||
Type string // application use - message type name
|
||||
UserId string // application use - creating user id
|
||||
AppId string // application use - creating application
|
||||
reserved1 string // was cluster-id - process for buffer consumption
|
||||
}
|
||||
|
||||
// DeliveryMode. Transient means higher throughput but messages will not be
|
||||
// restored on broker restart. The delivery mode of publishings is unrelated
|
||||
// to the durability of the queues they reside on. Transient messages will
|
||||
// not be restored to durable queues, persistent messages will be restored to
|
||||
// durable queues and lost on non-durable queues during server restart.
|
||||
//
|
||||
// This remains typed as uint8 to match Publishing.DeliveryMode. Other
|
||||
// delivery modes specific to custom queue implementations are not enumerated
|
||||
// here.
|
||||
const (
|
||||
Transient uint8 = 1
|
||||
Persistent uint8 = 2
|
||||
)
|
||||
|
||||
// The property flags are an array of bits that indicate the presence or
|
||||
// absence of each property value in sequence. The bits are ordered from most
|
||||
// high to low - bit 15 indicates the first property.
|
||||
const (
|
||||
flagContentType = 0x8000
|
||||
flagContentEncoding = 0x4000
|
||||
flagHeaders = 0x2000
|
||||
flagDeliveryMode = 0x1000
|
||||
flagPriority = 0x0800
|
||||
flagCorrelationId = 0x0400
|
||||
flagReplyTo = 0x0200
|
||||
flagExpiration = 0x0100
|
||||
flagMessageId = 0x0080
|
||||
flagTimestamp = 0x0040
|
||||
flagType = 0x0020
|
||||
flagUserId = 0x0010
|
||||
flagAppId = 0x0008
|
||||
flagReserved1 = 0x0004
|
||||
)
|
||||
|
||||
// Queue captures the current server state of the queue on the server returned
|
||||
// from Channel.QueueDeclare or Channel.QueueInspect.
|
||||
type Queue struct {
|
||||
Name string // server confirmed or generated name
|
||||
Messages int // count of messages not awaiting acknowledgment
|
||||
Consumers int // number of consumers receiving deliveries
|
||||
}
|
||||
|
||||
// Publishing captures the client message sent to the server. The fields
|
||||
// outside of the Headers table included in this struct mirror the underlying
|
||||
// fields in the content frame. They use native types for convenience and
|
||||
// efficiency.
|
||||
type Publishing struct {
|
||||
// Application or exchange specific fields,
|
||||
// the headers exchange will inspect this field.
|
||||
Headers Table
|
||||
|
||||
// Properties
|
||||
ContentType string // MIME content type
|
||||
ContentEncoding string // MIME content encoding
|
||||
DeliveryMode uint8 // Transient (0 or 1) or Persistent (2)
|
||||
Priority uint8 // 0 to 9
|
||||
CorrelationId string // correlation identifier
|
||||
ReplyTo string // address to to reply to (ex: RPC)
|
||||
Expiration string // message expiration spec
|
||||
MessageId string // message identifier
|
||||
Timestamp time.Time // message timestamp
|
||||
Type string // message type name
|
||||
UserId string // creating user id - ex: "guest"
|
||||
AppId string // creating application id
|
||||
|
||||
// The application specific payload of the message
|
||||
Body []byte
|
||||
}
|
||||
|
||||
// Blocking notifies the server's TCP flow control of the Connection. When a
|
||||
// server hits a memory or disk alarm it will block all connections until the
|
||||
// resources are reclaimed. Use NotifyBlock on the Connection to receive these
|
||||
// events.
|
||||
type Blocking struct {
|
||||
Active bool // TCP pushback active/inactive on server
|
||||
Reason string // Server reason for activation
|
||||
}
|
||||
|
||||
// Decimal matches the AMQP decimal type. Scale is the number of decimal
|
||||
// digits Scale == 2, Value == 12345, Decimal == 123.45
|
||||
type Decimal struct {
|
||||
Scale uint8
|
||||
Value int32
|
||||
}
|
||||
|
||||
// Table stores user supplied fields of the following types:
|
||||
//
|
||||
// bool
|
||||
// byte
|
||||
// float32
|
||||
// float64
|
||||
// int16
|
||||
// int32
|
||||
// int64
|
||||
// nil
|
||||
// string
|
||||
// time.Time
|
||||
// amqp.Decimal
|
||||
// amqp.Table
|
||||
// []byte
|
||||
// []interface{} - containing above types
|
||||
//
|
||||
// Functions taking a table will immediately fail when the table contains a
|
||||
// value of an unsupported type.
|
||||
//
|
||||
// The caller must be specific in which precision of integer it wishes to
|
||||
// encode.
|
||||
//
|
||||
// Use a type assertion when reading values from a table for type converstion.
|
||||
//
|
||||
// RabbitMQ expects int32 for integer values.
|
||||
//
|
||||
type Table map[string]interface{}
|
||||
|
||||
func validateField(f interface{}) error {
|
||||
switch fv := f.(type) {
|
||||
case nil, bool, byte, int16, int32, int64, float32, float64, string, []byte, Decimal, time.Time:
|
||||
return nil
|
||||
|
||||
case []interface{}:
|
||||
for _, v := range fv {
|
||||
if err := validateField(v); err != nil {
|
||||
return fmt.Errorf("in array %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case Table:
|
||||
for k, v := range fv {
|
||||
if err := validateField(v); err != nil {
|
||||
return fmt.Errorf("table field %q %s", k, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("value %t not supported", f)
|
||||
}
|
||||
|
||||
func (t Table) Validate() error {
|
||||
return validateField(t)
|
||||
}
|
||||
|
||||
// Heap interface for maintaining delivery tags
|
||||
type tagSet []uint64
|
||||
|
||||
func (me tagSet) Len() int { return len(me) }
|
||||
func (me tagSet) Less(i, j int) bool { return (me)[i] < (me)[j] }
|
||||
func (me tagSet) Swap(i, j int) { (me)[i], (me)[j] = (me)[j], (me)[i] }
|
||||
func (me *tagSet) Push(tag interface{}) { *me = append(*me, tag.(uint64)) }
|
||||
func (me *tagSet) Pop() interface{} {
|
||||
val := (*me)[len(*me)-1]
|
||||
*me = (*me)[:len(*me)-1]
|
||||
return val
|
||||
}
|
||||
|
||||
type message interface {
|
||||
id() (uint16, uint16)
|
||||
wait() bool
|
||||
read(io.Reader) error
|
||||
write(io.Writer) error
|
||||
}
|
||||
|
||||
type messageWithContent interface {
|
||||
message
|
||||
getContent() (properties, []byte)
|
||||
setContent(properties, []byte)
|
||||
}
|
||||
|
||||
/*
|
||||
The base interface implemented as:
|
||||
|
||||
2.3.5 frame Details
|
||||
|
||||
All frames consist of a header (7 octets), a payload of arbitrary size, and a 'frame-end' octet that detects
|
||||
malformed frames:
|
||||
|
||||
0 1 3 7 size+7 size+8
|
||||
+------+---------+-------------+ +------------+ +-----------+
|
||||
| type | channel | size | | payload | | frame-end |
|
||||
+------+---------+-------------+ +------------+ +-----------+
|
||||
octet short long size octets octet
|
||||
|
||||
To read a frame, we:
|
||||
|
||||
1. Read the header and check the frame type and channel.
|
||||
2. Depending on the frame type, we read the payload and process it.
|
||||
3. Read the frame end octet.
|
||||
|
||||
In realistic implementations where performance is a concern, we would use
|
||||
“read-ahead buffering” or “gathering reads” to avoid doing three separate
|
||||
system calls to read a frame.
|
||||
|
||||
*/
|
||||
type frame interface {
|
||||
write(io.Writer) error
|
||||
channel() uint16
|
||||
}
|
||||
|
||||
type reader struct {
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
type writer struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// Implements the frame interface for Connection RPC
|
||||
type protocolHeader struct{}
|
||||
|
||||
func (protocolHeader) write(w io.Writer) error {
|
||||
_, err := w.Write([]byte{'A', 'M', 'Q', 'P', 0, 0, 9, 1})
|
||||
return err
|
||||
}
|
||||
|
||||
func (protocolHeader) channel() uint16 {
|
||||
panic("only valid as initial handshake")
|
||||
}
|
||||
|
||||
/*
|
||||
Method frames carry the high-level protocol commands (which we call "methods").
|
||||
One method frame carries one command. The method frame payload has this format:
|
||||
|
||||
0 2 4
|
||||
+----------+-----------+-------------- - -
|
||||
| class-id | method-id | arguments...
|
||||
+----------+-----------+-------------- - -
|
||||
short short ...
|
||||
|
||||
To process a method frame, we:
|
||||
1. Read the method frame payload.
|
||||
2. Unpack it into a structure. A given method always has the same structure,
|
||||
so we can unpack the method rapidly. 3. Check that the method is allowed in
|
||||
the current context.
|
||||
4. Check that the method arguments are valid.
|
||||
5. Execute the method.
|
||||
|
||||
Method frame bodies are constructed as a list of AMQP data fields (bits,
|
||||
integers, strings and string tables). The marshalling code is trivially
|
||||
generated directly from the protocol specifications, and can be very rapid.
|
||||
*/
|
||||
type methodFrame struct {
|
||||
ChannelId uint16
|
||||
ClassId uint16
|
||||
MethodId uint16
|
||||
Method message
|
||||
}
|
||||
|
||||
func (me *methodFrame) channel() uint16 { return me.ChannelId }
|
||||
|
||||
/*
|
||||
Heartbeating is a technique designed to undo one of TCP/IP's features, namely
|
||||
its ability to recover from a broken physical connection by closing only after
|
||||
a quite long time-out. In some scenarios we need to know very rapidly if a
|
||||
peer is disconnected or not responding for other reasons (e.g. it is looping).
|
||||
Since heartbeating can be done at a low level, we implement this as a special
|
||||
type of frame that peers exchange at the transport level, rather than as a
|
||||
class method.
|
||||
*/
|
||||
type heartbeatFrame struct {
|
||||
ChannelId uint16
|
||||
}
|
||||
|
||||
func (me *heartbeatFrame) channel() uint16 { return me.ChannelId }
|
||||
|
||||
/*
|
||||
Certain methods (such as Basic.Publish, Basic.Deliver, etc.) are formally
|
||||
defined as carrying content. When a peer sends such a method frame, it always
|
||||
follows it with a content header and zero or more content body frames.
|
||||
|
||||
A content header frame has this format:
|
||||
|
||||
0 2 4 12 14
|
||||
+----------+--------+-----------+----------------+------------- - -
|
||||
| class-id | weight | body size | property flags | property list...
|
||||
+----------+--------+-----------+----------------+------------- - -
|
||||
short short long long short remainder...
|
||||
|
||||
We place content body in distinct frames (rather than including it in the
|
||||
method) so that AMQP may support "zero copy" techniques in which content is
|
||||
never marshalled or encoded. We place the content properties in their own
|
||||
frame so that recipients can selectively discard contents they do not want to
|
||||
process
|
||||
*/
|
||||
type headerFrame struct {
|
||||
ChannelId uint16
|
||||
ClassId uint16
|
||||
weight uint16
|
||||
Size uint64
|
||||
Properties properties
|
||||
}
|
||||
|
||||
func (me *headerFrame) channel() uint16 { return me.ChannelId }
|
||||
|
||||
/*
|
||||
Content is the application data we carry from client-to-client via the AMQP
|
||||
server. Content is, roughly speaking, a set of properties plus a binary data
|
||||
part. The set of allowed properties are defined by the Basic class, and these
|
||||
form the "content header frame". The data can be any size, and MAY be broken
|
||||
into several (or many) chunks, each forming a "content body frame".
|
||||
|
||||
Looking at the frames for a specific channel, as they pass on the wire, we
|
||||
might see something like this:
|
||||
|
||||
[method]
|
||||
[method] [header] [body] [body]
|
||||
[method]
|
||||
...
|
||||
*/
|
||||
type bodyFrame struct {
|
||||
ChannelId uint16
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func (me *bodyFrame) channel() uint16 { return me.ChannelId }
|
170
vendor/github.com/streadway/amqp/uri.go
generated
vendored
170
vendor/github.com/streadway/amqp/uri.go
generated
vendored
@ -1,170 +0,0 @@
|
||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Source code and contact info at http://github.com/streadway/amqp
|
||||
|
||||
package amqp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var errURIScheme = errors.New("AMQP scheme must be either 'amqp://' or 'amqps://'")
|
||||
|
||||
var schemePorts = map[string]int{
|
||||
"amqp": 5672,
|
||||
"amqps": 5671,
|
||||
}
|
||||
|
||||
var defaultURI = URI{
|
||||
Scheme: "amqp",
|
||||
Host: "localhost",
|
||||
Port: 5672,
|
||||
Username: "guest",
|
||||
Password: "guest",
|
||||
Vhost: "/",
|
||||
}
|
||||
|
||||
// URI represents a parsed AMQP URI string.
|
||||
type URI struct {
|
||||
Scheme string
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
Vhost string
|
||||
}
|
||||
|
||||
// ParseURI attempts to parse the given AMQP URI according to the spec.
|
||||
// See http://www.rabbitmq.com/uri-spec.html.
|
||||
//
|
||||
// Default values for the fields are:
|
||||
//
|
||||
// Scheme: amqp
|
||||
// Host: localhost
|
||||
// Port: 5672
|
||||
// Username: guest
|
||||
// Password: guest
|
||||
// Vhost: /
|
||||
//
|
||||
func ParseURI(uri string) (URI, error) {
|
||||
me := defaultURI
|
||||
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return me, err
|
||||
}
|
||||
|
||||
defaultPort, okScheme := schemePorts[u.Scheme]
|
||||
|
||||
if okScheme {
|
||||
me.Scheme = u.Scheme
|
||||
} else {
|
||||
return me, errURIScheme
|
||||
}
|
||||
|
||||
host, port := splitHostPort(u.Host)
|
||||
|
||||
if host != "" {
|
||||
me.Host = host
|
||||
}
|
||||
|
||||
if port != "" {
|
||||
port32, err := strconv.ParseInt(port, 10, 32)
|
||||
if err != nil {
|
||||
return me, err
|
||||
}
|
||||
me.Port = int(port32)
|
||||
} else {
|
||||
me.Port = defaultPort
|
||||
}
|
||||
|
||||
if u.User != nil {
|
||||
me.Username = u.User.Username()
|
||||
if password, ok := u.User.Password(); ok {
|
||||
me.Password = password
|
||||
}
|
||||
}
|
||||
|
||||
if u.Path != "" {
|
||||
if strings.HasPrefix(u.Path, "/") {
|
||||
if u.Host == "" && strings.HasPrefix(u.Path, "///") {
|
||||
// net/url doesn't handle local context authorities and leaves that up
|
||||
// to the scheme handler. In our case, we translate amqp:/// into the
|
||||
// default host and whatever the vhost should be
|
||||
if len(u.Path) > 3 {
|
||||
me.Vhost = u.Path[3:]
|
||||
}
|
||||
} else if len(u.Path) > 1 {
|
||||
me.Vhost = u.Path[1:]
|
||||
}
|
||||
} else {
|
||||
me.Vhost = u.Path
|
||||
}
|
||||
}
|
||||
|
||||
return me, nil
|
||||
}
|
||||
|
||||
// Splits host:port, host, [ho:st]:port, or [ho:st]. Unlike net.SplitHostPort
|
||||
// which splits :port, host:port or [host]:port
|
||||
//
|
||||
// Handles hosts that have colons that are in brackets like [::1]:http
|
||||
func splitHostPort(addr string) (host, port string) {
|
||||
i := strings.LastIndex(addr, ":")
|
||||
|
||||
if i >= 0 {
|
||||
host, port = addr[:i], addr[i+1:]
|
||||
|
||||
if len(port) > 0 && port[len(port)-1] == ']' && addr[0] == '[' {
|
||||
// we've split on an inner colon, the port was missing outside of the
|
||||
// brackets so use the full addr. We could assert that host should not
|
||||
// contain any colons here
|
||||
host, port = addr, ""
|
||||
}
|
||||
} else {
|
||||
host = addr
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// PlainAuth returns a PlainAuth structure based on the parsed URI's
|
||||
// Username and Password fields.
|
||||
func (me URI) PlainAuth() *PlainAuth {
|
||||
return &PlainAuth{
|
||||
Username: me.Username,
|
||||
Password: me.Password,
|
||||
}
|
||||
}
|
||||
|
||||
func (me URI) String() string {
|
||||
var authority string
|
||||
|
||||
if me.Username != defaultURI.Username || me.Password != defaultURI.Password {
|
||||
authority += me.Username
|
||||
|
||||
if me.Password != defaultURI.Password {
|
||||
authority += ":" + me.Password
|
||||
}
|
||||
|
||||
authority += "@"
|
||||
}
|
||||
|
||||
authority += me.Host
|
||||
|
||||
if defaultPort, found := schemePorts[me.Scheme]; !found || defaultPort != me.Port {
|
||||
authority += ":" + strconv.FormatInt(int64(me.Port), 10)
|
||||
}
|
||||
|
||||
var vhost string
|
||||
if me.Vhost != defaultURI.Vhost {
|
||||
vhost = me.Vhost
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s://%s/%s", me.Scheme, authority, url.QueryEscape(vhost))
|
||||
}
|
328
vendor/github.com/streadway/amqp/uri_test.go
generated
vendored
328
vendor/github.com/streadway/amqp/uri_test.go
generated
vendored
@ -1,328 +0,0 @@
|
||||
package amqp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test matrix defined on http://www.rabbitmq.com/uri-spec.html
|
||||
type testURI struct {
|
||||
url string
|
||||
username string
|
||||
password string
|
||||
host string
|
||||
port int
|
||||
vhost string
|
||||
canon string
|
||||
}
|
||||
|
||||
var uriTests = []testURI{
|
||||
{
|
||||
url: "amqp://user:pass@host:10000/vhost",
|
||||
username: "user",
|
||||
password: "pass",
|
||||
host: "host",
|
||||
port: 10000,
|
||||
vhost: "vhost",
|
||||
canon: "amqp://user:pass@host:10000/vhost",
|
||||
},
|
||||
|
||||
// this fails due to net/url not parsing pct-encoding in host
|
||||
// testURI{url: "amqp://user%61:%61pass@ho%61st:10000/v%2Fhost",
|
||||
// username: "usera",
|
||||
// password: "apass",
|
||||
// host: "hoast",
|
||||
// port: 10000,
|
||||
// vhost: "v/host",
|
||||
// },
|
||||
|
||||
{
|
||||
url: "amqp://",
|
||||
username: defaultURI.Username,
|
||||
password: defaultURI.Password,
|
||||
host: defaultURI.Host,
|
||||
port: defaultURI.Port,
|
||||
vhost: defaultURI.Vhost,
|
||||
canon: "amqp://localhost/",
|
||||
},
|
||||
|
||||
{
|
||||
url: "amqp://:@/",
|
||||
username: "",
|
||||
password: "",
|
||||
host: defaultURI.Host,
|
||||
port: defaultURI.Port,
|
||||
vhost: defaultURI.Vhost,
|
||||
canon: "amqp://:@localhost/",
|
||||
},
|
||||
|
||||
{
|
||||
url: "amqp://user@",
|
||||
username: "user",
|
||||
password: defaultURI.Password,
|
||||
host: defaultURI.Host,
|
||||
port: defaultURI.Port,
|
||||
vhost: defaultURI.Vhost,
|
||||
canon: "amqp://user@localhost/",
|
||||
},
|
||||
|
||||
{
|
||||
url: "amqp://user:pass@",
|
||||
username: "user",
|
||||
password: "pass",
|
||||
host: defaultURI.Host,
|
||||
port: defaultURI.Port,
|
||||
vhost: defaultURI.Vhost,
|
||||
canon: "amqp://user:pass@localhost/",
|
||||
},
|
||||
|
||||
{
|
||||
url: "amqp://guest:pass@",
|
||||
username: "guest",
|
||||
password: "pass",
|
||||
host: defaultURI.Host,
|
||||
port: defaultURI.Port,
|
||||
vhost: defaultURI.Vhost,
|
||||
canon: "amqp://guest:pass@localhost/",
|
||||
},
|
||||
|
||||
{
|
||||
url: "amqp://host",
|
||||
username: defaultURI.Username,
|
||||
password: defaultURI.Password,
|
||||
host: "host",
|
||||
port: defaultURI.Port,
|
||||
vhost: defaultURI.Vhost,
|
||||
canon: "amqp://host/",
|
||||
},
|
||||
|
||||
{
|
||||
url: "amqp://:10000",
|
||||
username: defaultURI.Username,
|
||||
password: defaultURI.Password,
|
||||
host: defaultURI.Host,
|
||||
port: 10000,
|
||||
vhost: defaultURI.Vhost,
|
||||
canon: "amqp://localhost:10000/",
|
||||
},
|
||||
|
||||
{
|
||||
url: "amqp:///vhost",
|
||||
username: defaultURI.Username,
|
||||
password: defaultURI.Password,
|
||||
host: defaultURI.Host,
|
||||
port: defaultURI.Port,
|
||||
vhost: "vhost",
|
||||
canon: "amqp://localhost/vhost",
|
||||
},
|
||||
|
||||
{
|
||||
url: "amqp://host/",
|
||||
username: defaultURI.Username,
|
||||
password: defaultURI.Password,
|
||||
host: "host",
|
||||
port: defaultURI.Port,
|
||||
vhost: defaultURI.Vhost,
|
||||
canon: "amqp://host/",
|
||||
},
|
||||
|
||||
{
|
||||
url: "amqp://host/%2F",
|
||||
username: defaultURI.Username,
|
||||
password: defaultURI.Password,
|
||||
host: "host",
|
||||
port: defaultURI.Port,
|
||||
vhost: "/",
|
||||
canon: "amqp://host/",
|
||||
},
|
||||
|
||||
{
|
||||
url: "amqp://host/%2F%2F",
|
||||
username: defaultURI.Username,
|
||||
password: defaultURI.Password,
|
||||
host: "host",
|
||||
port: defaultURI.Port,
|
||||
vhost: "//",
|
||||
canon: "amqp://host/%2F%2F",
|
||||
},
|
||||
|
||||
{
|
||||
url: "amqp://host/%2Fslash%2F",
|
||||
username: defaultURI.Username,
|
||||
password: defaultURI.Password,
|
||||
host: "host",
|
||||
port: defaultURI.Port,
|
||||
vhost: "/slash/",
|
||||
canon: "amqp://host/%2Fslash%2F",
|
||||
},
|
||||
|
||||
{
|
||||
url: "amqp://192.168.1.1:1000/",
|
||||
username: defaultURI.Username,
|
||||
password: defaultURI.Password,
|
||||
host: "192.168.1.1",
|
||||
port: 1000,
|
||||
vhost: defaultURI.Vhost,
|
||||
canon: "amqp://192.168.1.1:1000/",
|
||||
},
|
||||
|
||||
{
|
||||
url: "amqp://[::1]",
|
||||
username: defaultURI.Username,
|
||||
password: defaultURI.Password,
|
||||
host: "[::1]",
|
||||
port: defaultURI.Port,
|
||||
vhost: defaultURI.Vhost,
|
||||
canon: "amqp://[::1]/",
|
||||
},
|
||||
|
||||
{
|
||||
url: "amqp://[::1]:1000",
|
||||
username: defaultURI.Username,
|
||||
password: defaultURI.Password,
|
||||
host: "[::1]",
|
||||
port: 1000,
|
||||
vhost: defaultURI.Vhost,
|
||||
canon: "amqp://[::1]:1000/",
|
||||
},
|
||||
|
||||
{
|
||||
url: "amqps:///",
|
||||
username: defaultURI.Username,
|
||||
password: defaultURI.Password,
|
||||
host: defaultURI.Host,
|
||||
port: schemePorts["amqps"],
|
||||
vhost: defaultURI.Vhost,
|
||||
canon: "amqps://localhost/",
|
||||
},
|
||||
|
||||
{
|
||||
url: "amqps://host:1000/",
|
||||
username: defaultURI.Username,
|
||||
password: defaultURI.Password,
|
||||
host: "host",
|
||||
port: 1000,
|
||||
vhost: defaultURI.Vhost,
|
||||
canon: "amqps://host:1000/",
|
||||
},
|
||||
}
|
||||
|
||||
func TestURISpec(t *testing.T) {
|
||||
for _, test := range uriTests {
|
||||
u, err := ParseURI(test.url)
|
||||
if err != nil {
|
||||
t.Fatal("Could not parse spec URI: ", test.url, " err: ", err)
|
||||
}
|
||||
|
||||
if test.username != u.Username {
|
||||
t.Error("For: ", test.url, " usernames do not match. want: ", test.username, " got: ", u.Username)
|
||||
}
|
||||
|
||||
if test.password != u.Password {
|
||||
t.Error("For: ", test.url, " passwords do not match. want: ", test.password, " got: ", u.Password)
|
||||
}
|
||||
|
||||
if test.host != u.Host {
|
||||
t.Error("For: ", test.url, " hosts do not match. want: ", test.host, " got: ", u.Host)
|
||||
}
|
||||
|
||||
if test.port != u.Port {
|
||||
t.Error("For: ", test.url, " ports do not match. want: ", test.port, " got: ", u.Port)
|
||||
}
|
||||
|
||||
if test.vhost != u.Vhost {
|
||||
t.Error("For: ", test.url, " vhosts do not match. want: ", test.vhost, " got: ", u.Vhost)
|
||||
}
|
||||
|
||||
if test.canon != u.String() {
|
||||
t.Error("For: ", test.url, " canonical string does not match. want: ", test.canon, " got: ", u.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestURIUnknownScheme(t *testing.T) {
|
||||
if _, err := ParseURI("http://example.com/"); err == nil {
|
||||
t.Fatal("Expected error when parsing non-amqp scheme")
|
||||
}
|
||||
}
|
||||
|
||||
func TestURIScheme(t *testing.T) {
|
||||
if _, err := ParseURI("amqp://example.com/"); err != nil {
|
||||
t.Fatalf("Expected to parse amqp scheme, got %v", err)
|
||||
}
|
||||
|
||||
if _, err := ParseURI("amqps://example.com/"); err != nil {
|
||||
t.Fatalf("Expected to parse amqps scheme, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestURIDefaults(t *testing.T) {
|
||||
url := "amqp://"
|
||||
uri, err := ParseURI(url)
|
||||
if err != nil {
|
||||
t.Fatal("Could not parse")
|
||||
}
|
||||
|
||||
if uri.String() != "amqp://localhost/" {
|
||||
t.Fatal("Defaults not encoded properly got:", uri.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestURIComplete(t *testing.T) {
|
||||
url := "amqp://bob:dobbs@foo.bar:5678/private"
|
||||
uri, err := ParseURI(url)
|
||||
if err != nil {
|
||||
t.Fatal("Could not parse")
|
||||
}
|
||||
|
||||
if uri.String() != url {
|
||||
t.Fatal("Defaults not encoded properly want:", url, " got:", uri.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestURIDefaultPortAmqpNotIncluded(t *testing.T) {
|
||||
url := "amqp://foo.bar:5672/"
|
||||
uri, err := ParseURI(url)
|
||||
if err != nil {
|
||||
t.Fatal("Could not parse")
|
||||
}
|
||||
|
||||
if uri.String() != "amqp://foo.bar/" {
|
||||
t.Fatal("Defaults not encoded properly got:", uri.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestURIDefaultPortAmqp(t *testing.T) {
|
||||
url := "amqp://foo.bar/"
|
||||
uri, err := ParseURI(url)
|
||||
if err != nil {
|
||||
t.Fatal("Could not parse")
|
||||
}
|
||||
|
||||
if uri.Port != 5672 {
|
||||
t.Fatal("Default port not correct for amqp, got:", uri.Port)
|
||||
}
|
||||
}
|
||||
|
||||
func TestURIDefaultPortAmqpsNotIncludedInString(t *testing.T) {
|
||||
url := "amqps://foo.bar:5671/"
|
||||
uri, err := ParseURI(url)
|
||||
if err != nil {
|
||||
t.Fatal("Could not parse")
|
||||
}
|
||||
|
||||
if uri.String() != "amqps://foo.bar/" {
|
||||
t.Fatal("Defaults not encoded properly got:", uri.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestURIDefaultPortAmqps(t *testing.T) {
|
||||
url := "amqps://foo.bar/"
|
||||
uri, err := ParseURI(url)
|
||||
if err != nil {
|
||||
t.Fatal("Could not parse")
|
||||
}
|
||||
|
||||
if uri.Port != 5671 {
|
||||
t.Fatal("Default port not correct for amqps, got:", uri.Port)
|
||||
}
|
||||
}
|
411
vendor/github.com/streadway/amqp/write.go
generated
vendored
411
vendor/github.com/streadway/amqp/write.go
generated
vendored
@ -1,411 +0,0 @@
|
||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// Source code and contact info at http://github.com/streadway/amqp
|
||||
|
||||
package amqp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (me *writer) WriteFrame(frame frame) (err error) {
|
||||
if err = frame.write(me.w); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if buf, ok := me.w.(*bufio.Writer); ok {
|
||||
err = buf.Flush()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (me *methodFrame) write(w io.Writer) (err error) {
|
||||
var payload bytes.Buffer
|
||||
|
||||
if me.Method == nil {
|
||||
return errors.New("malformed frame: missing method")
|
||||
}
|
||||
|
||||
class, method := me.Method.id()
|
||||
|
||||
if err = binary.Write(&payload, binary.BigEndian, class); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = binary.Write(&payload, binary.BigEndian, method); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = me.Method.write(&payload); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return writeFrame(w, frameMethod, me.ChannelId, payload.Bytes())
|
||||
}
|
||||
|
||||
// Heartbeat
|
||||
//
|
||||
// Payload is empty
|
||||
func (me *heartbeatFrame) write(w io.Writer) (err error) {
|
||||
return writeFrame(w, frameHeartbeat, me.ChannelId, []byte{})
|
||||
}
|
||||
|
||||
// CONTENT HEADER
|
||||
// 0 2 4 12 14
|
||||
// +----------+--------+-----------+----------------+------------- - -
|
||||
// | class-id | weight | body size | property flags | property list...
|
||||
// +----------+--------+-----------+----------------+------------- - -
|
||||
// short short long long short remainder...
|
||||
//
|
||||
func (me *headerFrame) write(w io.Writer) (err error) {
|
||||
var payload bytes.Buffer
|
||||
var zeroTime time.Time
|
||||
|
||||
if err = binary.Write(&payload, binary.BigEndian, me.ClassId); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = binary.Write(&payload, binary.BigEndian, me.weight); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = binary.Write(&payload, binary.BigEndian, me.Size); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// First pass will build the mask to be serialized, second pass will serialize
|
||||
// each of the fields that appear in the mask.
|
||||
|
||||
var mask uint16
|
||||
|
||||
if len(me.Properties.ContentType) > 0 {
|
||||
mask = mask | flagContentType
|
||||
}
|
||||
if len(me.Properties.ContentEncoding) > 0 {
|
||||
mask = mask | flagContentEncoding
|
||||
}
|
||||
if me.Properties.Headers != nil && len(me.Properties.Headers) > 0 {
|
||||
mask = mask | flagHeaders
|
||||
}
|
||||
if me.Properties.DeliveryMode > 0 {
|
||||
mask = mask | flagDeliveryMode
|
||||
}
|
||||
if me.Properties.Priority > 0 {
|
||||
mask = mask | flagPriority
|
||||
}
|
||||
if len(me.Properties.CorrelationId) > 0 {
|
||||
mask = mask | flagCorrelationId
|
||||
}
|
||||
if len(me.Properties.ReplyTo) > 0 {
|
||||
mask = mask | flagReplyTo
|
||||
}
|
||||
if len(me.Properties.Expiration) > 0 {
|
||||
mask = mask | flagExpiration
|
||||
}
|
||||
if len(me.Properties.MessageId) > 0 {
|
||||
mask = mask | flagMessageId
|
||||
}
|
||||
if me.Properties.Timestamp != zeroTime {
|
||||
mask = mask | flagTimestamp
|
||||
}
|
||||
if len(me.Properties.Type) > 0 {
|
||||
mask = mask | flagType
|
||||
}
|
||||
if len(me.Properties.UserId) > 0 {
|
||||
mask = mask | flagUserId
|
||||
}
|
||||
if len(me.Properties.AppId) > 0 {
|
||||
mask = mask | flagAppId
|
||||
}
|
||||
|
||||
if err = binary.Write(&payload, binary.BigEndian, mask); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if hasProperty(mask, flagContentType) {
|
||||
if err = writeShortstr(&payload, me.Properties.ContentType); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(mask, flagContentEncoding) {
|
||||
if err = writeShortstr(&payload, me.Properties.ContentEncoding); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(mask, flagHeaders) {
|
||||
if err = writeTable(&payload, me.Properties.Headers); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(mask, flagDeliveryMode) {
|
||||
if err = binary.Write(&payload, binary.BigEndian, me.Properties.DeliveryMode); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(mask, flagPriority) {
|
||||
if err = binary.Write(&payload, binary.BigEndian, me.Properties.Priority); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(mask, flagCorrelationId) {
|
||||
if err = writeShortstr(&payload, me.Properties.CorrelationId); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(mask, flagReplyTo) {
|
||||
if err = writeShortstr(&payload, me.Properties.ReplyTo); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(mask, flagExpiration) {
|
||||
if err = writeShortstr(&payload, me.Properties.Expiration); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(mask, flagMessageId) {
|
||||
if err = writeShortstr(&payload, me.Properties.MessageId); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(mask, flagTimestamp) {
|
||||
if err = binary.Write(&payload, binary.BigEndian, uint64(me.Properties.Timestamp.Unix())); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(mask, flagType) {
|
||||
if err = writeShortstr(&payload, me.Properties.Type); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(mask, flagUserId) {
|
||||
if err = writeShortstr(&payload, me.Properties.UserId); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if hasProperty(mask, flagAppId) {
|
||||
if err = writeShortstr(&payload, me.Properties.AppId); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return writeFrame(w, frameHeader, me.ChannelId, payload.Bytes())
|
||||
}
|
||||
|
||||
// Body
|
||||
//
|
||||
// Payload is one byterange from the full body who's size is declared in the
|
||||
// Header frame
|
||||
func (me *bodyFrame) write(w io.Writer) (err error) {
|
||||
return writeFrame(w, frameBody, me.ChannelId, me.Body)
|
||||
}
|
||||
|
||||
func writeFrame(w io.Writer, typ uint8, channel uint16, payload []byte) (err error) {
|
||||
end := []byte{frameEnd}
|
||||
size := uint(len(payload))
|
||||
|
||||
_, err = w.Write([]byte{
|
||||
byte(typ),
|
||||
byte((channel & 0xff00) >> 8),
|
||||
byte((channel & 0x00ff) >> 0),
|
||||
byte((size & 0xff000000) >> 24),
|
||||
byte((size & 0x00ff0000) >> 16),
|
||||
byte((size & 0x0000ff00) >> 8),
|
||||
byte((size & 0x000000ff) >> 0),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = w.Write(payload); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = w.Write(end); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func writeShortstr(w io.Writer, s string) (err error) {
|
||||
b := []byte(s)
|
||||
|
||||
var length uint8 = uint8(len(b))
|
||||
|
||||
if err = binary.Write(w, binary.BigEndian, length); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = w.Write(b[:length]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func writeLongstr(w io.Writer, s string) (err error) {
|
||||
b := []byte(s)
|
||||
|
||||
var length uint32 = uint32(len(b))
|
||||
|
||||
if err = binary.Write(w, binary.BigEndian, length); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = w.Write(b[:length]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
'A': []interface{}
|
||||
'D': Decimal
|
||||
'F': Table
|
||||
'I': int32
|
||||
'S': string
|
||||
'T': time.Time
|
||||
'V': nil
|
||||
'b': byte
|
||||
'd': float64
|
||||
'f': float32
|
||||
'l': int64
|
||||
's': int16
|
||||
't': bool
|
||||
'x': []byte
|
||||
*/
|
||||
func writeField(w io.Writer, value interface{}) (err error) {
|
||||
var buf [9]byte
|
||||
var enc []byte
|
||||
|
||||
switch v := value.(type) {
|
||||
case bool:
|
||||
buf[0] = 't'
|
||||
if v {
|
||||
buf[1] = byte(1)
|
||||
} else {
|
||||
buf[1] = byte(0)
|
||||
}
|
||||
enc = buf[:2]
|
||||
|
||||
case byte:
|
||||
buf[0] = 'b'
|
||||
buf[1] = byte(v)
|
||||
enc = buf[:2]
|
||||
|
||||
case int16:
|
||||
buf[0] = 's'
|
||||
binary.BigEndian.PutUint16(buf[1:3], uint16(v))
|
||||
enc = buf[:3]
|
||||
|
||||
case int32:
|
||||
buf[0] = 'I'
|
||||
binary.BigEndian.PutUint32(buf[1:5], uint32(v))
|
||||
enc = buf[:5]
|
||||
|
||||
case int64:
|
||||
buf[0] = 'l'
|
||||
binary.BigEndian.PutUint64(buf[1:9], uint64(v))
|
||||
enc = buf[:9]
|
||||
|
||||
case float32:
|
||||
buf[0] = 'f'
|
||||
binary.BigEndian.PutUint32(buf[1:5], math.Float32bits(v))
|
||||
enc = buf[:5]
|
||||
|
||||
case float64:
|
||||
buf[0] = 'd'
|
||||
binary.BigEndian.PutUint64(buf[1:9], math.Float64bits(v))
|
||||
enc = buf[:9]
|
||||
|
||||
case Decimal:
|
||||
buf[0] = 'D'
|
||||
buf[1] = byte(v.Scale)
|
||||
binary.BigEndian.PutUint32(buf[2:6], uint32(v.Value))
|
||||
enc = buf[:6]
|
||||
|
||||
case string:
|
||||
buf[0] = 'S'
|
||||
binary.BigEndian.PutUint32(buf[1:5], uint32(len(v)))
|
||||
enc = append(buf[:5], []byte(v)...)
|
||||
|
||||
case []interface{}: // field-array
|
||||
buf[0] = 'A'
|
||||
|
||||
sec := new(bytes.Buffer)
|
||||
for _, val := range v {
|
||||
if err = writeField(sec, val); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint32(buf[1:5], uint32(sec.Len()))
|
||||
if _, err = w.Write(buf[:5]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = w.Write(sec.Bytes()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
case time.Time:
|
||||
buf[0] = 'T'
|
||||
binary.BigEndian.PutUint64(buf[1:9], uint64(v.Unix()))
|
||||
enc = buf[:9]
|
||||
|
||||
case Table:
|
||||
if _, err = w.Write([]byte{'F'}); err != nil {
|
||||
return
|
||||
}
|
||||
return writeTable(w, v)
|
||||
|
||||
case []byte:
|
||||
buf[0] = 'x'
|
||||
binary.BigEndian.PutUint32(buf[1:5], uint32(len(v)))
|
||||
if _, err = w.Write(buf[0:5]); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = w.Write(v); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
|
||||
case nil:
|
||||
buf[0] = 'V'
|
||||
enc = buf[:1]
|
||||
|
||||
default:
|
||||
return ErrFieldType
|
||||
}
|
||||
|
||||
_, err = w.Write(enc)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func writeTable(w io.Writer, table Table) (err error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
for key, val := range table {
|
||||
if err = writeShortstr(&buf, key); err != nil {
|
||||
return
|
||||
}
|
||||
if err = writeField(&buf, val); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return writeLongstr(w, string(buf.Bytes()))
|
||||
}
|
Loading…
Reference in New Issue
Block a user