wakame-vdc-agents 10.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. data/LICENSE +202 -0
  2. data/NOTICE +1 -0
  3. data/Rakefile +142 -0
  4. data/bin/hva +972 -0
  5. data/bin/nsa +147 -0
  6. data/bin/sta +182 -0
  7. data/config/hva.conf.example +10 -0
  8. data/config/initializers/isono.rb +43 -0
  9. data/config/initializers/passenger.rb +6 -0
  10. data/config/initializers/sequel.rb +21 -0
  11. data/config/nsa.conf.example +9 -0
  12. data/config/path_resolver.rb +12 -0
  13. data/lib/dcmgr.rb +115 -0
  14. data/lib/dcmgr/endpoints/core_api.rb +1004 -0
  15. data/lib/dcmgr/endpoints/core_api_mock.rb +816 -0
  16. data/lib/dcmgr/endpoints/errors.rb +55 -0
  17. data/lib/dcmgr/endpoints/metadata.rb +129 -0
  18. data/lib/dcmgr/logger.rb +44 -0
  19. data/lib/dcmgr/models/account.rb +104 -0
  20. data/lib/dcmgr/models/account_resource.rb +16 -0
  21. data/lib/dcmgr/models/base.rb +69 -0
  22. data/lib/dcmgr/models/base_new.rb +371 -0
  23. data/lib/dcmgr/models/frontend_system.rb +38 -0
  24. data/lib/dcmgr/models/host_pool.rb +102 -0
  25. data/lib/dcmgr/models/image.rb +46 -0
  26. data/lib/dcmgr/models/instance.rb +255 -0
  27. data/lib/dcmgr/models/instance_netfilter_group.rb +16 -0
  28. data/lib/dcmgr/models/instance_nic.rb +68 -0
  29. data/lib/dcmgr/models/instance_spec.rb +21 -0
  30. data/lib/dcmgr/models/ip_lease.rb +42 -0
  31. data/lib/dcmgr/models/netfilter_group.rb +88 -0
  32. data/lib/dcmgr/models/netfilter_rule.rb +21 -0
  33. data/lib/dcmgr/models/network.rb +32 -0
  34. data/lib/dcmgr/models/physical_host.rb +67 -0
  35. data/lib/dcmgr/models/request_log.rb +25 -0
  36. data/lib/dcmgr/models/ssh_key_pair.rb +55 -0
  37. data/lib/dcmgr/models/storage_pool.rb +134 -0
  38. data/lib/dcmgr/models/tag.rb +126 -0
  39. data/lib/dcmgr/models/tag_mapping.rb +28 -0
  40. data/lib/dcmgr/models/volume.rb +130 -0
  41. data/lib/dcmgr/models/volume_snapshot.rb +47 -0
  42. data/lib/dcmgr/node_modules/hva_collector.rb +134 -0
  43. data/lib/dcmgr/node_modules/sta_collector.rb +72 -0
  44. data/lib/dcmgr/scheduler.rb +12 -0
  45. data/lib/dcmgr/scheduler/find_last.rb +16 -0
  46. data/lib/dcmgr/scheduler/find_random.rb +16 -0
  47. data/lib/dcmgr/stm/instance.rb +25 -0
  48. data/lib/dcmgr/stm/snapshot_context.rb +33 -0
  49. data/lib/dcmgr/stm/volume_context.rb +65 -0
  50. data/lib/dcmgr/web/base.rb +21 -0
  51. data/lib/sinatra/accept_media_types.rb +128 -0
  52. data/lib/sinatra/lazy_auth.rb +56 -0
  53. data/lib/sinatra/rabbit.rb +278 -0
  54. data/lib/sinatra/respond_to.rb +272 -0
  55. data/lib/sinatra/sequel_transaction.rb +27 -0
  56. data/lib/sinatra/static_assets.rb +83 -0
  57. data/lib/sinatra/url_for.rb +44 -0
  58. metadata +270 -0
@@ -0,0 +1,278 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/url_for'
3
+ require 'sinatra/respond_to'
4
+
5
+ module Sinatra
6
+ module Rabbit
7
+
8
+ class DuplicateParamException < Exception; end
9
+ class DuplicateOperationException < Exception; end
10
+ class DuplicateCollectionException < Exception; end
11
+
12
+ class Operation
13
+ attr_reader :name, :method
14
+
15
+ STANDARD = {
16
+ :index => { :method => :get, :member => false },
17
+ :show => { :method => :get, :member => true },
18
+ :create => { :method => :post, :member => false },
19
+ :update => { :method => :put, :member => true },
20
+ :destroy => { :method => :delete, :member => true }
21
+ }
22
+
23
+ def initialize(coll, name, opts, &block)
24
+ @name = name.to_sym
25
+ opts = STANDARD[@name].merge(opts) if standard?
26
+ @collection = coll
27
+ raise "No method for operation #{name}" unless opts[:method]
28
+ @method = opts[:method].to_sym
29
+ @member = opts[:member]
30
+ @description = ""
31
+ instance_eval(&block) if block_given?
32
+ end
33
+
34
+ def standard?
35
+ STANDARD.keys.include?(name)
36
+ end
37
+
38
+ def description(text="")
39
+ return @description if text.blank?
40
+ @description = text
41
+ end
42
+
43
+ def generate_documentation(app)
44
+ raise ArgumentError unless app < Sinatra::Base
45
+ coll, oper = @collection, self
46
+ app.get("/api/docs/#{@collection.name}/#{@name}") do
47
+ @collection, @operation = coll, oper
48
+ respond_to do |format|
49
+ format.html { haml :'docs/operation' }
50
+ format.xml { haml :'docs/operation' }
51
+ end
52
+ end
53
+ end
54
+
55
+ def control(&block)
56
+ op = self
57
+ @control = Proc.new do
58
+ #op.validate(params)
59
+ instance_eval(&block)
60
+ end
61
+ end
62
+
63
+ def prefix
64
+ # FIXME: Make the /api prefix configurable
65
+ "/api"
66
+ end
67
+
68
+ def path(args = {})
69
+ l_prefix = args[:prefix] ? args[:prefix] : prefix
70
+ if @member
71
+ if standard?
72
+ "#{l_prefix}/#{@collection.name}/:id"
73
+ else
74
+ "#{l_prefix}/#{@collection.name}/:id/#{name}"
75
+ end
76
+ else
77
+ "#{l_prefix}/#{@collection.name}"
78
+ end
79
+ end
80
+
81
+ def generate(app)
82
+ raise ArgumentError unless app < Sinatra::Base
83
+
84
+ app.send(@method, path, {}, &@control)
85
+ # Set up some Rails-like URL helpers
86
+ if name == :index
87
+ gen_route "#{@collection.name}_url", app
88
+ elsif name == :show
89
+ gen_route "#{@collection.name.to_s.singularize}_url", app
90
+ else
91
+ gen_route "#{name}_#{@collection.name.to_s.singularize}_url", app
92
+ end
93
+ end
94
+
95
+ def authz(&blk)
96
+ end
97
+
98
+ private
99
+ def gen_route(name, app)
100
+ route_url = path
101
+ if @member
102
+ app.send(:define_method, name) do |id, *args|
103
+ url = query_url(route_url, args[0])
104
+ url_for url.gsub(/:id/, id.to_s), :full
105
+ end
106
+ else
107
+ app.send(:define_method, name) do |*args|
108
+ url = query_url(route_url, args[0])
109
+ url_for url, :full
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ class Collection
116
+ attr_reader :name, :operations
117
+
118
+ def initialize(name, &block)
119
+ @name = name
120
+ @description = ""
121
+ @operations = {}
122
+ instance_eval(&block) if block_given?
123
+ end
124
+
125
+ # Set/Return description for collection
126
+ # If first parameter is not present, full description will be
127
+ # returned.
128
+ def description(text='')
129
+ return @description if text.blank?
130
+ @description = text
131
+ end
132
+
133
+ def generate_documentation(app)
134
+ raise ArgumentError unless app < Sinatra::Base
135
+ coll, oper, features = self, @operations, driver.features(name)
136
+ app.get("/api/docs/#{@name}") do
137
+ @collection, @operations, @features = coll, oper, features
138
+ respond_to do |format|
139
+ format.html { haml :'docs/collection' }
140
+ format.xml { haml :'docs/collection' }
141
+ end
142
+ end
143
+ end
144
+
145
+ # Add a new operation for this collection. For the standard REST
146
+ # operations :index, :show, :update, and :destroy, we already know
147
+ # what method to use and whether this is an operation on the URL for
148
+ # individual elements or for the whole collection.
149
+ #
150
+ # For non-standard operations, options must be passed:
151
+ # :method : one of the HTTP methods
152
+ # :member : whether this is an operation on the collection or an
153
+ # individual element (FIXME: custom operations on the
154
+ # collection will use a nonsensical URL) The URL for the
155
+ # operation is the element URL with the name of the operation
156
+ # appended
157
+ #
158
+ # This also defines a helper method like show_instance_url that returns
159
+ # the URL to this operation (in request context)
160
+ def operation(name, opts = {}, &block)
161
+ raise DuplicateOperationException if @operations[name]
162
+ @operations[name] = Operation.new(self, name, opts, &block)
163
+ end
164
+
165
+ def generate(app = ::Sinatra::Application)
166
+ raise ArgumentError unless app < Sinatra::Base
167
+ operations.values.each { |op| op.generate(app) }
168
+ collname = name # Work around Ruby's weird scoping/capture
169
+ app.send(:define_method, "#{name.to_s.singularize}_url") do |id|
170
+ url_for "/api/#{collname}/#{id}", :full
171
+ end
172
+
173
+ if index_op = operations[:index]
174
+ app.send(:define_method, "#{name}_url") do
175
+ url_for index_op.path.gsub(/\/\?$/,''), :full
176
+ end
177
+ end
178
+ end
179
+
180
+ def add_feature_params(features)
181
+ features.each do |f|
182
+ f.operations.each do |fop|
183
+ if cop = operations[fop.name]
184
+ fop.params.each_key do |k|
185
+ if cop.params.has_key?(k)
186
+ raise DuplicateParamException, "Parameter '#{k}' for operation #{fop.name} defined by collection #{@name} and by feature #{f.name}"
187
+ else
188
+ cop.params[k] = fop.params[k]
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+
197
+ def collections
198
+ @collections ||= {}
199
+ end
200
+
201
+ # Create a new collection. NAME should be the pluralized name of the
202
+ # collection.
203
+ #
204
+ # Adds a helper method #{name}_url which returns the URL to the :index
205
+ # operation on this collection.
206
+ def collection(name, &block)
207
+ raise DuplicateCollectionException if collections[name]
208
+ #return unless driver.has_collection?(name.to_sym)
209
+ collections[name] = Collection.new(name, &block)
210
+ #collections[name].add_feature_params(driver.features(name))
211
+ collections[name].generate(self)
212
+ end
213
+
214
+ def self.registered(app)
215
+ app.register(Sinatra::RespondTo)
216
+ app.helpers RabbitHelper
217
+ app.class_eval {
218
+ # Generate a root route for API docs
219
+ get '/api/docs\/?' do
220
+ respond_to do |format|
221
+ format.html { haml :'docs/index' }
222
+ format.xml { haml :'docs/index' }
223
+ end
224
+ end
225
+ }
226
+ end
227
+ end
228
+
229
+ module RabbitHelper
230
+ def query_url(url, params)
231
+ return url if params.nil? || params.empty?
232
+ url + "?#{URI.escape(params.collect{|k,v| "#{k}=#{v}"}.join('&'))}"
233
+ end
234
+
235
+ def entry_points
236
+ collections.values.inject([]) do |m, coll|
237
+ url = url_for coll.operations[:index].path, :full
238
+ m << [ coll.name, url ]
239
+ end
240
+ end
241
+ end
242
+
243
+ end
244
+
245
+ class String
246
+ # Rails defines this for a number of other classes, including Object
247
+ # see activesupport/lib/active_support/core_ext/object/blank.rb
248
+ def blank?
249
+ self !~ /\S/
250
+ end
251
+
252
+ # Title case.
253
+ #
254
+ # "this is a string".titlecase
255
+ # => "This Is A String"
256
+ #
257
+ # CREDIT: Eliazar Parra
258
+ # Copied from facets
259
+ def titlecase
260
+ gsub(/\b\w/){ $`[-1,1] == "'" ? $& : $&.upcase }
261
+ end
262
+
263
+ def pluralize
264
+ self + "s"
265
+ end
266
+
267
+ def singularize
268
+ self.gsub(/s$/, '')
269
+ end
270
+
271
+ def underscore
272
+ gsub(/::/, '/').
273
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
274
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
275
+ tr("-", "_").
276
+ downcase
277
+ end
278
+ end
@@ -0,0 +1,272 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/accept_media_types'
3
+
4
+ # Accept header parsing was looked at but deemed
5
+ # too much of an irregularity to deal with. Problems with the header
6
+ # differences from IE, Firefox, Safari, and every other UA causes
7
+ # problems with the expected output. The general expected behavior
8
+ # would be serve html when no extension provided, but most UAs say
9
+ # they will accept application/xml with out a quality indicator, meaning
10
+ # you'd get the xml block served insead. Just plain retarded, use the
11
+ # extension and you'll never be suprised.
12
+
13
+ module Sinatra
14
+ module RespondTo
15
+ class UnhandledFormat < Sinatra::NotFound; end
16
+ class MissingTemplate < Sinatra::NotFound
17
+ def code; 500 end
18
+ end
19
+
20
+ TEXT_MIME_TYPES = [:txt, :html, :js, :json, :xml, :rss, :atom, :css, :asm, :c, :cc, :conf,
21
+ :csv, :cxx, :diff, :dtd, :f, :f77, :f90, :for, :gemspec, :h, :hh, :htm,
22
+ :log, :mathml, :mml, :p, :pas, :pl, :pm, :py, :rake, :rb, :rdf, :rtf, :ru,
23
+ :s, :sgm, :sgml, :sh, :svg, :svgz, :text, :wsdl, :xhtml, :xsl, :xslt, :yaml,
24
+ :yml, :ics, :png]
25
+
26
+ def self.registered(app)
27
+ app.helpers RespondTo::Helpers
28
+
29
+ app.set :default_charset, 'utf-8'
30
+ app.set :default_content, :html
31
+ app.set :assume_xhr_is_js, true
32
+
33
+ # We remove the trailing extension so routes
34
+ # don't have to be of the style
35
+ #
36
+ # get '/resouce.:format'
37
+ #
38
+ # They can instead be of the style
39
+ #
40
+ # get '/resource'
41
+ #
42
+ # and the format will automatically be available in <tt>format</tt>
43
+ app.before do
44
+ # Let through sinatra image urls in development
45
+ next if self.class.development? && request.path_info =~ %r{/__sinatra__/.*?.png}
46
+
47
+ unless options.static? && options.public? && (request.get? || request.head?) && static_file?(request.path_info)
48
+ rpi = request.path_info.sub(%r{\.([^\./]+)$}, '')
49
+
50
+ if (not $1) or ($1 and TEXT_MIME_TYPES.include?($1.to_sym))
51
+ request.path_info, ext = rpi, nil
52
+ if ! $1.nil?
53
+ ext = $1
54
+ elsif env['HTTP_ACCEPT'].nil? || env['HTTP_ACCEPT'].empty?
55
+ ext = options.default_content
56
+ end
57
+ if ext
58
+ @mime_types = [ Helpers::mime_type(ext) ]
59
+ format ext
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ app.configure :development do |dev|
66
+ dev.error UnhandledFormat do
67
+ content_type :html, :charset => 'utf-8'
68
+
69
+ (<<-HTML).gsub(/^ {10}/, '')
70
+ <!DOCTYPE html>
71
+ <html>
72
+ <head>
73
+ <style type="text/css">
74
+ body { text-align:center;font-family:helvetica,arial;font-size:22px;
75
+ color:#888;margin:20px}
76
+ #c {margin:0 auto;width:500px;text-align:left}
77
+ </style>
78
+ </head>
79
+ <body>
80
+ <h2>Sinatra doesn't know this ditty.</h2>
81
+ <img src='/__sinatra__/404.png'>
82
+ <div id="c">
83
+ Try this:
84
+ <pre>#{request.request_method.downcase} '#{request.path_info}' do\n respond_to do |wants|\n wants.#{format} { "Hello World" }\n end\nend</pre>
85
+ </div>
86
+ </body>
87
+ </html>
88
+ HTML
89
+ end
90
+
91
+ dev.error MissingTemplate do
92
+ content_type :html, :charset => 'utf-8'
93
+ response.status = request.env['sinatra.error'].code
94
+
95
+ engine = request.env['sinatra.error'].message.split('.').last
96
+ engine = 'haml' unless ['haml', 'builder', 'erb'].include? engine
97
+
98
+ path = File.basename(request.path_info)
99
+ path = "root" if path.nil? || path.empty?
100
+
101
+ format = engine == 'builder' ? 'xml' : 'html'
102
+
103
+ layout = case engine
104
+ when 'haml' then "!!!\n%html\n %body= yield"
105
+ when 'erb' then "<html>\n <body>\n <%= yield %>\n </body>\n</html>"
106
+ when 'builder' then ::Sinatra::VERSION =~ /^1.0/ ? "xml << yield" : "builder do |xml|\n xml << yield\nend"
107
+ end
108
+
109
+ layout = "<small>app.#{format}.#{engine}</small>\n<pre>#{escape_html(layout)}</pre>"
110
+
111
+ (<<-HTML).gsub(/^ {10}/, '')
112
+ <!DOCTYPE html>
113
+ <html>
114
+ <head>
115
+ <style type="text/css">
116
+ body { text-align:center;font-family:helvetica,arial;font-size:22px;
117
+ color:#888;margin:20px}
118
+ #c {margin:0 auto;width:500px;text-align:left;}
119
+ small {float:right;clear:both;}
120
+ pre {clear:both;}
121
+ </style>
122
+ </head>
123
+ <body>
124
+ <h2>Sinatra can't find #{request.env['sinatra.error'].message}</h2>
125
+ <img src='/__sinatra__/500.png'>
126
+ <div id="c">
127
+ Try this:<br />
128
+ #{layout}
129
+ <small>#{path}.#{format}.#{engine}</small>
130
+ <pre>Hello World!</pre>
131
+ <small>application.rb</small>
132
+ <pre>#{request.request_method.downcase} '#{request.path_info}' do\n respond_to do |wants|\n wants.#{engine == 'builder' ? 'xml' : 'html'} { #{engine} :#{path}#{",\n#{' '*32}layout => :app" if layout} }\n end\nend</pre>
133
+ </div>
134
+ </body>
135
+ </html>
136
+ HTML
137
+ end
138
+
139
+ end
140
+
141
+ app.class_eval do
142
+ private
143
+ def accept_list
144
+ @mime_types || Rack::AcceptMediaTypes.new(env['HTTP_ACCEPT'] || '')
145
+ end
146
+
147
+ # Changes in 1.0 Sinatra reuse render for layout so we store
148
+ # the original value to tell us if this is an automatic attempt
149
+ # to do a layout call. If it is, it might fail with Errno::ENOENT
150
+ # and we want to pass that back to sinatra since it isn't a MissingTemplate
151
+ # error
152
+ def render_with_format(*args, &block)
153
+ assumed_layout = args[1] == :layout
154
+ args[1] = "#{args[1]}.#{format}".to_sym if args[1].is_a?(::Symbol)
155
+ render_without_format *args, &block
156
+ rescue Errno::ENOENT => e
157
+ raise MissingTemplate, "#{args[1]}.#{args[0]}" unless assumed_layout
158
+ raise e
159
+ end
160
+ alias_method :render_without_format, :render
161
+ alias_method :render, :render_with_format
162
+
163
+ if ::Sinatra::VERSION =~ /^0\.9/
164
+ def lookup_layout_with_format(*args)
165
+ args[1] = "#{args[1]}.#{format}".to_sym if args[1].is_a?(::Symbol)
166
+ lookup_layout_without_format *args
167
+ end
168
+ alias_method :lookup_layout_without_format, :lookup_layout
169
+ alias_method :lookup_layout, :lookup_layout_with_format
170
+ end
171
+ end
172
+ end
173
+
174
+ module Helpers
175
+ # Patch the content_type function to remember the set type
176
+ # This helps cut down on time in the format helper so it
177
+ # doesn't have to do a reverse lookup on the header
178
+ def self.included(klass)
179
+ klass.class_eval do
180
+ def content_type_with_save(*args)
181
+ content_type_without_save *args
182
+ @_format = args.first.to_sym
183
+ response['Content-Type']
184
+ end
185
+ alias_method :content_type_without_save, :content_type
186
+ alias_method :content_type, :content_type_with_save
187
+ end if ::Sinatra::VERSION =~ /^1.0/
188
+ end
189
+
190
+ def self.mime_type(sym)
191
+ ::Sinatra::Base.respond_to?(:mime_type) && ::Sinatra::Base.mime_type(sym) || ::Sinatra::Base.media_type(sym)
192
+ end
193
+
194
+ def format(val=nil)
195
+ unless val.nil?
196
+ mime_type = ::Sinatra::RespondTo::Helpers.mime_type(val)
197
+ fail "Unknown media type #{val}\nTry registering the extension with a mime type" if mime_type.nil?
198
+
199
+ @_format = val.to_sym
200
+ response['Content-Type'].sub!(/^[^;]+/, mime_type)
201
+ charset options.default_charset if Sinatra::RespondTo::TEXT_MIME_TYPES.include?(format) and format!=:png
202
+ end
203
+
204
+ @_format
205
+ end
206
+
207
+ # This is mostly just a helper so request.path_info isn't changed when
208
+ # serving files from the public directory
209
+ def static_file?(path)
210
+ public_dir = File.expand_path(options.public)
211
+ path = File.expand_path(File.join(public_dir, unescape(path)))
212
+
213
+ path[0, public_dir.length] == public_dir && File.file?(path)
214
+ end
215
+
216
+ def charset(val=nil)
217
+ fail "Content-Type must be set in order to specify a charset" if response['Content-Type'].nil?
218
+
219
+ if response['Content-Type'] =~ /charset=[^;]+/
220
+ response['Content-Type'].sub!(/charset=[^;]+/, (val == '' && '') || "charset=#{val}")
221
+ else
222
+ response['Content-Type'] += ";charset=#{val}"
223
+ end unless val.nil?
224
+
225
+ response['Content-Type'][/charset=([^;]+)/, 1]
226
+ end
227
+
228
+ def respond_to(&block)
229
+ wants = Format.new
230
+ yield wants
231
+ fmt, type, handler = match_accept_type(accept_list, wants)
232
+ raise UnhandledFormat if fmt.nil?
233
+ format fmt
234
+ handler.nil? ? nil : handler.call
235
+ end
236
+
237
+ def match_accept_type(mime_types, format)
238
+ selected = []
239
+ accepted_types = mime_types.map {|type| Regexp.escape(type).gsub(/\\\*/,'.*') }
240
+ # Fix for Chrome based browsers which returns XML when 'xhtml' is requested.
241
+ if env['HTTP_USER_AGENT'] =~ /Chrome/ and accepted_types.size>1
242
+ accepted_types[0], accepted_types[1] = accepted_types[1], accepted_types[0]
243
+ if accepted_types[0].eql?('application/xhtml\\+xml')
244
+ accepted_types[0] = 'text/html'
245
+ end
246
+ end
247
+ accepted_types.each do |at|
248
+ format.each do |fmt, ht, handler|
249
+ (selected = [fmt, ht, handler]) and break if ht.match(at)
250
+ end
251
+ break unless selected.empty?
252
+ end
253
+ selected
254
+ end
255
+
256
+ # NOTE Array instead of hash because order matters (wildcard type
257
+ # matches first handler)
258
+ class Format < Array #:nodoc:
259
+ def method_missing(format, *args, &handler)
260
+ mt = Sinatra::RespondTo::Helpers.mime_type(format)
261
+ if mt.nil?
262
+ Sinatra::Base.send(:fail, "Unknown media type for respond_to: #{format}\nTry registering the extension with a mime type")
263
+ end
264
+ self << [format.to_s, mt, handler]
265
+ end
266
+ end
267
+ end
268
+ end
269
+ end
270
+
271
+ Rack::Mime::MIME_TYPES.merge!({ ".gv" => "text/plain" })
272
+ Sinatra::Application.register Sinatra::RespondTo