== Developers Guide to OMF == === Prerequisites === ==== Set up your environment ==== Please please make sure you followed the [wiki:OMF/OMF60/2Operator/0aInstallation INSTALLATION GUIDE] and get things installed and configured properly. === Communication layer test and debug tools === To quickly send a message or monitor incoming messages on a topic, we provide a few utilities that come with the omf_common gem. Monitor a topic: {{{ $ omf_monitor_topic -h Usage: omf_monitor_topic [options] topic1 topic2 ... Monitor a set of resources (topics) and print all observed messages. If the 'follow-children' flag is set, automatically add all resources created by the monitored resources to the monitor set. Please note that there will be a delay until the new monitors are in place which can result in missed messages. -c, --comms-url URL URL of communication server (e.g. xmpp://my.server.com) [] -f, --[no-]follow-children Follow all newly created resources [true] -d, --debug Set log level to DEBUG -h, --help Show this message }}} Send a CREATE message: {{{ $ omf_send_create -h Usage: omf_send_create [options] property1:value1 property2:value2 ... Send a create message to a specific resource (topic) and print out any replies. Any additional command line arguments are interpreted as property:value and are sent with the create message. -r, --resource-url URL URL of resource (e.g. xmpp://my.server.com/topic1) -t, --type TYPE Type of resource to create (e.g. node) -y, --yaml YAML_FILE Read type and property from YAML file -d, --debug Set log level to DEBUG -h, --help Show this message }}} Send a REQUEST message: {{{ $ omf_send_request -h Usage: omf_send_request [options] prop1 prop2 ... Send a request to a specific resource (topic) and print out any replies. Any additional command line arguments are interpreted as limiting the request to those, otherwise all properties are requested. -r, --resource-url URL URL of resource (e.g. xmpp://my.server.com/topic1) -d, --debug Set log level to DEBUG -h, --help Show this message }}} === Background === ==== FRCP protocol ==== If you are more towards implementing FRCP protocol without using Ruby nor our library, you could skip this guide and go to check out [wiki:/OMF/OMF6/1Developer/ArchitecturalFoundation2ProtocolInteractions FRCP protocol documents]. ==== Resource controller system ==== One of the biggest changes we are trying to make in version 6 resource controller system is to focus on the core features, and instead of trying to implement all the functionalities and hardware support, we want to provide an abstract entity acts as the proxy, processing the resource related messages based on the new FRCP protocol, and decides what type of the actions to perform according to the operation defined by the message and constrained by the proxy's capabilities which could be easily defined and extended by the resource providers. In our design, users interact with the resource proxies purely via pubsub messages, they publish certain operation (create, request, configure, and release) messages to the pubsub topics, and subscribe to the PubSub topics for inform messages published by the resource proxies based on the outcome of the these requested operations. The resource proxy instances are actually doing the same, but the opposite, they are subscribing to the PubSub topics, react when new operation messages appeared by calling the internal methods corresponding to the content of the operation messages. Interaction with proxy We created a little tutorial will give a brief example demonstrating the work flow and how to implement these resource proxy definitions. === Tutorial === Suppose we are doing some kind of engine testing in a racing team garage, and using OMF to build a garage controller to handle interaction messages issued by the staff. This tutorial will utilise DSL methods provided by our RC library, and the full API documentation can be accessed here: [/docs/60/OmfRc/ResourceProxyDSL/ClassMethods.html Resource proxy DSL API doucumentation ] This will be a good reference you feel puzzled about certain keywords, methods in the tutorial code we provide here. The Ruby source code provided in this tutorial can be copied and pasted to your favourite editor, and execute as standard Ruby scripts. ==== Step 1: Garage and engine proxy definition ==== We will build a garage resource controller acts as the proxy to the garage and engines, while the staff use scripts to interact and guide garage controller to perform certain operations. A resource proxy definition module (Mixin) represents the functionalities the resource could provide, including internal property state, what properties can be configured or requested, what operations to perform based on stage of the work flow. The features defined in the proxy definition module will be available to the engine proxy instance by the time it is created by the resource factory. We start by defining a garage proxy and a engine proxy, where garage can create engines (parent child relationship). ===== Garage controller skeletion ===== {{{ # Need omf_rc gem to be required # require 'omf_rc' # By using default namespace OmfRc::ResourceProxy, the module defined could be loaded automatically. # module OmfRc::ResourceProxy::Garage # Include DSL module, which provides all DSL helper methods # include OmfRc::ResourceProxyDSL # DSL method register_proxy will register this module definition, # where :garage become the :type of the proxy. # register_proxy :garage end module OmfRc::ResourceProxy::Engine include OmfRc::ResourceProxyDSL # You can specify what kind of proxy can create it, this case, :garage # register_proxy :engine, :create_by => :garage # DSL method property will define proxy's internal properties, # and you can provide initial default value. # property :manufacturer, :default => "Cosworth" property :max_rpm, :default => 12500 property :rpm, :default => 1000 end # This init method will set up your run time environment, # communication, eventloop, logging etc. We will explain that later. # OmfCommon.init(:development, communication: { url: 'xmpp://localhost' }) do OmfCommon.comm.on_connected do |comm| info "Garage controoler >> Connected to XMPP server" garage = OmfRc::ResourceFactory.create(:garage, uid: 'garage') comm.on_interrupted { garage.disconnect } end end }}} Now save the script to a file, then run the script using: {{{ ruby }}} You can verify it is running by compare its output to the following ===== Garage controller console output ===== {{{ 18:17:46 INFO XMPP::Communicator: Connecting to 'localhost' ... 18:17:47 INFO Object: Garage controller >> Connected to XMPP server 18:17:47 DEBUG XMPP::Topic: New topic: garage 18:17:47 DEBUG XMPP::Communicator: _subscribe >> garage SUCCEED 18:17:47 DEBUG XMPP::Communicator: publish >> garage SUCCEED Interact with garage controller }}} ==== Step 2: Request information of the garage ==== Now since the garage controller is running, we can query its information by sending FRCP request message. We will omf_common library's communicator to achieve this. Request information of the garage ===== Test script, querying garage ===== {{{ # Use omf_common communicator directly # require 'omf_common' # As seen previously, this init will set up various run time options for you. # # First line simply indicates: # * Use :development as default environment, # this will use Eventmachine by default, set logging level to :debug # * Use XMPP as default communication layer and XMPP server to connect to is localhost # * By default username:password will be auto generated # # OmfCommon.comm returns a communicator instance, # and this will be your entry point to interact with XMPP server. # # OmfCommon.eventloop returns Eventmachine runtime instance since it is default. # OmfCommon.init(:development, communication: { url: 'xmpp://localhost' }) do # Event :on_connected will be triggered when connected to XMPP server # OmfCommon.comm.on_connected do |comm| info "Engine test script >> Connected to XMPP" # Subscribe to a XMPP topic represents :garage, the name was set in the controller code if you wonder. # Once triggered, it will yield a Topic object. # comm.subscribe('garage') do |garage| unless garage.error? # Request two properties from garage, :uid and :type # # This is asynchronous, the reply_msg will only get processed when garage received the request # and we actually received the inform message it issued. # # Once we got the reply, simply iterate two properties and print them # garage.request([:uid, :type]) do |reply_msg| reply_msg.each_property do |k, v| info "#{k} >> #{v}" end end else error garage.inspect end end # Eventloop allows to control the flow, in this case, we disconnect after 5 seconds. # OmfCommon.eventloop.after(5) { comm.disconnect } # If you hit ctrl-c, we will disconnect too. # comm.on_interrupted { comm.disconnect } end end }}} While engine is running, execute this script, you should be able to see garage's properties in the output. ===== Console output ===== {{{ 13:08:25 INFO XMPP::Communicator: Connecting to 'localhost' ... 13:08:25 INFO Object: Engine test script >> Connected to XMPP 13:08:25 DEBUG XMPP::Topic: New topic: garage 13:08:25 DEBUG XMPP::Communicator: _subscribe >> garage SUCCEED 13:08:25 DEBUG XMPP::Topic: (garage) create_message_and_publish 'request': [:uid, :type] 13:08:25 DEBUG XMPP::Communicator: publish >> garage SUCCEED 13:08:25 INFO Object: uid >> garage 13:08:25 INFO Object: type >> garage 13:08:25 INFO Object: hrn >> 13:08:30 INFO XMPP::Communicator: Disconnecting ... }}} ==== Step 3: Create & release engine ==== Now we will let garage to create an engine proxy instance, and once it is created, we will then release it after certain period. This will demonstrate the resource life-cycle described in the FRCP protocol. Create & release engine ===== Create engine and then release it ===== {{{ require 'omf_common' # We define a create_engine method here to contain all the logic around engine creation # def create_engine(garage) # We create an engine instance with a human readable name 'my_engine' # garage.create(:engine, hrn: 'my_engine') do |reply_msg| # This reply_msg will be the inform message issued by garage controller # if reply_msg.success? # Since we need to interact with engine's PubSub topic, # we call #resource method to construct a topic from the FRCP message content. # engine = reply_msg.resource # Because of the asynchronous nature, we need to use this on_subscribed callback # to make sure the operation in the block executed only when subscribed to the newly created engine's topic engine.on_subscribed do info ">>> Connected to newly created engine #{reply_msg[:hrn]}(id: #{reply_msg[:res_id]})" end # Then later on, we will ask garage again to release this engine. # OmfCommon.eventloop.after(3) do release_engine(garage, engine) end else error ">>> Resource creation failed - #{reply_msg[:reason]}" end end end def release_engine(garage, engine) info ">>> Release engine" # Only parent (garage) can release its child (engine) # garage.release(engine) do |reply_msg| info "Engine #{reply_msg[:res_id]} released" OmfCommon.comm.disconnect end end OmfCommon.init(:development, communication: { url: 'xmpp://localhost' }) do OmfCommon.comm.on_connected do |comm| info "Engine test script >> Connected to XMPP" comm.subscribe('garage') do |garage| unless garage.error? # Now calling create_engine method we defined, with newly created garage topic object # create_engine(garage) else error garage.inspect end end OmfCommon.eventloop.after(10) { comm.disconnect } comm.on_interrupted { comm.disconnect } end end }}} While garage controller is running, execute this script and check the output Console output {{{ 13:20:50 INFO XMPP::Communicator: Connecting to 'localhost' ... 13:20:50 INFO Object: Engine test script >> Connected to XMPP 13:20:50 DEBUG XMPP::Topic: New topic: garage 13:20:50 DEBUG XMPP::Communicator: _subscribe >> garage SUCCEED 13:20:50 DEBUG XMPP::Topic: Create resource of type 'engine' 13:20:50 DEBUG XMPP::Topic: (garage) create_message_and_publish 'create': {:hrn=>"my_engine", :type=>:engine} 13:20:50 DEBUG XMPP::Communicator: publish >> garage SUCCEED 13:20:50 DEBUG XMPP::Topic: New topic: xmpp://1e1c5fd6-6d7a-4375-a48c-9dddbdf05394@interlagos 13:20:50 DEBUG XMPP::Communicator: _subscribe >> 1e1c5fd6-6d7a-4375-a48c-9dddbdf05394 SUCCEED 13:20:50 INFO Object: >>> Connected to newly created engine my_engine(id: xmpp://1e1c5fd6-6d7a-4375-a48c-9dddbdf05394@interlagos) 13:20:53 INFO Object: >>> Release engine 13:20:53 DEBUG XMPP::Communicator: publish >> garage SUCCEED 13:20:59 INFO Object: Engine xmpp://1e1c5fd6-6d7a-4375-a48c-9dddbdf05394@interlagos released 13:20:59 INFO XMPP::Communicator: Disconnecting ... }}} ==== Step 4: Add more features to garage engine proxies ==== Now we are going to add a feature of controlling throttle to the engine proxy, so that we can apply different level of throttle and monitor engine's RPM value. Configure engine property To allow this, we need to update our garage controller to allow configuring throttle, and provide some simple RPM calculation. Modified garage proxy {{{ require 'omf_rc' module OmfRc::ResourceProxy::Garage include OmfRc::ResourceProxyDSL register_proxy :garage end module OmfRc::ResourceProxy::Engine include OmfRc::ResourceProxyDSL register_proxy :engine, :create_by => :garage property :manufacturer, :default => "Cosworth" property :max_rpm, :default => 12500 # Add additional property to store rpm and throttle # property :rpm, :default => 1000 property :throttle, :default => 0 hook :before_ready do |engine| # Constantly calculate RPM value, rules are: # # * Applying 100% throttle will increase RPM by 5000 per second # * Engine will reduce RPM by 500 per second when no throttle applied # OmfCommon.eventloop.every(2) do engine.property.rpm += (engine.property.throttle * 5000 - 500).to_i engine.property.rpm = 1000 if engine.property.rpm < 1000 end end # Then we simply register a configure property handler for throttle, # We expect a percentage value received and convert into decimal value # configure :throttle do |engine, value| engine.property.throttle = value.to_f / 100.0 end end OmfCommon.init(:development, communication: { url: 'xmpp://localhost' }) do OmfCommon.comm.on_connected do |comm| info "Garage controoler >> Connected to XMPP server" garage = OmfRc::ResourceFactory.create(:garage, uid: 'garage') comm.on_interrupted { garage.disconnect } end end }}} We then modify the engine test script to configure throttle, and request RPM values. ===== Engine test ===== {{{ require 'omf_common' def create_engine(garage) garage.create(:engine, hrn: 'my_engine') do |reply_msg| if reply_msg.success? engine = reply_msg.resource engine.on_subscribed do info ">>> Connected to newly created engine #{reply_msg[:hrn]}(id: #{reply_msg[:res_id]})" on_engine_created(engine) end OmfCommon.eventloop.after(10) do release_engine(garage, engine) end else error ">>> Resource creation failed - #{reply_msg[:reason]}" end end end def on_engine_created(engine) info "> Now we will apply 50% throttle to the engine" engine.configure(throttle: 50) # Every 2 seconds, we send a request to engine, request its RPM value # OmfCommon.eventloop.every(2) do engine.request([:rpm]) do |reply_msg| info "RPM >> #{reply_msg[:rpm]}" end end # Some time later, we configure the throttle back to 0 # OmfCommon.eventloop.after(5) do info "> We want to reduce the throttle to 0" engine.configure(throttle: 0) end end def release_engine(garage, engine) info ">>> Release engine" garage.release(engine) do |reply_msg| info "Engine #{reply_msg[:res_id]} released" OmfCommon.comm.disconnect end end OmfCommon.init(:development, communication: { url: 'xmpp://localhost' }) do OmfCommon.comm.on_connected do |comm| info "Engine test script >> Connected to XMPP" comm.subscribe('garage') do |garage| unless garage.error? create_engine(garage) else error garage.inspect end end OmfCommon.eventloop.after(20) { comm.disconnect } comm.on_interrupted { comm.disconnect } end end }}} Now in the output you could notice that RPM value increased and then decreased. ===== Engine test output ===== {{{ 15:52:32 INFO XMPP::Communicator: Connecting to 'localhost' ... 15:52:32 INFO Object: Engine test script >> Connected to XMPP 15:52:32 DEBUG XMPP::Topic: New topic: garage 15:52:32 DEBUG XMPP::Communicator: _subscribe >> garage SUCCEED 15:52:32 DEBUG XMPP::Topic: Create resource of type 'engine' 15:52:32 DEBUG XMPP::Topic: (garage) create_message_and_publish 'create': {:hrn=>"my_engine", :type=>:engine} 15:52:32 DEBUG XMPP::Communicator: publish >> garage SUCCEED 15:52:32 DEBUG XMPP::Topic: New topic: xmpp://f9288cef-ae76-4561-a199-276800efe029@interlagos 15:52:32 DEBUG XMPP::Communicator: _subscribe >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED 15:52:32 INFO Object: >>> Connected to newly created engine my_engine(id: xmpp://f9288cef-ae76-4561-a199-276800efe029@interlagos) 15:52:32 INFO Object: > Now we will apply 50% throttle to the engine 15:52:32 DEBUG XMPP::Topic: (f9288cef-ae76-4561-a199-276800efe029) create_message_and_publish 'configure': {:throttle=>50} 15:52:32 DEBUG XMPP::Communicator: publish >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED 15:52:34 DEBUG XMPP::Topic: (f9288cef-ae76-4561-a199-276800efe029) create_message_and_publish 'request': [:rpm] 15:52:34 DEBUG XMPP::Communicator: publish >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED 15:52:34 INFO Object: RPM >> 3000 15:52:36 DEBUG XMPP::Topic: (f9288cef-ae76-4561-a199-276800efe029) create_message_and_publish 'request': [:rpm] 15:52:36 DEBUG XMPP::Communicator: publish >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED 15:52:36 INFO Object: RPM >> 5000 15:52:37 INFO Object: > We want to reduce the throttle to 0 15:52:37 DEBUG XMPP::Topic: (f9288cef-ae76-4561-a199-276800efe029) create_message_and_publish 'configure': {:throttle=>0} 15:52:37 DEBUG XMPP::Communicator: publish >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED 15:52:38 DEBUG XMPP::Topic: (f9288cef-ae76-4561-a199-276800efe029) create_message_and_publish 'request': [:rpm] 15:52:38 DEBUG XMPP::Communicator: publish >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED 15:52:38 INFO Object: RPM >> 4500 15:52:40 DEBUG XMPP::Topic: (f9288cef-ae76-4561-a199-276800efe029) create_message_and_publish 'request': [:rpm] 15:52:40 DEBUG XMPP::Communicator: publish >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED 15:52:40 INFO Object: RPM >> 4000 15:52:42 INFO Object: >>> Release engine 15:52:42 DEBUG XMPP::Communicator: publish >> garage SUCCEED 15:52:42 DEBUG XMPP::Topic: (f9288cef-ae76-4561-a199-276800efe029) create_message_and_publish 'request': [:rpm] 15:52:42 DEBUG XMPP::Communicator: publish >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED 15:52:42 INFO Object: RPM >> 3500 15:52:44 DEBUG XMPP::Topic: (f9288cef-ae76-4561-a199-276800efe029) create_message_and_publish 'request': [:rpm] 15:52:44 DEBUG XMPP::Communicator: publish >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED 15:52:44 INFO Object: RPM >> 3000 15:52:46 DEBUG XMPP::Topic: (f9288cef-ae76-4561-a199-276800efe029) create_message_and_publish 'request': [:rpm] 15:52:46 DEBUG XMPP::Communicator: publish >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED 15:52:46 INFO Object: RPM >> 2500 15:52:47 INFO Object: Engine xmpp://f9288cef-ae76-4561-a199-276800efe029@interlagos released 15:52:47 INFO XMPP::Communicator: Disconnecting ... }}} ==== Step 5: Hooks ==== OMF allow you to define hook callbacks, which basically can be called at certain stage of the operation. Currently the system supports these hooks: Execute in parent resource before_create, called before parent creates the child resource. (in the context of parent resource) after_create, called after parent creates the child resource. Execute in child resource before_ready, called when a resource created, before creating an associated pubsub topic before_release, called before a resource released after_initial_configured, called after child resource created, and initial set of properties have been configured. Please also refer to DSL hook method for more information. Hooks Now modify the garage controller to test these hooks. Proxy with hooks require 'omf_rc' module OmfRc::ResourceProxy::Garage include OmfRc::ResourceProxyDSL register_proxy :garage # before_create allows you access the current garage instance, the type of new resource it is going to create, # and initial options passed to be used for new resource # hook :before_create do |garage, new_resource_type, new_resource_opts| # Can check existing engines already created # info "Garage has #{garage.children.size} engine(s)" # Can verify new resource's options # info "You asked me to create a new #{new_resource_type} with options: #{new_resource_opts}" end # after_create hook has access to the current garage instance and newly created engine instance # hook :after_create do |garage, engine| # Can inspect or update newly created resource # info "Engine #{engine.uid} created" end end module OmfRc::ResourceProxy::Engine include OmfRc::ResourceProxyDSL register_proxy :engine, :create_by => :garage property :serial_number, :default => "0000" property :rpm, :default => 0 # Use this to do initialisation/bootstrap # hook :before_ready do |engine| engine.property.rpm = 1000 # Notice that now serial number hasn't been configured yet. # info "Engine serial number is #{engine.property.serial_number}" end # Since now new resource has been created and configured properly, # additional logic can be applied based on configured properties' state. # hook :after_initial_configured do |engine| # Notice now serial number is configured. # info "Engine serial number is #{engine.property.serial_number}" end # before_release hook will be called before the resource is fully released, shut down the engine in this case. # hook :before_release do |engine| engine.property.rpm = 0 end end OmfCommon.init(:development, communication: { url: 'xmpp://localhost' }) do OmfCommon.comm.on_connected do |comm| info "Garage controoler >> Connected to XMPP server" garage = OmfRc::ResourceFactory.create(:garage, uid: 'garage', hrn: 'my_garage') comm.on_interrupted { garage.disconnect } end end Then use this simple engine test script to run through the resource cycle, from create to release. Engine test {{{ require 'omf_common' def create_engine(garage) garage.create(:engine, hrn: 'my_engine', serial_number: '1234') do |reply_msg| if reply_msg.success? engine = reply_msg.resource engine.on_subscribed do info ">>> Connected to newly created engine #{reply_msg[:hrn]}(id: #{reply_msg[:res_id]})" end OmfCommon.eventloop.after(3) do release_engine(garage, engine) end else error ">>> Resource creation failed - #{reply_msg[:reason]}" end end end def release_engine(garage, engine) info ">>> Release engine" garage.release(engine) do |reply_msg| info "Engine #{reply_msg[:res_id]} released" OmfCommon.comm.disconnect end end OmfCommon.init(:development, communication: { url: 'xmpp://localhost' }) do OmfCommon.comm.on_connected do |comm| info "Engine test script >> Connected to XMPP" comm.subscribe('garage') do |garage| unless garage.error? create_engine(garage) else error garage.inspect end end OmfCommon.eventloop.after(10) { comm.disconnect } comm.on_interrupted { comm.disconnect } end end }}} While you are running garage controller and test script, you should be able to see the following log message on the garage controller side. Please check the 'info' messages and you could notice the order of how these hooks got executed. Verify the order against the diagram we showed earlier. Proxy output {{{ 15:33:03 INFO XMPP::Communicator: Connecting to 'localhost' ... 15:33:03 INFO Object: Garage controoler >> Connected to XMPP server 15:33:03 DEBUG XMPP::Topic: New topic: garage 15:33:03 DEBUG XMPP::Communicator: _subscribe >> garage SUCCEED 15:33:03 DEBUG XMPP::Communicator: publish >> garage SUCCEED 15:33:12 INFO ResourceProxy::Garage: Garage has 0 engine(s) 15:33:12 INFO ResourceProxy::Garage: You asked me to create a new engine with options: {:hrn=>"my_engine"} 15:33:12 DEBUG XMPP::Topic: New topic: 9ae92796-bcd7-4770-a698-78df44d63fe2 15:33:12 INFO ResourceProxy::Engine: Engine serial number is 0000 15:33:12 INFO ResourceProxy::Garage: Engine 9ae92796-bcd7-4770-a698-78df44d63fe2 created 15:33:12 DEBUG XMPP::Communicator: _create >> 9ae92796-bcd7-4770-a698-78df44d63fe2 SUCCEED 15:33:12 DEBUG XMPP::Communicator: _subscribe >> 9ae92796-bcd7-4770-a698-78df44d63fe2 SUCCEED 15:33:12 INFO ResourceProxy::Engine: Engine serial number is 1234 15:33:12 DEBUG XMPP::Communicator: publish >> garage SUCCEED 15:33:12 DEBUG XMPP::Communicator: publish >> 9ae92796-bcd7-4770-a698-78df44d63fe2 SUCCEED 15:33:15 INFO ResourceProxy::AbstractResource: Releasing hrn: my_engine, uid: 9ae92796-bcd7-4770-a698-78df44d63fe2 }}} That concludes our little tutorial for you. ==== Proxies included in the official RC gem ==== OMF RC has included some resource proxies to support network configuration and running OMF enabled applications. For more details, refer to OmfRc::ResourceProxy::AbstractResource. Application Proxy Application proxy has been provided to set up and control applications. We include a separate guide explaining how it works. See How to use the Application Proxy. It is also a good example demonstrating how to write a non-trivial resource proxy. Logging Given controlling logging is important for developing and debugging, we created a dedicated guide. Logging system in OMF 6 Advanced topics Organise resource proxy modules Define inline If you have a rather simple resource controller, with minimal set of features, like the ones described in this tutorial, you could just define these modules as part of the RC script. Include resource proxy modules in the default package The default location of resource proxy definition files are located in the directory omf_rc/lib/omf_rc/resource_proxy. If you wish your feature set could be available as part of the default package, save them under this default directory, following this naming convention: OmfRc::ResourceProxy::Engine will register a proxy named :engine, and saved to file omf_rc/lib/omf_rc/resource_proxy/engine.rb To load these default resource proxies, simple call a load method provided by ResourceFactory class in your resource controller script (e.g. engine_control.rb) OmfRc::ResourceFactory.load_default_resource_proxies Commit your definition files into the git repository and simply send us a pull request. Package your proxy definition files as OMF extension gem You could also package your proxy definition files into separate gems, if you feel they should not go into the default RC package. This process is rather simple, take a look at this third party rc gem of openflow integration. https://github.com/kohoumas/omf_rc_openflow Refactor common features into resource utilities If a set of features can be shared among different types of resources, it is a good idea to refactor them into resource utilities. Take this engine test example, if we have more than one type of engine needs to be tested, and they could all be able to adjust throttle, we can create a utility for this. {{{ module OmfRc::Util::Throttle include OmfRc::ResourceProxyDSL configure :throttle do |resource, value| resource.property.throttle = value.to_f / 100.0 end end }}} Then include this utility inside the engine resource proxy file by using: {{{ utility :throttle }}} You could also overwrite a property definition provided by the utility, by registering it again using the same name. === Contributing to OMF === Ready to be part of OMF project? Please refer to [wiki:OMF/OMF60/1Developer/1Contributing Contributing] document for some guidelines.