Changes between Version 2 and Version 3 of OMF/OMF60/1Developer


Ignore:
Timestamp:
Jun 9, 2019, 11:18:59 PM (5 years ago)
Author:
seskar
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • OMF/OMF60/1Developer

    v2 v3  
    1 == Developer Documentation ==
    2 
    3 [[TOC(heading=OMF 6.0 Documentation, OMF/OMF60/1Developer*, depth=4)]]
    4 
     1== Developers Guide to OMF ==
     2
     3=== Prerequisites ===
     4==== Set up your environment ====
     5Please please make sure you followed the [wiki:OMF/OMF60/2Operator/0aInstallation INSTALLATION GUIDE] and get things installed and configured properly.
     6
     7=== Communication layer test and debug tools ===
     8To quickly send a message or monitor incoming messages on a topic, we provide a few utilities that come with the omf_common gem.
     9
     10Monitor a topic:
     11{{{
     12$ omf_monitor_topic -h
     13Usage: omf_monitor_topic [options] topic1 topic2 ...
     14
     15Monitor a set of resources (topics) and print all observed messages.
     16
     17If the 'follow-children' flag is set, automatically add all resources
     18created by the monitored resources to the monitor set. Please note
     19that there will be a delay until the new monitors are in place which
     20can result in missed messages.
     21
     22-c, --comms-url URL              URL of communication server (e.g. xmpp://my.server.com) []
     23-f, --[no-]follow-children       Follow all newly created resources [true]
     24-d, --debug                      Set log level to DEBUG
     25-h, --help                       Show this message
     26}}}
     27
     28Send a CREATE message:
     29{{{
     30$ omf_send_create -h
     31Usage: omf_send_create [options] property1:value1 property2:value2 ...
     32
     33Send a create message to a specific resource (topic) and print out any replies.
     34
     35Any additional command line arguments are interpreted as property:value and are
     36sent with the create message.
     37
     38-r, --resource-url URL           URL of resource (e.g. xmpp://my.server.com/topic1)
     39-t, --type TYPE                  Type of resource to create (e.g. node)
     40-y, --yaml YAML_FILE             Read type and property from YAML file
     41-d, --debug                      Set log level to DEBUG
     42-h, --help                       Show this message
     43}}}
     44
     45Send a REQUEST message:
     46{{{
     47$ omf_send_request -h
     48Usage: omf_send_request [options] prop1 prop2 ...
     49
     50Send a request to a specific resource (topic) and print out any replies.
     51
     52Any additional command line arguments are interpreted as limiting the request
     53to those, otherwise all properties are requested.
     54
     55    -r, --resource-url URL           URL of resource (e.g. xmpp://my.server.com/topic1)
     56    -d, --debug                      Set log level to DEBUG
     57    -h, --help                       Show this message
     58}}}
     59
     60=== Background ===
     61==== FRCP protocol ====
     62If 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].
     63
     64==== Resource controller system ====
     65One 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.
     66
     67In 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.
     68
     69Interaction with proxy
     70
     71We created a little tutorial will give a brief example demonstrating the work flow and how to implement these resource proxy definitions.
     72
     73=== Tutorial ===
     74Suppose 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.
     75
     76This tutorial will utilise DSL methods provided by our RC library, and the full API documentation can be accessed here:
     77
     78[/docs/60/OmfRc/ResourceProxyDSL/ClassMethods.html Resource proxy DSL API doucumentation ]
     79
     80This will be a good reference you feel puzzled about certain keywords, methods in the tutorial code we provide here.
     81
     82The Ruby source code provided in this tutorial can be copied and pasted to your favourite editor, and execute as standard Ruby scripts.
     83
     84==== Step 1: Garage and engine proxy definition ====
     85We 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.
     86
     87A 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.
     88
     89We start by defining a garage proxy and a engine proxy, where garage can create engines (parent child relationship).
     90
     91===== Garage controller skeletion =====
     92{{{
     93# Need omf_rc gem to be required
     94#
     95require 'omf_rc'
     96
     97# By using default namespace OmfRc::ResourceProxy, the module defined could be loaded automatically.
     98#
     99module OmfRc::ResourceProxy::Garage
     100  # Include DSL module, which provides all DSL helper methods
     101  #
     102  include OmfRc::ResourceProxyDSL
     103
     104  # DSL method register_proxy will register this module definition,
     105  # where :garage become the :type of the proxy.
     106  #
     107  register_proxy :garage
     108end
     109
     110module OmfRc::ResourceProxy::Engine
     111  include OmfRc::ResourceProxyDSL
     112
     113  # You can specify what kind of proxy can create it, this case, :garage
     114  #
     115  register_proxy :engine, :create_by => :garage
     116
     117  # DSL method property will define proxy's internal properties,
     118  # and you can provide initial default value.
     119  #
     120  property :manufacturer, :default => "Cosworth"
     121  property :max_rpm, :default => 12500
     122  property :rpm, :default => 1000
     123end
     124
     125# This init method will set up your run time environment,
     126# communication, eventloop, logging etc. We will explain that later.
     127#
     128OmfCommon.init(:development, communication: { url: 'xmpp://localhost' }) do
     129  OmfCommon.comm.on_connected do |comm|
     130    info "Garage controoler >> Connected to XMPP server"
     131    garage = OmfRc::ResourceFactory.create(:garage, uid: 'garage')
     132    comm.on_interrupted { garage.disconnect }
     133  end
     134end
     135}}}
     136
     137Now save the script to a file, then run the script using:
     138{{{
     139ruby <filename>
     140}}}
     141
     142You can verify it is running by compare its output to the following
     143
     144===== Garage controller console output =====
     145{{{
     14618:17:46  INFO XMPP::Communicator: Connecting to 'localhost' ...
     14718:17:47  INFO Object: Garage controller >> Connected to XMPP server
     14818:17:47 DEBUG XMPP::Topic: New topic: garage
     14918:17:47 DEBUG XMPP::Communicator: _subscribe >> garage SUCCEED
     15018:17:47 DEBUG XMPP::Communicator: publish >> garage SUCCEED
     151Interact with garage controller
     152}}}
     153
     154==== Step 2: Request information of the garage ====
     155Now 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.
     156
     157Request information of the garage
     158
     159===== Test script, querying garage =====
     160{{{
     161# Use omf_common communicator directly
     162#
     163require 'omf_common'
     164
     165# As seen previously, this init will set up various run time options for you.
     166#
     167# First line simply indicates:
     168# * Use :development as default environment,
     169#   this will use Eventmachine by default, set logging level to :debug
     170# * Use XMPP as default communication layer and XMPP server to connect to is localhost
     171# * By default username:password will be auto generated
     172#
     173# OmfCommon.comm returns a communicator instance,
     174# and this will be your entry point to interact with XMPP server.
     175#
     176# OmfCommon.eventloop returns Eventmachine runtime instance since it is default.
     177#
     178OmfCommon.init(:development, communication: { url: 'xmpp://localhost' }) do
     179  # Event :on_connected will be triggered when connected to XMPP server
     180  #
     181  OmfCommon.comm.on_connected do |comm|
     182    info "Engine test script >> Connected to XMPP"
     183
     184    # Subscribe to a XMPP topic represents :garage, the name was set in the controller code if you wonder.
     185    # Once triggered, it will yield a Topic object.
     186    #
     187    comm.subscribe('garage') do |garage|
     188      unless garage.error?
     189        # Request two properties from garage, :uid and :type
     190        #
     191        # This is asynchronous, the reply_msg will only get processed when garage received the request
     192        # and we actually received the inform message it issued.
     193        #
     194        # Once we got the reply, simply iterate two properties and print them
     195        #
     196        garage.request([:uid, :type]) do |reply_msg|
     197          reply_msg.each_property do |k, v|
     198            info "#{k} >> #{v}"
     199          end
     200        end
     201      else
     202        error garage.inspect
     203      end
     204    end
     205
     206    # Eventloop allows to control the flow, in this case, we disconnect after 5 seconds.
     207    #
     208    OmfCommon.eventloop.after(5) { comm.disconnect }
     209    # If you hit ctrl-c, we will disconnect too.
     210    #
     211    comm.on_interrupted { comm.disconnect }
     212  end
     213end
     214}}}
     215
     216While engine is running, execute this script, you should be able to see garage's properties in the output.
     217
     218===== Console output =====
     219{{{
     22013:08:25  INFO XMPP::Communicator: Connecting to 'localhost' ...
     22113:08:25  INFO Object: Engine test script >> Connected to XMPP
     22213:08:25 DEBUG XMPP::Topic: New topic: garage
     22313:08:25 DEBUG XMPP::Communicator: _subscribe >> garage SUCCEED
     22413:08:25 DEBUG XMPP::Topic: (garage) create_message_and_publish 'request': [:uid, :type]
     22513:08:25 DEBUG XMPP::Communicator: publish >> garage SUCCEED
     22613:08:25  INFO Object: uid >> garage
     22713:08:25  INFO Object: type >> garage
     22813:08:25  INFO Object: hrn >>
     22913:08:30  INFO XMPP::Communicator: Disconnecting ...
     230}}}
     231
     232==== Step 3: Create & release engine ====
     233
     234Now 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.
     235
     236Create & release engine
     237
     238===== Create engine and then release it =====
     239
     240{{{
     241require 'omf_common'
     242
     243# We define a create_engine method here to contain all the logic around engine creation
     244#
     245def create_engine(garage)
     246  # We create an engine instance with a human readable name 'my_engine'
     247  #
     248  garage.create(:engine, hrn: 'my_engine') do |reply_msg|
     249    # This reply_msg will be the inform message issued by garage controller
     250    #
     251    if reply_msg.success?
     252      # Since we need to interact with engine's PubSub topic,
     253      # we call #resource method to construct a topic from the FRCP message content.
     254      #
     255      engine = reply_msg.resource
     256
     257      # Because of the asynchronous nature, we need to use this on_subscribed callback
     258      # to make sure the operation in the block executed only when subscribed to the newly created engine's topic
     259      engine.on_subscribed do
     260        info ">>> Connected to newly created engine #{reply_msg[:hrn]}(id: #{reply_msg[:res_id]})"
     261      end
     262
     263      # Then later on, we will ask garage again to release this engine.
     264      #
     265      OmfCommon.eventloop.after(3) do
     266        release_engine(garage, engine)
     267      end
     268    else
     269      error ">>> Resource creation failed - #{reply_msg[:reason]}"
     270    end
     271  end
     272end
     273
     274def release_engine(garage, engine)
     275  info ">>> Release engine"
     276  # Only parent (garage) can release its child (engine)
     277  #
     278  garage.release(engine) do |reply_msg|
     279    info "Engine #{reply_msg[:res_id]} released"
     280    OmfCommon.comm.disconnect
     281  end
     282end
     283
     284OmfCommon.init(:development, communication: { url: 'xmpp://localhost' }) do
     285  OmfCommon.comm.on_connected do |comm|
     286    info "Engine test script >> Connected to XMPP"
     287
     288    comm.subscribe('garage') do |garage|
     289      unless garage.error?
     290        # Now calling create_engine method we defined, with newly created garage topic object
     291        #
     292        create_engine(garage)
     293      else
     294        error garage.inspect
     295      end
     296    end
     297
     298    OmfCommon.eventloop.after(10) { comm.disconnect }
     299    comm.on_interrupted { comm.disconnect }
     300  end
     301end
     302}}}
     303
     304While garage controller is running, execute this script and check the output
     305
     306Console output
     307{{{
     30813:20:50  INFO XMPP::Communicator: Connecting to 'localhost' ...
     30913:20:50  INFO Object: Engine test script >> Connected to XMPP
     31013:20:50 DEBUG XMPP::Topic: New topic: garage
     31113:20:50 DEBUG XMPP::Communicator: _subscribe >> garage SUCCEED
     31213:20:50 DEBUG XMPP::Topic: Create resource of type 'engine'
     31313:20:50 DEBUG XMPP::Topic: (garage) create_message_and_publish 'create': {:hrn=>"my_engine", :type=>:engine}
     31413:20:50 DEBUG XMPP::Communicator: publish >> garage SUCCEED
     31513:20:50 DEBUG XMPP::Topic: New topic: xmpp://1e1c5fd6-6d7a-4375-a48c-9dddbdf05394@interlagos
     31613:20:50 DEBUG XMPP::Communicator: _subscribe >> 1e1c5fd6-6d7a-4375-a48c-9dddbdf05394 SUCCEED
     31713:20:50  INFO Object: >>> Connected to newly created engine my_engine(id: xmpp://1e1c5fd6-6d7a-4375-a48c-9dddbdf05394@interlagos)
     31813:20:53  INFO Object: >>> Release engine
     31913:20:53 DEBUG XMPP::Communicator: publish >> garage SUCCEED
     32013:20:59  INFO Object: Engine xmpp://1e1c5fd6-6d7a-4375-a48c-9dddbdf05394@interlagos released
     32113:20:59  INFO XMPP::Communicator: Disconnecting ...
     322}}}
     323
     324==== Step 4: Add more features to garage engine proxies ====
     325
     326Now 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.
     327
     328Configure engine property
     329
     330To allow this, we need to update our garage controller to allow configuring throttle, and provide some simple RPM calculation.
     331
     332Modified garage proxy
     333{{{
     334require 'omf_rc'
     335
     336module OmfRc::ResourceProxy::Garage
     337  include OmfRc::ResourceProxyDSL
     338
     339  register_proxy :garage
     340end
     341
     342module OmfRc::ResourceProxy::Engine
     343  include OmfRc::ResourceProxyDSL
     344
     345  register_proxy :engine, :create_by => :garage
     346
     347  property :manufacturer, :default => "Cosworth"
     348  property :max_rpm, :default => 12500
     349  # Add additional property to store rpm and throttle
     350  #
     351  property :rpm, :default => 1000
     352  property :throttle, :default => 0
     353
     354  hook :before_ready do |engine|
     355    # Constantly calculate RPM value, rules are:
     356    #
     357    # * Applying 100% throttle will increase RPM by 5000 per second
     358    # * Engine will reduce RPM by 500 per second when no throttle applied
     359    #
     360    OmfCommon.eventloop.every(2) do
     361      engine.property.rpm += (engine.property.throttle * 5000 - 500).to_i
     362      engine.property.rpm = 1000 if engine.property.rpm < 1000
     363    end
     364  end
     365
     366  # Then we simply register a configure property handler for throttle,
     367  # We expect a percentage value received and convert into decimal value
     368  #
     369  configure :throttle do |engine, value|
     370    engine.property.throttle = value.to_f / 100.0
     371  end
     372end
     373
     374OmfCommon.init(:development, communication: { url: 'xmpp://localhost' }) do
     375  OmfCommon.comm.on_connected do |comm|
     376    info "Garage controoler >> Connected to XMPP server"
     377    garage = OmfRc::ResourceFactory.create(:garage, uid: 'garage')
     378    comm.on_interrupted { garage.disconnect }
     379  end
     380end
     381}}}
     382
     383We then modify the engine test script to configure throttle, and request RPM values.
     384
     385===== Engine test =====
     386
     387{{{
     388require 'omf_common'
     389
     390def create_engine(garage)
     391  garage.create(:engine, hrn: 'my_engine') do |reply_msg|
     392    if reply_msg.success?
     393      engine = reply_msg.resource
     394
     395      engine.on_subscribed do
     396        info ">>> Connected to newly created engine #{reply_msg[:hrn]}(id: #{reply_msg[:res_id]})"
     397        on_engine_created(engine)
     398      end
     399
     400      OmfCommon.eventloop.after(10) do
     401        release_engine(garage, engine)
     402      end
     403    else
     404      error ">>> Resource creation failed - #{reply_msg[:reason]}"
     405    end
     406  end
     407end
     408
     409def on_engine_created(engine)
     410  info "> Now we will apply 50% throttle to the engine"
     411  engine.configure(throttle: 50)
     412
     413  # Every 2 seconds, we send a request to engine, request its RPM value
     414  #
     415  OmfCommon.eventloop.every(2) do
     416    engine.request([:rpm]) do |reply_msg|
     417      info "RPM >> #{reply_msg[:rpm]}"
     418    end
     419  end
     420
     421  # Some time later, we configure the throttle back to 0
     422  #
     423  OmfCommon.eventloop.after(5) do
     424    info "> We want to reduce the throttle to 0"
     425    engine.configure(throttle: 0)
     426  end
     427end
     428
     429def release_engine(garage, engine)
     430  info ">>> Release engine"
     431  garage.release(engine) do |reply_msg|
     432    info "Engine #{reply_msg[:res_id]} released"
     433    OmfCommon.comm.disconnect
     434  end
     435end
     436
     437OmfCommon.init(:development, communication: { url: 'xmpp://localhost' }) do
     438  OmfCommon.comm.on_connected do |comm|
     439    info "Engine test script >> Connected to XMPP"
     440
     441    comm.subscribe('garage') do |garage|
     442      unless garage.error?
     443        create_engine(garage)
     444      else
     445        error garage.inspect
     446      end
     447    end
     448
     449    OmfCommon.eventloop.after(20) { comm.disconnect }
     450    comm.on_interrupted { comm.disconnect }
     451  end
     452end
     453}}}
     454
     455Now in the output you could notice that RPM value increased and then decreased.
     456
     457===== Engine test output =====
     458{{{
     45915:52:32  INFO XMPP::Communicator: Connecting to 'localhost' ...
     46015:52:32  INFO Object: Engine test script >> Connected to XMPP
     46115:52:32 DEBUG XMPP::Topic: New topic: garage
     46215:52:32 DEBUG XMPP::Communicator: _subscribe >> garage SUCCEED
     46315:52:32 DEBUG XMPP::Topic: Create resource of type 'engine'
     46415:52:32 DEBUG XMPP::Topic: (garage) create_message_and_publish 'create': {:hrn=>"my_engine", :type=>:engine}
     46515:52:32 DEBUG XMPP::Communicator: publish >> garage SUCCEED
     46615:52:32 DEBUG XMPP::Topic: New topic: xmpp://f9288cef-ae76-4561-a199-276800efe029@interlagos
     46715:52:32 DEBUG XMPP::Communicator: _subscribe >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED
     46815:52:32  INFO Object: >>> Connected to newly created engine my_engine(id: xmpp://f9288cef-ae76-4561-a199-276800efe029@interlagos)
     46915:52:32  INFO Object: > Now we will apply 50% throttle to the engine
     47015:52:32 DEBUG XMPP::Topic: (f9288cef-ae76-4561-a199-276800efe029) create_message_and_publish 'configure': {:throttle=>50}
     47115:52:32 DEBUG XMPP::Communicator: publish >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED
     47215:52:34 DEBUG XMPP::Topic: (f9288cef-ae76-4561-a199-276800efe029) create_message_and_publish 'request': [:rpm]
     47315:52:34 DEBUG XMPP::Communicator: publish >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED
     47415:52:34  INFO Object: RPM >> 3000
     47515:52:36 DEBUG XMPP::Topic: (f9288cef-ae76-4561-a199-276800efe029) create_message_and_publish 'request': [:rpm]
     47615:52:36 DEBUG XMPP::Communicator: publish >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED
     47715:52:36  INFO Object: RPM >> 5000
     47815:52:37  INFO Object: > We want to reduce the throttle to 0
     47915:52:37 DEBUG XMPP::Topic: (f9288cef-ae76-4561-a199-276800efe029) create_message_and_publish 'configure': {:throttle=>0}
     48015:52:37 DEBUG XMPP::Communicator: publish >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED
     48115:52:38 DEBUG XMPP::Topic: (f9288cef-ae76-4561-a199-276800efe029) create_message_and_publish 'request': [:rpm]
     48215:52:38 DEBUG XMPP::Communicator: publish >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED
     48315:52:38  INFO Object: RPM >> 4500
     48415:52:40 DEBUG XMPP::Topic: (f9288cef-ae76-4561-a199-276800efe029) create_message_and_publish 'request': [:rpm]
     48515:52:40 DEBUG XMPP::Communicator: publish >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED
     48615:52:40  INFO Object: RPM >> 4000
     48715:52:42  INFO Object: >>> Release engine
     48815:52:42 DEBUG XMPP::Communicator: publish >> garage SUCCEED
     48915:52:42 DEBUG XMPP::Topic: (f9288cef-ae76-4561-a199-276800efe029) create_message_and_publish 'request': [:rpm]
     49015:52:42 DEBUG XMPP::Communicator: publish >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED
     49115:52:42  INFO Object: RPM >> 3500
     49215:52:44 DEBUG XMPP::Topic: (f9288cef-ae76-4561-a199-276800efe029) create_message_and_publish 'request': [:rpm]
     49315:52:44 DEBUG XMPP::Communicator: publish >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED
     49415:52:44  INFO Object: RPM >> 3000
     49515:52:46 DEBUG XMPP::Topic: (f9288cef-ae76-4561-a199-276800efe029) create_message_and_publish 'request': [:rpm]
     49615:52:46 DEBUG XMPP::Communicator: publish >> f9288cef-ae76-4561-a199-276800efe029 SUCCEED
     49715:52:46  INFO Object: RPM >> 2500
     49815:52:47  INFO Object: Engine xmpp://f9288cef-ae76-4561-a199-276800efe029@interlagos released
     49915:52:47  INFO XMPP::Communicator: Disconnecting ...
     500}}}
     501
     502==== Step 5: Hooks ====
     503
     504OMF allow you to define hook callbacks, which basically can be called at certain stage of the operation.
     505
     506Currently the system supports these hooks:
     507
     508Execute in parent resource
     509
     510before_create, called before parent creates the child resource. (in the context of parent resource)
     511after_create, called after parent creates the child resource.
     512Execute in child resource
     513
     514before_ready, called when a resource created, before creating an associated pubsub topic
     515before_release, called before a resource released
     516after_initial_configured, called after child resource created, and initial set of properties have been configured.
     517Please also refer to DSL hook method for more information.
     518
     519Hooks
     520
     521Now modify the garage controller to test these hooks.
     522
     523Proxy with hooks
     524
     525require 'omf_rc'
     526
     527module OmfRc::ResourceProxy::Garage
     528  include OmfRc::ResourceProxyDSL
     529
     530  register_proxy :garage
     531
     532  # before_create allows you access the current garage instance, the type of new resource it is going to create,
     533  # and initial options passed to be used for new resource
     534  #
     535  hook :before_create do |garage, new_resource_type, new_resource_opts|
     536    # Can check existing engines already created
     537    #
     538    info "Garage has #{garage.children.size} engine(s)"
     539
     540    # Can verify new resource's options
     541    #
     542    info "You asked me to create a new #{new_resource_type} with options: #{new_resource_opts}"
     543  end
     544
     545  # after_create hook has access to the current garage instance and newly created engine instance
     546  #
     547  hook :after_create do |garage, engine|
     548    # Can inspect or update newly created resource
     549    #
     550    info "Engine #{engine.uid} created"
     551  end
     552end
     553
     554module OmfRc::ResourceProxy::Engine
     555  include OmfRc::ResourceProxyDSL
     556
     557  register_proxy :engine, :create_by => :garage
     558
     559  property :serial_number, :default => "0000"
     560  property :rpm, :default => 0
     561
     562  # Use this to do initialisation/bootstrap
     563  #
     564  hook :before_ready do |engine|
     565    engine.property.rpm = 1000
     566    # Notice that now serial number hasn't been configured yet.
     567    #
     568    info "Engine serial number is #{engine.property.serial_number}"
     569  end
     570
     571  # Since now new resource has been created and configured properly,
     572  # additional logic can be applied based on configured properties' state.
     573  #
     574  hook :after_initial_configured do |engine|
     575    # Notice now serial number is configured.
     576    #
     577    info "Engine serial number is #{engine.property.serial_number}"
     578  end
     579
     580  # before_release hook will be called before the resource is fully released, shut down the engine in this case.
     581  #
     582  hook :before_release do |engine|
     583    engine.property.rpm = 0
     584  end
     585end
     586
     587OmfCommon.init(:development, communication: { url: 'xmpp://localhost' }) do
     588  OmfCommon.comm.on_connected do |comm|
     589    info "Garage controoler >> Connected to XMPP server"
     590    garage = OmfRc::ResourceFactory.create(:garage, uid: 'garage', hrn: 'my_garage')
     591    comm.on_interrupted { garage.disconnect }
     592  end
     593end
     594Then use this simple engine test script to run through the resource cycle, from create to release.
     595
     596Engine test
     597{{{
     598require 'omf_common'
     599
     600def create_engine(garage)
     601  garage.create(:engine, hrn: 'my_engine', serial_number: '1234') do |reply_msg|
     602    if reply_msg.success?
     603      engine = reply_msg.resource
     604
     605      engine.on_subscribed do
     606        info ">>> Connected to newly created engine #{reply_msg[:hrn]}(id: #{reply_msg[:res_id]})"
     607      end
     608
     609      OmfCommon.eventloop.after(3) do
     610        release_engine(garage, engine)
     611      end
     612    else
     613      error ">>> Resource creation failed - #{reply_msg[:reason]}"
     614    end
     615  end
     616end
     617
     618def release_engine(garage, engine)
     619  info ">>> Release engine"
     620  garage.release(engine) do |reply_msg|
     621    info "Engine #{reply_msg[:res_id]} released"
     622    OmfCommon.comm.disconnect
     623  end
     624end
     625
     626OmfCommon.init(:development, communication: { url: 'xmpp://localhost' }) do
     627  OmfCommon.comm.on_connected do |comm|
     628    info "Engine test script >> Connected to XMPP"
     629
     630    comm.subscribe('garage') do |garage|
     631      unless garage.error?
     632        create_engine(garage)
     633      else
     634        error garage.inspect
     635      end
     636    end
     637
     638    OmfCommon.eventloop.after(10) { comm.disconnect }
     639    comm.on_interrupted { comm.disconnect }
     640  end
     641end
     642}}}
     643
     644While you are running garage controller and test script, you should be able to see the following log message on the garage controller side.
     645
     646Please 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.
     647
     648Proxy output
     649
     650{{{
     65115:33:03  INFO XMPP::Communicator: Connecting to 'localhost' ...
     65215:33:03  INFO Object: Garage controoler >> Connected to XMPP server
     65315:33:03 DEBUG XMPP::Topic: New topic: garage
     65415:33:03 DEBUG XMPP::Communicator: _subscribe >> garage SUCCEED
     65515:33:03 DEBUG XMPP::Communicator: publish >> garage SUCCEED
     65615:33:12  INFO ResourceProxy::Garage: Garage has 0 engine(s)
     65715:33:12  INFO ResourceProxy::Garage: You asked me to create a new engine with options: {:hrn=>"my_engine"}
     65815:33:12 DEBUG XMPP::Topic: New topic: 9ae92796-bcd7-4770-a698-78df44d63fe2
     65915:33:12  INFO ResourceProxy::Engine: Engine serial number is 0000
     66015:33:12  INFO ResourceProxy::Garage: Engine 9ae92796-bcd7-4770-a698-78df44d63fe2 created
     66115:33:12 DEBUG XMPP::Communicator: _create >> 9ae92796-bcd7-4770-a698-78df44d63fe2 SUCCEED
     66215:33:12 DEBUG XMPP::Communicator: _subscribe >> 9ae92796-bcd7-4770-a698-78df44d63fe2 SUCCEED
     66315:33:12  INFO ResourceProxy::Engine: Engine serial number is 1234
     66415:33:12 DEBUG XMPP::Communicator: publish >> garage SUCCEED
     66515:33:12 DEBUG XMPP::Communicator: publish >> 9ae92796-bcd7-4770-a698-78df44d63fe2 SUCCEED
     66615:33:15  INFO ResourceProxy::AbstractResource: Releasing hrn: my_engine, uid: 9ae92796-bcd7-4770-a698-78df44d63fe2
     667}}}
     668
     669That concludes our little tutorial for you.
     670
     671==== Proxies included in the official RC gem ====
     672
     673OMF RC has included some resource proxies to support network configuration and running OMF enabled applications. For more details, refer to OmfRc::ResourceProxy::AbstractResource.
     674
     675Application Proxy
     676Application 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.
     677
     678Logging
     679Given controlling logging is important for developing and debugging, we created a dedicated guide.
     680
     681Logging system in OMF 6
     682
     683Advanced topics
     684Organise resource proxy modules
     685Define inline
     686
     687If 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.
     688
     689Include resource proxy modules in the default package
     690
     691The default location of resource proxy definition files are located in the directory omf_rc/lib/omf_rc/resource_proxy.
     692
     693If 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
     694
     695To load these default resource proxies, simple call a load method provided by ResourceFactory class in your resource controller script (e.g. engine_control.rb)
     696
     697OmfRc::ResourceFactory.load_default_resource_proxies
     698Commit your definition files into the git repository and simply send us a pull request.
     699
     700Package your proxy definition files as OMF extension gem
     701
     702You could also package your proxy definition files into separate gems, if you feel they should not go into the default RC package.
     703
     704This process is rather simple, take a look at this third party rc gem of openflow integration.
     705
     706https://github.com/kohoumas/omf_rc_openflow
     707
     708Refactor common features into resource utilities
     709If a set of features can be shared among different types of resources, it is a good idea to refactor them into resource utilities.
     710
     711Take 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.
     712{{{
     713module OmfRc::Util::Throttle
     714  include OmfRc::ResourceProxyDSL
     715
     716  configure :throttle do |resource, value|
     717    resource.property.throttle = value.to_f / 100.0
     718  end
     719end
     720}}}
     721Then include this utility inside the engine resource proxy file by using:
     722{{{
     723utility :throttle
     724}}}
     725
     726You could also overwrite a property definition provided by the utility, by registering it again using the same name.
     727
     728=== Contributing to OMF ===
     729Ready to be part of OMF project? Please refer to [wiki:OMF/OMF60/1Developer/1Contributing Contributing] document for some guidelines.