cdncontrol 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +4 -0
- data/Gemfile +3 -0
- data/LICENSE.md +27 -0
- data/README.md +190 -0
- data/Rakefile +2 -0
- data/bin/cdncontrol +128 -0
- data/cdncontrol.conf.example +26 -0
- data/cdncontrol.gemspec +21 -0
- data/lib/cdncontrol/trafficmanager.rb +297 -0
- metadata +95 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
Y2ZlOTNjMjAyYzc0ZDhlZmE3MTMzNDdkMjBmMzgyMmI2NzAyNGY0ZA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NDc2OWY3OTk1YjJlM2E1NzM4ODA1NGUzMjM4ZGY1NTgwZDE1ODNjNw==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
Y2ZlYTRlOWJmNWMyOTFlMDY3YzhjYzhmYjAzMGExYTAyYmYwYWNjN2JiZDgz
|
10
|
+
MTQzY2U1N2E2MmM3NDI2NzViM2UxNjVjYjdjNmM4NjVmMmYyYjNiMGY4ZWYy
|
11
|
+
OTFjY2MzM2E4NWY3Y2I3NThjOWYyZDRiNmQyNzkwYTgyZjQwMTc=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MjYwN2U0OTM3MDdkZDlkMGNmZGY5ZTAyY2UzMzY0ZTZiMzkxZDM2MmY3YjVi
|
14
|
+
NzYxYTZmYzlhOWY2ZGQ2NWIyNDk2ZTdjNjQ5OGNlNTgyNmU2ZGYzOTU5Yzc1
|
15
|
+
Y2EwMTY2MzNkODg4MTdiZmU5YTk4ZjI4MDFlMjQ3NjZiNTg5MjY=
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
CDNControl
|
2
|
+
----------
|
3
|
+
* Author:: Jon Cowie (<jcowie@etsy.com>), Marcus Barczak (<mbarczak@etsy.com>), Avleen Vig (<avig@etsy.com>)
|
4
|
+
* Copyright:: Copyright (c) 2013 Etsy Inc
|
5
|
+
* License:: MIT
|
6
|
+
|
7
|
+
The MIT License
|
8
|
+
|
9
|
+
Copyright (c) 2004 Stan Salvador (stansalvador@hotmail.com), Philip Chan (pkc@cs.fit.edu)
|
10
|
+
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
13
|
+
in the Software without restriction, including without limitation the rights
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
16
|
+
furnished to do so, subject to the following conditions:
|
17
|
+
|
18
|
+
The above copyright notice and this permission notice shall be included in
|
19
|
+
all copies or substantial portions of the Software.
|
20
|
+
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
27
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
CDNControl
|
2
|
+
===========
|
3
|
+
CDNControl is a rubygem which provides an interface to Dyn's GSLB service. It's used by Etsy to control the balance of traffic between our CDN providers, and also to enable or disable individual CDNs.
|
4
|
+
|
5
|
+
Installation
|
6
|
+
------------
|
7
|
+
|
8
|
+
### Gem Install
|
9
|
+
`cdncontrol` is available on rubygems. Add the following to your `Gemfile`:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'cdncontrol'
|
13
|
+
```
|
14
|
+
|
15
|
+
or install the gem manually:
|
16
|
+
|
17
|
+
```bash
|
18
|
+
gem install cdncontrol
|
19
|
+
```
|
20
|
+
|
21
|
+
The gem installs the *cdncontrol* binary at ```/usr/bin/cdncontrol```
|
22
|
+
CDNControl Configuration
|
23
|
+
-------------------
|
24
|
+
CDNControl requires a configuration file in order to function, which needs to be in ```/usr/local/etc/cdncontrol.conf```
|
25
|
+
|
26
|
+
|
27
|
+
Below is a sample config file with all supported options included, followed by an explanation of each section.
|
28
|
+
|
29
|
+
```yaml
|
30
|
+
organization: "myorganisation"
|
31
|
+
username: "username"
|
32
|
+
password: "password"
|
33
|
+
output_path: "/var/www"
|
34
|
+
cdncontrol_ui_hostname: "http://cdn.mydomain.com"
|
35
|
+
|
36
|
+
|
37
|
+
valid_providers:
|
38
|
+
- provider1
|
39
|
+
- provider2
|
40
|
+
- provider3
|
41
|
+
|
42
|
+
targets:
|
43
|
+
target1:
|
44
|
+
zone: myzone.com
|
45
|
+
nodes:
|
46
|
+
- 1.myzone.com
|
47
|
+
- 2.myzone.com
|
48
|
+
- 3.myzone.com
|
49
|
+
- 4.myzone.com
|
50
|
+
graph_url: "<your_graphite_cdn_metric_url>"
|
51
|
+
graph_color_key:
|
52
|
+
provider1: "#FF7400"
|
53
|
+
provider2: "#1240AB"
|
54
|
+
provider3: "#00CC00"
|
55
|
+
provider4: "#380470"
|
56
|
+
|
57
|
+
```
|
58
|
+
|
59
|
+
#### Organization
|
60
|
+
The `organization` directive specifies the customer name you use when logging into the DynECT portal
|
61
|
+
|
62
|
+
####Username
|
63
|
+
The `username` directive specifies the username to use when authenticating to the Dyn API.
|
64
|
+
|
65
|
+
####Password
|
66
|
+
The `password` directive specifies the password to use when authenticating to the Dyn API.
|
67
|
+
|
68
|
+
####Output Path
|
69
|
+
The `output_path` directive specifies where cdncontrol should dump JSON containing the CDN balances.
|
70
|
+
|
71
|
+
####CDNControl UI Hostname (Optional)
|
72
|
+
The `cdncontrol_ui_hostname` directive specifies the hostname where the CDNControlUI web application can be reached, if you're using it.
|
73
|
+
|
74
|
+
####Valid Providers
|
75
|
+
The `valid_providers` section specifies the valid CDN providers which may be configured with this tool. This corresponds to the names of the global pools you have configured on the Dyn GSLB platform.
|
76
|
+
|
77
|
+
####Targets
|
78
|
+
The `targets` section of the config file lists the different site configurations you want to use cdncontrol to manage. The following are the parameters which can be used to configure a target (in this case, we're looking at the parameters of *target1* above:
|
79
|
+
|
80
|
+
* **zone**: This is the name used to configure the site in Dyn's GSLB platform. In the DynECT web interface, this is the top-level site from which all of your nodes are configured
|
81
|
+
* **nodes**: These are the nodes under the zone which are configured to use GSLB. For example, if your zone is *mydomain.com*, you might configure GSLB for *img0.mydomain.com* and *img1.mydomain.com*. These should all be specified here.
|
82
|
+
* **graph_url** (optional): If you're using the CDNControlUI web interface to this tool, this option lets you specify a graph_url to be displayed on the page for this target.
|
83
|
+
* **graph_color_key** (optional): If you're using the CDNControlUI web interface to this tool, this option lets you specify a color key to be displayed above the graph (to indicate which CDN is which color, for example).
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
CDNControl Usage
|
88
|
+
================
|
89
|
+
|
90
|
+
### Supported Options
|
91
|
+
```
|
92
|
+
$> cdncontrol
|
93
|
+
Usage: cdncontrol [-tpwmcsav]
|
94
|
+
Please specify the command in one of the following formats:
|
95
|
+
|
96
|
+
cdncontrol -t TARGET --show
|
97
|
+
cdncontrol -t TARGET --write
|
98
|
+
cdncontrol -t TARGET -p PROVIDER -w WEIGHT
|
99
|
+
cdncontrol -t TARGET -p PROVIDER -m MODE
|
100
|
+
|
101
|
+
Specific options:
|
102
|
+
-t, --target The configuration to work on (one of <targets specified in your config file>)
|
103
|
+
-p, --provider Provider to modify (one of <providers specified in your config file>)
|
104
|
+
-w, --weight Weight of traffic to send to provider (value between 1 and 15)
|
105
|
+
-m, --mode Set the serve mode of the provider (one of always,obey,remove,no)
|
106
|
+
-c, --cname Target CNAME for new provider
|
107
|
+
-s, --show Show current provider ratios
|
108
|
+
-v, --verbose Show me in excrutiating detail what is happening
|
109
|
+
--write Dump all weights out to JSON files
|
110
|
+
```
|
111
|
+
|
112
|
+
### Viewing target details
|
113
|
+
```
|
114
|
+
$> cdncontrol -t test --show
|
115
|
+
|
116
|
+
** connecting to dynect API
|
117
|
+
** fetching node details .......done!
|
118
|
+
|
119
|
+
NODE BALANCE
|
120
|
+
============
|
121
|
+
img0.mydomain.com
|
122
|
+
provider1 weight = 15 | serve_mode = always | status = up | address = cdn.provider1.com.
|
123
|
+
provider2 weight = 15 | serve_mode = always | status = up | address = cdn.provider2.com.
|
124
|
+
provider3 weight = 15 | serve_mode = always | status = up | address = cdn.provider3.com.
|
125
|
+
```
|
126
|
+
|
127
|
+
### Writing target details as JSON
|
128
|
+
```
|
129
|
+
$ cdncontrol -t test --write
|
130
|
+
** connecting to dynect API
|
131
|
+
** fetching node details .......done!
|
132
|
+
** Updated details in /var/www/cdn_test.json
|
133
|
+
```
|
134
|
+
|
135
|
+
### Setting the weight of a target's provider
|
136
|
+
```
|
137
|
+
$> cdncontrol -t test -p provider1 -w 5
|
138
|
+
** connecting to dynect API
|
139
|
+
** fetching node details .......done!
|
140
|
+
|
141
|
+
CURRENT LIVE WEIGHTS
|
142
|
+
====================
|
143
|
+
img0.mydomain.com
|
144
|
+
provider1 weight = 15 | serve_mode = always | status = up | address = cdn.provider1.com.
|
145
|
+
provider2 weight = 15 | serve_mode = always | status = up | address = cdn.provider2.com.
|
146
|
+
provider3 weight = 15 | serve_mode = always | status = up | address = cdn.provider3.com.
|
147
|
+
|
148
|
+
You're about to modify the weight of provider1 to 5 are you sure (Y|N)? Y
|
149
|
+
|
150
|
+
** setting weight = 5 on GSLBRegionPoolEntry/myorg-mydomain.com/img0.mydomain.com/global/cdn.provider1.com.
|
151
|
+
|
152
|
+
** fetching node details .......done!
|
153
|
+
|
154
|
+
NODE WEIGHTS AFTER CHANGE
|
155
|
+
=========================
|
156
|
+
img0.mydomain.com
|
157
|
+
provider1 weight = 5 | serve_mode = always | status = up | address = cdn.provider1.com.
|
158
|
+
provider2 weight = 15 | serve_mode = always | status = up | address = cdn.provider2.com.
|
159
|
+
provider3 weight = 15 | serve_mode = always | status = up | address = cdn.provider3.com.
|
160
|
+
|
161
|
+
** Updated details in /var/www/cdn_test.json
|
162
|
+
```
|
163
|
+
|
164
|
+
### Setting the mode of a target's provider
|
165
|
+
```
|
166
|
+
cdncontrol -t test -p provider1 -m no
|
167
|
+
** connecting to dynect API
|
168
|
+
** fetching node details .......done!
|
169
|
+
|
170
|
+
CURRENT SERVE MODES AND WEIGHTS
|
171
|
+
===============================
|
172
|
+
img0.mydomain.com
|
173
|
+
provider1 weight = 15 | serve_mode = always | status = up | address = cdn.provider1.com.
|
174
|
+
provider2 weight = 15 | serve_mode = always | status = up | address = cdn.provider2.com.
|
175
|
+
provider3 weight = 15 | serve_mode = always | status = up | address = cdn.provider3.com.
|
176
|
+
|
177
|
+
You're about to change the serving mode for provider1 to 'no' are you sure (Y|N)? Y
|
178
|
+
|
179
|
+
** setting serve_mode = no on GSLBRegionPoolEntry/myorg-mydomain.com/img0.mydomain.com/global/cdn.provider1.com.
|
180
|
+
** fetching node details .......done!
|
181
|
+
|
182
|
+
NODE SERVE MODES AFTER CHANGE
|
183
|
+
=============================
|
184
|
+
img0.mydomain.com
|
185
|
+
provider1 weight = 15 | serve_mode = no | status = up | address = cdn.provider1.com.
|
186
|
+
provider2 weight = 15 | serve_mode = always | status = up | address = cdn.provider2.com.
|
187
|
+
provider3 weight = 15 | serve_mode = always | status = up | address = cdn.provider3.com.
|
188
|
+
|
189
|
+
** Updated details in /var/www/cdn_test.json
|
190
|
+
```
|
data/Rakefile
ADDED
data/bin/cdncontrol
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'choice'
|
4
|
+
require 'json'
|
5
|
+
require 'yaml'
|
6
|
+
require 'pp'
|
7
|
+
require 'cdncontrol/trafficmanager'
|
8
|
+
|
9
|
+
CONFIG = YAML.load_file("/usr/local/etc/cdncontrol.conf")
|
10
|
+
|
11
|
+
# TODO: this should be dynamic
|
12
|
+
VALID_PROVIDERS = CONFIG["valid_providers"]
|
13
|
+
VALID_MODES = CONFIG["valid_modes"] || [ "always", "obey", "remove", "no" ]
|
14
|
+
OUTPUT_PATH = CONFIG["output_path"] || "/tmp"
|
15
|
+
VALID_TARGETS = CONFIG["targets"].keys
|
16
|
+
options = { show: false, add: false, verbose: false }
|
17
|
+
|
18
|
+
Choice.options do
|
19
|
+
header "Please specify the command in one of the following formats:"
|
20
|
+
header ""
|
21
|
+
header "cdncontrol -t TARGET --show"
|
22
|
+
header "cdncontrol -t TARGET --write"
|
23
|
+
header "cdncontrol -t TARGET -p PROVIDER -w WEIGHT"
|
24
|
+
header "cdncontrol -t TARGET -p PROVIDER -m MODE"
|
25
|
+
header ""
|
26
|
+
header "Specific options:"
|
27
|
+
|
28
|
+
option :target, :required => false do
|
29
|
+
short '-t'
|
30
|
+
long '--target'
|
31
|
+
desc "The configuration to work on (one of #{VALID_TARGETS.join(",")})"
|
32
|
+
validate /^(#{VALID_TARGETS.join("|")})$/
|
33
|
+
end
|
34
|
+
|
35
|
+
option :provider, :required => false do
|
36
|
+
short '-p'
|
37
|
+
long '--provider'
|
38
|
+
desc "Provider to modify (one of #{VALID_PROVIDERS.join(",")})"
|
39
|
+
validate /^(#{VALID_PROVIDERS.join("|")})$/
|
40
|
+
end
|
41
|
+
|
42
|
+
option :weight, :required => false do
|
43
|
+
short '-w'
|
44
|
+
long '--weight'
|
45
|
+
desc 'Weight of traffic to send to provider (value between 1 and 15)'
|
46
|
+
validate /^([1-9]|1[0-5])$/
|
47
|
+
end
|
48
|
+
|
49
|
+
option :mode, :required => false do
|
50
|
+
short '-m'
|
51
|
+
long '--mode'
|
52
|
+
desc "Set the serve mode of the provider (one of #{VALID_MODES.join(",")})"
|
53
|
+
validate /^(#{VALID_MODES.join("|")})$/
|
54
|
+
end
|
55
|
+
|
56
|
+
option :show, :required => false do
|
57
|
+
short '-s'
|
58
|
+
long '--show'
|
59
|
+
desc 'Show current provider ratios'
|
60
|
+
end
|
61
|
+
|
62
|
+
option :verbose, :required => false do
|
63
|
+
short '-v'
|
64
|
+
long '--verbose'
|
65
|
+
desc 'Show me in excrutiating detail what is happening'
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
option :write, :required => false do
|
70
|
+
long '--write'
|
71
|
+
desc 'Dump all weights out to JSON files for the dashboard'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
options[:provider] = Choice.choices[:provider] unless !Choice.choices[:provider]
|
76
|
+
options[:target] = Choice.choices[:target] unless !Choice.choices[:target]
|
77
|
+
options[:mode] = Choice.choices[:mode] unless !Choice.choices[:mode]
|
78
|
+
options[:weight] = Choice.choices[:weight] unless !Choice.choices[:weight]
|
79
|
+
options[:show] = Choice.choices[:show] unless !Choice.choices[:show]
|
80
|
+
options[:verbose] = Choice.choices[:verbose] unless !Choice.choices[:verbose]
|
81
|
+
options[:write] = Choice.choices[:write] unless !Choice.choices[:write]
|
82
|
+
|
83
|
+
unless (options.has_key?(:target) && options[:show]) ||
|
84
|
+
(options.has_key?(:target) && options.has_key?(:write)) ||
|
85
|
+
(options.has_key?(:target) && options.has_key?(:provider) && options.has_key?(:weight)) ||
|
86
|
+
(options.has_key?(:target) && options.has_key?(:provider) && options.has_key?(:mode))
|
87
|
+
Choice.help
|
88
|
+
end
|
89
|
+
|
90
|
+
# trap SIGINT and return a clean exit message rather than stack trace
|
91
|
+
Signal.trap('INT') {
|
92
|
+
printf "\nAborting!\n"
|
93
|
+
exit
|
94
|
+
}
|
95
|
+
|
96
|
+
## action starts here
|
97
|
+
|
98
|
+
target = CONFIG['targets'][options[:target]]
|
99
|
+
|
100
|
+
tm = CDNControl::TrafficManager.new(CONFIG,target, options[:verbose])
|
101
|
+
|
102
|
+
if options[:show]
|
103
|
+
tm.show_balance
|
104
|
+
end
|
105
|
+
|
106
|
+
if options[:write]
|
107
|
+
tm.dump_weights(options[:target],OUTPUT_PATH)
|
108
|
+
end
|
109
|
+
|
110
|
+
if options.has_key?(:provider) && options.has_key?(:weight)
|
111
|
+
tm.show_balance("CURRENT LIVE WEIGHTS")
|
112
|
+
tm.set_weight(options[:provider], options[:weight])
|
113
|
+
tm.show_balance("NODE WEIGHTS AFTER CHANGE")
|
114
|
+
tm.dump_weights(options[:target],OUTPUT_PATH)
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
if options.has_key?(:provider) && options.has_key?(:mode)
|
119
|
+
tm.show_balance("CURRENT SERVE MODES AND WEIGHTS")
|
120
|
+
tm.set_serve_mode(options[:provider], options[:mode])
|
121
|
+
tm.show_balance("NODE SERVE MODES AFTER CHANGE")
|
122
|
+
tm.dump_weights(options[:target],OUTPUT_PATH)
|
123
|
+
end
|
124
|
+
|
125
|
+
# display a nag if configured
|
126
|
+
if options[:show] != true && CONFIG['targets'][options[:target]].has_key?('nag')
|
127
|
+
puts "** NOTE: #{CONFIG['targets'][options[:target]]['nag']}"
|
128
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
organization: "myorganisation"
|
2
|
+
username: "username"
|
3
|
+
password: "password"
|
4
|
+
output_path: "/var/www"
|
5
|
+
cdncontrol_ui_hostname: "http://cdn.mydomain.com"
|
6
|
+
|
7
|
+
|
8
|
+
valid_providers:
|
9
|
+
- provider1
|
10
|
+
- provider2
|
11
|
+
- provider3
|
12
|
+
|
13
|
+
targets:
|
14
|
+
target1:
|
15
|
+
zone: myzone.com
|
16
|
+
nodes:
|
17
|
+
- 1.myzone.com
|
18
|
+
- 2.myzone.com
|
19
|
+
- 3.myzone.com
|
20
|
+
- 4.myzone.com
|
21
|
+
graph_url: "<your_graphite_cdn_metric_url>"
|
22
|
+
graph_color_key:
|
23
|
+
provider1: "#FF7400"
|
24
|
+
provider2: "#1240AB"
|
25
|
+
provider3: "#00CC00"
|
26
|
+
provider4: "#380470"
|
data/cdncontrol.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$:.push File.expand_path('../lib', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = 'cdn_control'
|
5
|
+
gem.version = '0.0.9'
|
6
|
+
gem.authors = ["Jon Cowie", "Marcus Barczak"]
|
7
|
+
gem.email = 'jonlives@gmail.com'
|
8
|
+
gem.homepage = 'https://github.com/etsy/cdncontrol'
|
9
|
+
gem.summary = "Tool for managing multiple CDN balances on Dyn's GSLB"
|
10
|
+
gem.description = "Tool for managing multiple CDN balances on Dyn's GSLB"
|
11
|
+
|
12
|
+
gem.files = `git ls-files`.split($\)
|
13
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
14
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
15
|
+
gem.name = "cdncontrol"
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
|
18
|
+
gem.add_runtime_dependency 'dynect_rest', '>= 0.4.3'
|
19
|
+
gem.add_runtime_dependency 'choice'
|
20
|
+
gem.add_runtime_dependency 'app_conf'
|
21
|
+
end
|
@@ -0,0 +1,297 @@
|
|
1
|
+
require 'app_conf'
|
2
|
+
require 'json'
|
3
|
+
require 'dynect_rest'
|
4
|
+
|
5
|
+
module CDNControl
|
6
|
+
class TrafficManager
|
7
|
+
|
8
|
+
# The initialize method for the TrafficManager Class.
|
9
|
+
# Expects:
|
10
|
+
# config: Valid config in YAML format
|
11
|
+
# target: The target from the config which this instance is to be used with (String)
|
12
|
+
# verbose (optional): Whether or not to log verbosely (Boolean)
|
13
|
+
# Returns:
|
14
|
+
# nothing
|
15
|
+
def initialize(config,target, verbose=false)
|
16
|
+
@config=config
|
17
|
+
@zone = target['zone']
|
18
|
+
@nodes = target['nodes']
|
19
|
+
@verbose = verbose
|
20
|
+
@weights = Hash.new
|
21
|
+
@user = nil
|
22
|
+
|
23
|
+
if @config['eventinator_server']
|
24
|
+
require 'eventinator-client'
|
25
|
+
end
|
26
|
+
|
27
|
+
# connect to API
|
28
|
+
puts "** connecting to dynect API"
|
29
|
+
@dyn = DynectRest.new(@config['organization'], @config['username'], @config['password'], @zone)
|
30
|
+
@weights = fetch_weights
|
31
|
+
end
|
32
|
+
|
33
|
+
def eventinate(msg)
|
34
|
+
if @config['eventinator_server']
|
35
|
+
client = EventinatorClient.new @config['eventinator_server']
|
36
|
+
|
37
|
+
if !@user.nil?
|
38
|
+
user = @user
|
39
|
+
else
|
40
|
+
user = ENV['SUDO_USER'] || ENV['USER']
|
41
|
+
end
|
42
|
+
|
43
|
+
puts "User: #{user}"
|
44
|
+
payload = {
|
45
|
+
:status => msg,
|
46
|
+
:username => user,
|
47
|
+
:tag => "cdncontrol"
|
48
|
+
}
|
49
|
+
|
50
|
+
begin
|
51
|
+
client.create_oneshot(payload)
|
52
|
+
rescue
|
53
|
+
puts "** Couldn't reach the eventinator server, not eventinating"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Mutator method for the @user global
|
59
|
+
# Expects:
|
60
|
+
# user: Username to set (String)
|
61
|
+
# Returns:
|
62
|
+
# nothing
|
63
|
+
def set_user(user)
|
64
|
+
@user = user
|
65
|
+
end
|
66
|
+
|
67
|
+
# Method to get a list of CDN providers from Dyn
|
68
|
+
# Expects:
|
69
|
+
# nothing
|
70
|
+
# Returns:
|
71
|
+
# Array of strings
|
72
|
+
def get_providers
|
73
|
+
@weights.map{|k,v|v.keys}.flatten.uniq
|
74
|
+
end
|
75
|
+
|
76
|
+
# Accessor method for the @weights global
|
77
|
+
# Expects:
|
78
|
+
# nothing
|
79
|
+
# Returns:
|
80
|
+
# Hash of {string,int}
|
81
|
+
def get_weights
|
82
|
+
@weights
|
83
|
+
end
|
84
|
+
|
85
|
+
# Get list of nodes configured for this target
|
86
|
+
# Expects:
|
87
|
+
# nothing
|
88
|
+
# Returns:
|
89
|
+
# Array of strings
|
90
|
+
def get_nodes
|
91
|
+
@weights.keys
|
92
|
+
end
|
93
|
+
|
94
|
+
# Method to get weights from Dyn's API
|
95
|
+
# Expects:
|
96
|
+
# nothing
|
97
|
+
# Returns:
|
98
|
+
# Hash of {string,int}
|
99
|
+
def fetch_weights
|
100
|
+
weights = {}
|
101
|
+
|
102
|
+
print "** fetching node details "
|
103
|
+
nodes_queue = Queue.new
|
104
|
+
@nodes.each do |node|
|
105
|
+
weights[node] = {}
|
106
|
+
nodes_queue << node
|
107
|
+
end
|
108
|
+
|
109
|
+
threads = []
|
110
|
+
nodes_queue.size.times do |i|
|
111
|
+
threads << Thread.new do
|
112
|
+
node = nodes_queue.pop
|
113
|
+
path = "GSLBRegionPoolEntry/#{@zone}/#{node}/global"
|
114
|
+
|
115
|
+
if @verbose
|
116
|
+
puts "** fetching #{path}"
|
117
|
+
else
|
118
|
+
print "."
|
119
|
+
end
|
120
|
+
|
121
|
+
# Make a thread-local connection to dyn, so that we open many
|
122
|
+
# connections at once.
|
123
|
+
dyn_conn = DynectRest.new('etsy', @config['username'], @config['password'], @zone)
|
124
|
+
pool = dyn_conn.get(path)
|
125
|
+
|
126
|
+
pool.each do |address|
|
127
|
+
address.gsub!("\/REST\/", "")
|
128
|
+
|
129
|
+
if @verbose
|
130
|
+
puts " ** fetching #{address}"
|
131
|
+
else
|
132
|
+
print "."
|
133
|
+
end
|
134
|
+
|
135
|
+
address_detail = dyn_conn.get(address)
|
136
|
+
label = address_detail['label']
|
137
|
+
weights[node][label] = address_detail
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
threads.each(&:join)
|
142
|
+
print "done!\n" unless @verbose
|
143
|
+
|
144
|
+
@weu
|
145
|
+
weights
|
146
|
+
end
|
147
|
+
|
148
|
+
# Method to show current balance
|
149
|
+
# Expects:
|
150
|
+
# nothing
|
151
|
+
# Returns:
|
152
|
+
# nothing (prints to screen)
|
153
|
+
def show_balance(header = "NODE BALANCE")
|
154
|
+
|
155
|
+
puts "\n#{header}"
|
156
|
+
puts "=" * header.length
|
157
|
+
|
158
|
+
@weights.each do |node,providers|
|
159
|
+
puts "#{node}"
|
160
|
+
providers.each do |label,detail|
|
161
|
+
printf " %-12s weight = %2d | serve_mode = %-8s | status = %-4s | address = %s\n",
|
162
|
+
label, detail['weight'], detail['serve_mode'], detail['status'], detail['address']
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
puts ""
|
167
|
+
end
|
168
|
+
|
169
|
+
# Method to set weight for a provider
|
170
|
+
# Expects:
|
171
|
+
# provider: Provider to set weight for (String)
|
172
|
+
# weight: Weight to set provider to (Int)
|
173
|
+
# safe: Whether or not to prompt to confirmation (Boolean, default to true)
|
174
|
+
# Returns:
|
175
|
+
# nothing
|
176
|
+
def set_weight(provider, weight, safe=true)
|
177
|
+
get_yn("You're about to modify the weight of #{provider} to #{weight} are you sure (Y|N)?") unless safe == false
|
178
|
+
# do it
|
179
|
+
@nodes.each do |node|
|
180
|
+
old_weight = @weights[node][provider]['weight']
|
181
|
+
address = @weights[node][provider]['address']
|
182
|
+
|
183
|
+
if weight == old_weight
|
184
|
+
puts "** node #{node} weight is already #{old_weight} no change made"
|
185
|
+
else
|
186
|
+
path = "GSLBRegionPoolEntry/#{@zone}/#{node}/global/#{address}"
|
187
|
+
puts "** setting weight = #{weight} on #{path}"
|
188
|
+
@dyn.put(path, { "weight" => weight })
|
189
|
+
|
190
|
+
eventinate("modified weight of #{provider} to #{weight} for '#{node}'")
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
puts ""
|
195
|
+
|
196
|
+
# fetch the updated values from dyn
|
197
|
+
@weights = fetch_weights
|
198
|
+
end
|
199
|
+
|
200
|
+
# Method to set mode for a provider
|
201
|
+
# Expects:
|
202
|
+
# provider: Provider to set weight for (String)
|
203
|
+
# mode: Mode to set provider to (String)
|
204
|
+
# safe: Whether or not to prompt to confirmation (Boolean, default to true)
|
205
|
+
# Returns:
|
206
|
+
# nothing
|
207
|
+
def set_serve_mode(provider, mode, safe=true)
|
208
|
+
get_yn("You're about to change the serving mode for #{provider} to '#{mode}' are you sure (Y|N)?") unless safe == false
|
209
|
+
|
210
|
+
# do it
|
211
|
+
@nodes.each do |node|
|
212
|
+
old_mode = @weights[node][provider]['serve_mode']
|
213
|
+
address = @weights[node][provider]['address']
|
214
|
+
|
215
|
+
if mode == old_mode
|
216
|
+
puts "** node #{node} serve mode is already set to '#{old_mode}'"
|
217
|
+
else
|
218
|
+
path = "GSLBRegionPoolEntry/#{@zone}/#{node}/global/#{address}"
|
219
|
+
puts "** setting serve_mode = #{mode} on #{path}"
|
220
|
+
@dyn.put(path, { "serve_mode" => mode })
|
221
|
+
|
222
|
+
eventinate("modified serve_mode of #{provider} from '#{old_mode}' to '#{mode}' on #{node}")
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# fetch the updated values from dyn
|
227
|
+
@weights = fetch_weights
|
228
|
+
end
|
229
|
+
|
230
|
+
# Method to set dump current weights to JSON
|
231
|
+
# Expects:
|
232
|
+
# target: Target of which to dump weights (String)
|
233
|
+
# output_path: File path to dump JSON files to
|
234
|
+
# Returns:
|
235
|
+
# nothing
|
236
|
+
def dump_weights(target,output_path)
|
237
|
+
## jankiest thing that will work development approach
|
238
|
+
|
239
|
+
summary = {}
|
240
|
+
|
241
|
+
# assetion here is every node in the target group has same weight (should be true)
|
242
|
+
key = @weights.keys.first
|
243
|
+
@weights[key].each do |provider, config|
|
244
|
+
enabled = config["serve_mode"] != "no" ? true : false
|
245
|
+
weight = config["weight"]
|
246
|
+
summary[provider] = { :enabled => enabled, :weight => weight }
|
247
|
+
end
|
248
|
+
|
249
|
+
total_weight = 0
|
250
|
+
# calculate percentages
|
251
|
+
summary.each do |provider,config|
|
252
|
+
if config[:enabled]
|
253
|
+
total_weight += config[:weight]
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
summary.each do |provider,config|
|
258
|
+
if config[:enabled]
|
259
|
+
pct = (config[:weight].to_f / total_weight) * 100
|
260
|
+
else
|
261
|
+
pct = 0
|
262
|
+
end
|
263
|
+
summary[provider][:pct] = pct
|
264
|
+
end
|
265
|
+
|
266
|
+
fn = "#{output_path}/cdn_#{target}.json"
|
267
|
+
|
268
|
+
begin
|
269
|
+
File.open(fn, 'w') {|f| f.write(summary.to_json) }
|
270
|
+
rescue Exception => e
|
271
|
+
puts "** WARNING: was unable to update JSON (#{e.message}), dashboards will be inconsistent"
|
272
|
+
else
|
273
|
+
puts "** Updated details in #{fn}"
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# Helper method to get y/n response
|
278
|
+
# Expects:
|
279
|
+
# message: Message for y/n prompt (String)
|
280
|
+
# Returns:
|
281
|
+
# nothing
|
282
|
+
def get_yn(message)
|
283
|
+
while true
|
284
|
+
print "#{message} "
|
285
|
+
case STDIN.gets.strip
|
286
|
+
when 'N', 'n'
|
287
|
+
puts "Aborting!"
|
288
|
+
exit
|
289
|
+
when 'Y', 'y'
|
290
|
+
puts ""
|
291
|
+
break
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
end
|
297
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cdncontrol
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.9
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jon Cowie
|
8
|
+
- Marcus Barczak
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-11-14 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: dynect_rest
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ! '>='
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 0.4.3
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ! '>='
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 0.4.3
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: choice
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ! '>='
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ! '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: app_conf
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
description: Tool for managing multiple CDN balances on Dyn's GSLB
|
57
|
+
email: jonlives@gmail.com
|
58
|
+
executables:
|
59
|
+
- cdncontrol
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- .gitignore
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE.md
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- bin/cdncontrol
|
69
|
+
- cdncontrol.conf.example
|
70
|
+
- cdncontrol.gemspec
|
71
|
+
- lib/cdncontrol/trafficmanager.rb
|
72
|
+
homepage: https://github.com/etsy/cdncontrol
|
73
|
+
licenses: []
|
74
|
+
metadata: {}
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 2.1.10
|
92
|
+
signing_key:
|
93
|
+
specification_version: 4
|
94
|
+
summary: Tool for managing multiple CDN balances on Dyn's GSLB
|
95
|
+
test_files: []
|