How to Create a Smart-Proxy Plugin » History » Version 26
Lukas Zapletal, 02/19/2019 09:26 AM
Prefer underscore naming scheme
1 | 1 | Anonymous | h1. How to Create a Smart-Proxy Plugin |
---|---|---|---|
2 | |||
3 | 4 | Anonymous | This guide outlines main components of a plugin, but assumes some degree of familiarity with ruby gems, bundler, rack, and Sinatra. You'll find links to useful documentation in each of the sections below. |
4 | 1 | Anonymous | |
5 | 9 | Dominic Cleal | {{toc}} |
6 | |||
7 | 1 | Anonymous | h2. Plugin Organization |
8 | |||
9 | 11 | Dominic Cleal | Smart-Proxy plugins are normal ruby gems, please follow documentation at http://guides.rubygems.org/make-your-own-gem/ for guidance on gem creation and packaging. It is strongly recommended to follow smart_proxy_<your plugin name here> naming convention for your plugin. |
10 | 1 | Anonymous | |
11 | 11 | Dominic Cleal | We have some templates for creating your plugin: |
12 | |||
13 | * "smart_proxy_example plugin":https://github.com/theforeman/smart_proxy_example is a minimal example plugin that can be used as a skeleton |
||
14 | * "smart_proxy_dns_plugin_template":https://github.com/theforeman/smart_proxy_dns_plugin_template is a template for creating new DNS provider plugins |
||
15 | |||
16 | Also, "smart_proxy_pulp plugin":https://github.com/theforeman/smart-proxy-pulp is an example for a fully functional, yet easy to understand Smart-Proxy plugin. |
||
17 | |||
18 | 6 | Dominic Cleal | h2. Making your plugin official |
19 | |||
20 | 1 | Anonymous | Once you're ready to release the first version, please see [[How_to_Create_a_Plugin#Making-your-plugin-official]] for info on making your plugin part of the Foreman project. |
21 | |||
22 | 11 | Dominic Cleal | h2. Plugin definition |
23 | 2 | Anonymous | |
24 | 11 | Dominic Cleal | A plugin definition is used to define plugin's name, version, location of rackup configuration, and other parameters. At a minimum, Plugin Descriptor must define plugin name and version. Note the base class of the descriptor is ::Proxy::Plugin: |
25 | 3 | Anonymous | |
26 | 25 | Ewoud Kohl van Wijngaarden | <pre><code class="ruby"> |
27 | 8 | Anonymous | module Proxy::Example |
28 | class Plugin < ::Proxy::Plugin |
||
29 | 1 | Anonymous | plugin :example, "0.0.1" |
30 | 8 | Anonymous | http_rackup_path File.expand_path("http_config.ru", File.expand_path("../", __FILE__)) |
31 | 1 | Anonymous | https_rackup_path File.expand_path("https_config.ru", File.expand_path("../", __FILE__)) |
32 | default_settings :hello_greeting => 'Hello there!', :important_path => '/must/exist' |
||
33 | load_classes ::Proxy::Example::ClassLoader |
||
34 | load_programmable_settings "::Proxy::Example::ProgrammableSettings" |
||
35 | load_dependency_injection_wirings "::Proxy::Example::DIConfiguration" |
||
36 | 20 | Anonymous | load_validators :my_validator => Proxy::Example::CustomValidators::MyValidator |
37 | 8 | Anonymous | validate_readable :optional_path, :important_path |
38 | 1 | Anonymous | validate :a_setting, :my_validator => true, :if => lambda {|settings| !settings[:a_setting].nil?} |
39 | validate :another_setting, :my_other_validator => true |
||
40 | start_services :service_a, :service_b |
||
41 | end |
||
42 | end |
||
43 | </code></pre> |
||
44 | |||
45 | 19 | Dominic Cleal | Here we defined a plugin called "example", with version "0.0.1", that is going to listen on both http and https ports. |
46 | 1 | Anonymous | |
47 | 19 | Dominic Cleal | h3. Full list of descriptor parameters |
48 | |||
49 | Following is the full list of parameters that can be defined by the Plugin Descriptor, and the version of the Smart Proxy that they were added in. |
||
50 | |||
51 | General smart proxy configuration parameters: |
||
52 | |||
53 | 17 | Anonymous | * plugin :example, "1.2.3": *required*. Sets plugin name to "example" and version to "0.0.1". |
54 | 19 | Dominic Cleal | * http_rackup_path "path/to/http_config.ru": _optional_, _1.6+_. Sets path to http rackup configuration. If omitted, the plugin is not going to listen on the http port. Please see below for information on rackup configuration. |
55 | * https_rackup_path "path/to/https_config.ru": _optional_, _1.6+_. Sets path to https rackup configuration. If omitted, the plugin is not going to listen on the https port. Please see below for information on rackup configuration. |
||
56 | |||
57 | Loading and dependencies: |
||
58 | |||
59 | * requires :another_plugin, '~> 1.2.0': _optional_, _1.6+_. Specifies plugin dependencies, where ":another_plugin" is another plugin name, and '~> 1.2.0' is version specification (pls. see http://guides.rubygems.org/patterns/#pessimistic_version_constraint for details on version specification). |
||
60 | * bundler_group :my_plugin_group: _optional_, _1.6+_. Sets the name of the bundler group for plugin dependencies. If omitted the plugin name is used. |
||
61 | * after_activation { do_something }: _optional_, _1.6+_. Supplied block is going to be executed after the plugin has been loaded and enabled. Note that the block is going to be executed in the context of the Plugin Descriptor class. |
||
62 | 20 | Anonymous | * load_classes: _1.12+_. must be a class, class name, or a block. Specified class must implement "load_classes" instance method that loads module's dependencies. |
63 | * load_dependency_injection_wirings: can be a class, class name, or a block. _1.12+_. The class must implement load_dependency_injection_wirings(di_container, settings_hash) instance method. |
||
64 | 19 | Dominic Cleal | * start_services: _1.12+_. list of dependency injection wiring labels. Services that perform work independently (asynchroniously) of http requests should implement #start method. |
65 | |||
66 | Settings related: |
||
67 | |||
68 | 1 | Anonymous | * default_settings :first => 'my first setting', :another => 'my other setting': _optional_. _1.6+_. Defines default values for plugin parameters. These parameters can be overridden in plugin settings file. Setting any of the parameters in default_settings to nil will trigger a validation error. |
69 | 20 | Anonymous | * load_programmable_settings: can be a class, class name, or a block. _1.12+_. Specified class must implement load_programmable_settings(settings_hash) instance method that returns new or updated settings. |
70 | * load_validators: a hash of validator name (a symbol) to validator class mappings. _1.12+_. |
||
71 | 19 | Dominic Cleal | * validate: validate :setting_one, :setting_two, ..., :setting_n, :validator_name => { :validator_param_one => 'value one', ...,}, :if => lambda {|settings| ... }, alternatively use :validator_name => true if validator has no parameters. If predicate is specified, the validator will be called only if the lambda evaluates to true. Predicate's lambda expects module's settings passed as a parameter. (_1.12+_) |
72 | * validate_readable :optional_path, :important_path: _optional_, _1.10+_. Verifies that settings listed here contain paths to files that exist and are readable. Optional settings (not listed under default_settings) will be skipped if left uninitialized. |
||
73 | 20 | Anonymous | * validate_presence :setting_one, ..., :setting_n: _optional_, _1.10+_. Verifies that settings listed are not equal to nil. Executed automatically for each of the default settings. |
74 | 19 | Dominic Cleal | |
75 | 1 | Anonymous | h3. Provider definition |
76 | |||
77 | Some plugins are *providers* for an existing plugin or module in the Smart Proxy, e.g. a DNS provider. |
||
78 | |||
79 | These are registered almost identically, but use Proxy::Provider instead of Proxy::Plugin. No rackup_paths are used for providers, since they don't add any new REST API, they only add functionality to an existing module. |
||
80 | |||
81 | 25 | Ewoud Kohl van Wijngaarden | <pre><code class="ruby"> |
82 | 1 | Anonymous | module Proxy::Dns::PluginTemplate |
83 | class Plugin < ::Proxy::Provider |
||
84 | plugin :dns_plugin_template, ::Proxy::Dns::PluginTemplate::VERSION |
||
85 | |||
86 | requires :dns, '>= 1.11' |
||
87 | |||
88 | after_activation do |
||
89 | require 'smart_proxy_dns_plugin_template/dns_plugin_template_main' |
||
90 | require 'smart_proxy_dns_plugin_template/dns_plugin_template_dependencies' |
||
91 | end |
||
92 | end |
||
93 | 11 | Dominic Cleal | end |
94 | 25 | Ewoud Kohl van Wijngaarden | </code></pre> |
95 | 1 | Anonymous | |
96 | 11 | Dominic Cleal | Additionally, each provider must specify which class implements interface expected by the main plugin. This is done by declaring an association for module's dependency injection container. |
97 | 13 | Anonymous | |
98 | 25 | Ewoud Kohl van Wijngaarden | <pre><code class="ruby"> |
99 | 11 | Dominic Cleal | require 'dns_common/dependency_injection/dependencies' |
100 | |||
101 | class Proxy::Dns::DependencyInjection::Dependencies |
||
102 | 13 | Anonymous | dependency :dns_provider, Proxy::Dns::PluginTemplate::Record |
103 | 1 | Anonymous | end |
104 | 25 | Ewoud Kohl van Wijngaarden | </code></pre> |
105 | 11 | Dominic Cleal | |
106 | 19 | Dominic Cleal | h2. Plugin Initialization |
107 | |||
108 | The initialization process can be thought of as consisting of two phases: loading and validation of settings and runtime initialization -- selection of classes, their parameters, and how they will be instantiated. |
||
109 | |||
110 | During the first phase of the process, all modules are gathered into groups consisting of the main plugin and one or more providers. If any of the members of the group fail at any time during initialization, the rest of the modules in the group will be failed as well. Initialization starts with all loaded and enabled plugin (main modules) classes being collected, then for each: |
||
111 | * configuration file is loaded |
||
112 | * dependencies are loaded (also see load_classes) |
||
113 | * configuration-related code executed, and configuration updated (also see load_runtime_configuration) |
||
114 | * validators executed (also see load_validators) |
||
115 | * provider names resolved to provider classes |
||
116 | |||
117 | At this point the steps above are repeated for all providers, one module group at a time. During the second phase, for each of the modules: |
||
118 | * dependency injection wirings are resolved (also see load_dependency_injection_wirings) |
||
119 | * services started (also see start_services) |
||
120 | * module's versions are checked against other modules stated requirements (also see) |
||
121 | |||
122 | 1 | Anonymous | h2. How to Load Dependencies |
123 | 17 | Anonymous | |
124 | 19 | Dominic Cleal | _This technique requires Smart Proxy 1.12 or higher._ |
125 | |||
126 | 17 | Anonymous | The class loader must implement load_classes instance method: |
127 | |||
128 | 25 | Ewoud Kohl van Wijngaarden | <pre><code class="ruby"> |
129 | 17 | Anonymous | class ::Proxy::Example::ClassLoader |
130 | def load_classes |
||
131 | require 'example/class_a' |
||
132 | require 'example/class_b' |
||
133 | end |
||
134 | end |
||
135 | 25 | Ewoud Kohl van Wijngaarden | </code></pre> |
136 | 1 | Anonymous | |
137 | alternatively, a block can be used to load dependencies: |
||
138 | |||
139 | 25 | Ewoud Kohl van Wijngaarden | <pre><code class="ruby"> |
140 | 1 | Anonymous | module Proxy::Example |
141 | 18 | Anonymous | class Plugin < ::Proxy::Plugin |
142 | ... |
||
143 | load_classes do |
||
144 | require 'example/class_a' |
||
145 | require 'example/class_b' |
||
146 | end |
||
147 | ... |
||
148 | end |
||
149 | end |
||
150 | 25 | Ewoud Kohl van Wijngaarden | </code></pre> |
151 | 18 | Anonymous | |
152 | h2. How to Programmatically Update Settings |
||
153 | |||
154 | 19 | Dominic Cleal | _This technique requires Smart Proxy 1.12 or higher._ |
155 | |||
156 | 17 | Anonymous | The class must implement load_programmable_settings(settings_hash) instance method that returns new or updated settings: |
157 | 1 | Anonymous | |
158 | 25 | Ewoud Kohl van Wijngaarden | <pre><code class="ruby"> |
159 | 1 | Anonymous | class ::Proxy::Example::RuntimeConfiguration |
160 | 18 | Anonymous | def load_programmable_settings(settings_hash) |
161 | 17 | Anonymous | settings_hash[:a_setting] = "Hello, world" |
162 | settings_hash |
||
163 | end |
||
164 | 18 | Anonymous | end |
165 | 25 | Ewoud Kohl van Wijngaarden | </code></pre> |
166 | 17 | Anonymous | |
167 | h2. How to Define Dependency Injection Wirings |
||
168 | |||
169 | 19 | Dominic Cleal | _This technique requires Smart Proxy 1.12 or higher._ |
170 | |||
171 | 17 | Anonymous | The class must implement load_dependency_injection_wirings instance method that has dependency injection container and settings hash as its parameters: |
172 | |||
173 | 25 | Ewoud Kohl van Wijngaarden | <pre><code class="ruby"> |
174 | 17 | Anonymous | class ::Proxy::Example::DIConfiguration |
175 | def load_dependency_injection_wirings(container_instance, settings) |
||
176 | container_instance.dependency :depedency_a, ::Proxy::Example::ClassA |
||
177 | container_instance.dependency :dependency_b, ::Proxy::Example::ClassB |
||
178 | container_instance.singleton_dependency :service_a, lambda {|container| ::Proxy::Example::ServiceA.new(settings[:example_setting], container_instance.get_dependency(:dependency_a))} |
||
179 | end |
||
180 | end |
||
181 | 25 | Ewoud Kohl van Wijngaarden | </code></pre> |
182 | 17 | Anonymous | |
183 | When Proxy::DependencyInjection::Container#dependency is used to define a dependency, a new instance of a class will be returned, or lambda executed every time the dependency is requested. |
||
184 | 1 | Anonymous | If only a single instance of a class is ever required, use Proxy::DependencyInjection::Container#singleton_dependency: the class will be instantiated first time the dependency is requested, and then reused on all subsequent requests. |
185 | 17 | Anonymous | |
186 | 21 | Anonymous | h2. How to Create Custom Validators |
187 | 17 | Anonymous | |
188 | 19 | Dominic Cleal | _This technique requires Smart Proxy 1.12 or higher._ |
189 | |||
190 | 20 | Anonymous | A validator must use ::Proxy::PluginValidators::Base as its base class and implement validate!(settings) instance method that accepts a hash containing module settings. validate! should raise an exception if the check it's making fails. |
191 | 17 | Anonymous | |
192 | 25 | Ewoud Kohl van Wijngaarden | <pre><code class="ruby"> |
193 | 17 | Anonymous | class Proxy::Example::CustomValidators |
194 | class MyValidator < ::Proxy::PluginValidators::Base |
||
195 | def validate!(settings) |
||
196 | raise ::Proxy::Error::ConfigurationError("Unsupported greeting") if settings[@setting_name] != "Hello, world" |
||
197 | end |
||
198 | end |
||
199 | end |
||
200 | 25 | Ewoud Kohl van Wijngaarden | </code></pre> |
201 | 17 | Anonymous | |
202 | 4 | Anonymous | h2. API |
203 | 1 | Anonymous | |
204 | Modular Sinatra app is used to define plugin API. Note the base class Sinatra::Base and inclusion of ::Proxy::Helpers: |
||
205 | 24 | Ewoud Kohl van Wijngaarden | <pre><code class="ruby"> |
206 | 8 | Anonymous | module Proxy::Example |
207 | 4 | Anonymous | class Api < Sinatra::Base |
208 | helpers ::Proxy::Helpers |
||
209 | |||
210 | 8 | Anonymous | get "/hello" do |
211 | 4 | Anonymous | Proxy::Example::Plugin.settings.hello_greeting |
212 | end |
||
213 | end |
||
214 | </code></pre> |
||
215 | 1 | Anonymous | |
216 | 4 | Anonymous | Here we return a string defined in 'hello_greeting' parameter (see Plugin Descriptor above and settings file below) when a client performs a GET /hello. Please refer to "Sinatra documentation":http://www.sinatrarb.com/intro.html on details about routing, template rendering, available helpers, etc. |
217 | |||
218 | h2. Rackup Configuration |
||
219 | |||
220 | During startup Smart-Proxy assembles web applications listening on http and https ports using rackup files of enabled plugins. Plugin rackup files define mounting points of plugin API: |
||
221 | <pre><code class="ruby"> |
||
222 | require 'example_plugin/example_api' |
||
223 | |||
224 | 8 | Anonymous | map "/example" do |
225 | 1 | Anonymous | run Proxy::Example::Api |
226 | 4 | Anonymous | end |
227 | </code></pre> |
||
228 | |||
229 | 26 | Lukas Zapletal | The example above should be sufficient for the majority of plugins. Please refer to "Sinatra+Rack":http://www.sinatrarb.com/intro.html documentation for additional information. |
230 | 4 | Anonymous | |
231 | h2. Plugin Settings |
||
232 | |||
233 | On startup Smart-Proxy will load and parse plugin configuration files located in its settings.d/ directory. Each plugin config file is named after the plugin and is a yaml-encoded collection of key-value pairs and used to override default values of plugin parameters. |
||
234 | <pre> |
||
235 | --- |
||
236 | :enabled: true |
||
237 | :hello_greeting: "O hai!" |
||
238 | </pre> |
||
239 | 1 | Anonymous | |
240 | 26 | Lukas Zapletal | This settings file enables the plugin (by default all plugins are disabled), and overrides :hello_greeting parameter. Plugin settings can be accessed through .settings method of the Plugin class, for example: ExamplePlugin.settings.hello_greeting. Global Smart-Proxy parameters are accessible through Proxy::SETTINGS, for example Proxy::SETTINGS.foreman_url returns Foreman url configured for this Smart-Proxy. |
241 | |||
242 | Prefer underscore naming scheme (@hello_there@) to camel-case (@helloThere@). |
||
243 | 4 | Anonymous | |
244 | h2. Bundler Configuration |
||
245 | |||
246 | Smart-Proxy relies on bundler to load its dependencies and plugins. We recommend to create a dedicated bundler config file for your plugin, and name it after the plugin. For example: |
||
247 | <pre><code class="ruby"> |
||
248 | gem 'smart_proxy_example' |
||
249 | group :example do |
||
250 | gem 'json' |
||
251 | end |
||
252 | 1 | Anonymous | </code></pre> |
253 | |||
254 | You'll need to create a dedicated bundler group for additional dependencies of your plugin. By default the group shares the name with the plugin, but you can override it using bundler_group parameter in Plugin Descriptor. Please refer to [[How_to_Install_a_Smart-Proxy_Plugin]] for additional details on "from source" plugin installations. |
||
255 | 11 | Dominic Cleal | |
256 | h2. Adding a DNS provider |
||
257 | 15 | Dominic Cleal | |
258 | 22 | Ewoud Kohl van Wijngaarden | _Requires Smart Proxy 1.15 or higher (1.14 has a different interface.)_ |
259 | 16 | Anonymous | |
260 | 22 | Ewoud Kohl van Wijngaarden | When extending the 'dns' smart proxy module, the plugin needs to create a new Proxy::Dns::Record class with @do_create@ and @do_remove@ methods for adding and removing of DNS records. |
261 | 11 | Dominic Cleal | |
262 | The easiest way to do this is using the "Smart Proxy DNS plugin template":https://github.com/theforeman/smart_proxy_dns_plugin_template which can get you up and running with a new DNS provider plugin in minutes. |
||
263 | 16 | Anonymous | |
264 | 11 | Dominic Cleal | DNS Provider classes are instantiated by DNS module's dependency injection container. |
265 | |||
266 | 25 | Ewoud Kohl van Wijngaarden | <pre><code class="ruby"> |
267 | 11 | Dominic Cleal | plugin :dns_plugin_template, ::Proxy::Dns::PluginTemplate::VERSION |
268 | 25 | Ewoud Kohl van Wijngaarden | </code></pre> |
269 | 11 | Dominic Cleal | |
270 | 1 | Anonymous | And then in the main file of the plugin: |
271 | |||
272 | 25 | Ewoud Kohl van Wijngaarden | <pre><code class="ruby"> |
273 | 14 | Anonymous | require 'dns_common/dns_common' |
274 | 11 | Dominic Cleal | |
275 | 1 | Anonymous | module Proxy::Dns::PluginTemplate |
276 | class Record < ::Proxy::Dns::Record |
||
277 | include Proxy::Log |
||
278 | |||
279 | 22 | Ewoud Kohl van Wijngaarden | attr_reader :example_setting, :optional_path, :required_setting, :required_path |
280 | 14 | Anonymous | |
281 | 22 | Ewoud Kohl van Wijngaarden | def initialize(required_setting, example_setting, required_path, optional_path, dns_ttl) |
282 | @required_setting = required_setting # never nil |
||
283 | @example_setting = example_setting # can be nil |
||
284 | @required_path = required_path # file exists and is readable |
||
285 | @optional_path = optional_path # nil, or file exists and is readable |
||
286 | 1 | Anonymous | |
287 | 22 | Ewoud Kohl van Wijngaarden | # Common settings can be defined by the main plugin, it's ok to use them locally. |
288 | # Please note that providers must not rely on settings defined by other providers or plugins they are not related to. |
||
289 | super('localhost', dns_ttl) |
||
290 | 1 | Anonymous | end |
291 | |||
292 | 22 | Ewoud Kohl van Wijngaarden | def do_create(name, value, type) |
293 | # FIXME: There is no trailing dot. Your backend might require it. |
||
294 | if false |
||
295 | name += '.' |
||
296 | value += '.' if ['PTR', 'CNAME'].include?(type) |
||
297 | end |
||
298 | |||
299 | # FIXME: Create a record with the correct name, value and type |
||
300 | raise Proxy::Dns::Error.new("Failed to point #{name} to #{value} with type #{type}") |
||
301 | 14 | Anonymous | end |
302 | |||
303 | 22 | Ewoud Kohl van Wijngaarden | def do_remove(name, type) |
304 | # FIXME: There is no trailing dot. Your backend might require it. |
||
305 | if false |
||
306 | name += '.' |
||
307 | end |
||
308 | |||
309 | # FIXME: Remove a record with the correct name and type |
||
310 | raise Proxy::Dns::Error.new("Failed to remove #{name} of type #{type}") |
||
311 | 11 | Dominic Cleal | end |
312 | end |
||
313 | 1 | Anonymous | end |
314 | 25 | Ewoud Kohl van Wijngaarden | </code></pre> |
315 | 1 | Anonymous | |
316 | 22 | Ewoud Kohl van Wijngaarden | DNS provider support was first added in version 1.10, but the interface was updated between 1.10 and 1.11. Later in 1.15 it was further modified. Please see the history of this page for 1.14-compatible recommendations and the 1.14-stable branch of the example DNS plugin instead of master. |
317 | 16 | Anonymous | |
318 | h2. Adding a DHCP provider |
||
319 | |||
320 | 19 | Dominic Cleal | _Requires Smart Proxy 1.11 or higher._ |
321 | 16 | Anonymous | |
322 | When creating a new 'dhcp' provider smart proxy module, the plugin needs to create a new Proxy::DHCP::Server class that implements @load_subnets@, @load_subnet_data@, @find_subnet@, @subnets@, @all_hosts@, @unused_ip@, @find_record@, @add_record@, and @del_record@ methods. |
||
323 | |||
324 | Provider classes are instantiated by DHCP module's dependency injection container. |
||
325 | |||
326 | 25 | Ewoud Kohl van Wijngaarden | <pre><code class="ruby"> |
327 | 16 | Anonymous | plugin :example_dhcp_provider, ::ExampleDhcpPlugin::Provider::VERSION |
328 | 25 | Ewoud Kohl van Wijngaarden | </code></pre> |
329 | 16 | Anonymous | |
330 | And then in the main file of the plugin: |
||
331 | |||
332 | 25 | Ewoud Kohl van Wijngaarden | <pre><code class="ruby"> |
333 | 16 | Anonymous | require 'dhcp_common/server' |
334 | |||
335 | module ::ExampleDhcpPlugin |
||
336 | class Provider < ::Proxy::DHCP::Server |
||
337 | include Proxy::Log |
||
338 | include Proxy::Util |
||
339 | |||
340 | def initialize |
||
341 | super('localhost') |
||
342 | end |
||
343 | |||
344 | def load_subnets |
||
345 | # loads subnet data into memory |
||
346 | end |
||
347 | |||
348 | def find_subnet(network_address) |
||
349 | # returns Proxy::DHCP::Subnet that has network_address or nil if none was found |
||
350 | end |
||
351 | |||
352 | def load_subnet_data(a_subnet) |
||
353 | # loads lease- and host-records for a Proxy::DHCP::Subnet |
||
354 | end |
||
355 | |||
356 | def subnets |
||
357 | # returns all available subnets (instances of Proxy::DHCP::Subnet) |
||
358 | end |
||
359 | |||
360 | def all_hosts(network_address) |
||
361 | # returns all reservations in a subnet with network_address |
||
362 | end |
||
363 | |||
364 | def unused_ip(network_address, mac_address, from_ip_address, to_ip_address) |
||
365 | # returns first available ip address in a subnet with network_address, for a host with mac_address, in the range of ip addresses: from_ip_address, to_ip_address |
||
366 | end |
||
367 | |||
368 | def find_record(network_address, ip_or_mac_address) |
||
369 | # returns a Proxy::DHCP::Record from a subnet with network_address that has ip- or mac-address specified in ip_or_mac_address, or nil of none was found |
||
370 | end |
||
371 | |||
372 | def add_record(params) |
||
373 | # creates a record |
||
374 | end |
||
375 | |||
376 | def del_record(network_address,a_record) |
||
377 | # removes a Proxy::DHCP::Record from a subnet with network_address |
||
378 | end |
||
379 | end |
||
380 | end |
||
381 | 25 | Ewoud Kohl van Wijngaarden | </code></pre> |
382 | 16 | Anonymous | |
383 | DHCP provider support was first added in version 1.11. |
||
384 | 5 | Anonymous | |
385 | h2. Testing |
||
386 | 1 | Anonymous | |
387 | 23 | Ian Ballou | Make sure that your Gemfile includes the "smart-proxy" gem as a development dependency: |
388 | 7 | Anonymous | |
389 | <pre><code class="ruby"> |
||
390 | group :development do |
||
391 | gem 'smart_proxy', :git => "https://github.com/theforeman/smart-proxy.git" |
||
392 | end |
||
393 | </code></pre> |
||
394 | 1 | Anonymous | |
395 | 23 | Ian Ballou | Ensure that your plugin has a Rakefile, for example: |
396 | |||
397 | <pre><code class="ruby"> |
||
398 | require 'rake' |
||
399 | require 'rake/testtask' |
||
400 | |||
401 | desc 'Default: run unit tests.' |
||
402 | task :default => :test |
||
403 | |||
404 | desc 'Test Pulp Plugin' |
||
405 | Rake::TestTask.new(:test) do |t| |
||
406 | t.libs << '.' |
||
407 | t.libs << 'lib' |
||
408 | t.libs << 'test' |
||
409 | t.test_files = FileList['test/**/*_test.rb'] |
||
410 | t.verbose = true |
||
411 | end |
||
412 | </code></pre> |
||
413 | |||
414 | 1 | Anonymous | Load 'smart_proxy_for_testing' in your tests: |
415 | |||
416 | 23 | Ian Ballou | <pre><code class="ruby"> |
417 | 5 | Anonymous | $: << File.join(File.dirname(__FILE__), '..', 'lib') |
418 | |||
419 | require 'smart_proxy_for_testing' |
||
420 | require 'test/unit' |
||
421 | require 'webmock/test_unit' |
||
422 | require 'mocha/test_unit' |
||
423 | require "rack/test" |
||
424 | |||
425 | require 'smart_proxy_pulp_plugin/pulp_plugin' |
||
426 | require 'smart_proxy_pulp_plugin/pulp_api' |
||
427 | |||
428 | class PulpApiTest < Test::Unit::TestCase |
||
429 | include Rack::Test::Methods |
||
430 | |||
431 | def app |
||
432 | PulpProxy::Api.new |
||
433 | end |
||
434 | |||
435 | def test_returns_pulp_status_on_200 |
||
436 | stub_request(:get, "#{::PulpProxy::Plugin.settings.pulp_url.to_s}/api/v2/status/").to_return(:body => "{\"api_version\":\"2\"}") |
||
437 | get '/status' |
||
438 | |||
439 | assert last_response.ok?, "Last response was not ok: #{last_response.body}" |
||
440 | assert_equal("{\"api_version\":\"2\"}", last_response.body) |
||
441 | end |
||
442 | end |
||
443 | </code></pre> |
||
444 | |||
445 | 10 | Anonymous | To execute all tests <code><pre>bundle exec rake test</code></pre>. To save time during development it is possible to execute tests in a single file: <code><pre>bundle exec rake test TEST=path/to/test/file</pre></code> |
446 | |||
447 | 1 | Anonymous | Please refer to "Sinatra documention":http://www.sinatrarb.com/testing.html for detailed information on testing of Sinatra applications. |
448 | 9 | Dominic Cleal | |
449 | 25 | Ewoud Kohl van Wijngaarden | Once you have tests, see "Jenkins":https://projects.theforeman.org/projects/foreman/wiki/Jenkins#Smart-proxy-plugin-testing for info on setting up tests under Jenkins. |