Difference between revisions of "Cfengine3 Messaging"

From PostgreSQL_wiki
Jump to: navigation, search
(cf-messaged)
(cf-messaged)
Line 103: Line 103:
 
The output would be:
 
The output would be:
 
<pre>
 
<pre>
  +
Received request:
Received request: ['/root/client-synchronous-request-response.py', '{', 'host', ':', '{', 'uqhost', ':', 'x2gvdt0001', ',', 'domain', ':', 'webhuis.nl', ',', 'role', ':', 'tstutl', '}', '}']
 
  +
['/root/client-synchronous-request-response.py',
  +
'{', 'host', ':',
  +
'{', 'uqhost', ':', 'x2gvdt0001', ',',
  +
'domain', ':', 'webhuis.nl', ',',
  +
'role', ':', 'tstutl',
  +
'}',
  +
'}']
 
</pre>
 
</pre>
 
This is work in progress, the Puthon script does not yet treat the data container very well. When a message is received the daemon places the message in a local queue for processing.
 
This is work in progress, the Puthon script does not yet treat the data container very well. When a message is received the daemon places the message in a local queue for processing.

Revision as of 11:12, 12 December 2015

> what would be killer is a combination of the latest cfengine
> (with promise theory) and 0mq.

Source: http://lists.zeromq.org/pipermail/zeromq-dev/2011-February/009491.html

This part of the Webhuis CFEngine initiative is research and therefore subject to change. :-)
It is the objective to have messaging incorporated in CFEngine and thus enabling nodes, by cf-agent that is, to send out messages to whatever listeners out there. The listeners can be cf-messages daemons or other nodes configured to process messages delivered by CFEngine Nodes.
CFEngine will need to implement a new promise type: messages:.
Parts of the works is to be transferred to and discussions will take place on:

https://github.com/Webhuis/Data

So let's go and deliver that kill!

Nodes and messaging

When adding messages as a feature to CFEngine different positions with regards to messages exist.

  • Nodes carrying information they are willing to share
  • Nodes willing to process the information Nodes are willing to share

Nodes sharing information

Information sharing nodes need a method that enables them to send the information they are willing to share. In the analysis use cases of types of messages have to be mapped on the basic patterns. The listeners mirror the nodes mappings. Nodes carry different kinds of information

  • Common information
  • Static information
  • Dynamic Information
  • State Information

In the current design of CFEngine the cf-agent then has to be extended with a promise type: messages:

bundle agent cf_message {

vars:

# Webhuis CFEngine nodes always have a role, the role is set in a variable for reasons of simplicity.

  "role"        string => "tstutl";

  "host_info"     data => parsejson('
      {
         "host": { "uqhost": "$(sys.uqhost)", "domain": "$(sys.domain)", "role": "$(role)" },
      }');

  "host_info_index"  slist => getindices(host_info);
  "host_store"      string => storejson(host_info);

commands:

# We do not have the messages: promise-type yet, so have to invoke Python (or C within a short period of time).

  "/usr/bin/python /root/client-synchronous-request-response.py ${host_store}";

reports:

  "${this.bundle} host_store: ${host_store}";

}

body common control {

bundlesequence => { cf_message };

inputs => { "/var/cfengine/inputs/lib/3.6/stdlib.cf" };

}

Templates, Schemas or ?

Messages need to be structured.

  • Identity
  • Version
  • Envelope
  • Encryption
  • Signature

With CFEngine we have mechanisms in place for editing templates.

messages:

The messages promise type is an abstract messaging method. It enables the agent to send messages to whatever listener is around in the Universe. The data type of a message is a container, and the stucture is:

{
  "message_schema_id": {
    "message_id": "$(message_id)",
    "content": "$(content)",
    "version": "$(version)"
  }
}

Information processing Nodes

Information processing Nodes for their part may be willing to share their information through views.

cf-messaged

import time
import zmq

context = zmq.Context()
socket = context.socket(zmq.REP)
socket.bind("tcp://10.22.23.184:5555")

while True:
    #  Wait for next request from client
    message = socket.recv()
    print("Received request: %s " % message)

    #  Do some 'work'
    time.sleep(1)

    #  Send reply back to client
    socket.send( message)

The output would be:

Received request:
['/root/client-synchronous-request-response.py',
 '{', 'host', ':',
 '{', 'uqhost', ':', 'x2gvdt0001', ',',
 'domain', ':', 'webhuis.nl', ',',
 'role', ':', 'tstutl',
 '}',
 '}'] 

This is work in progress, the Puthon script does not yet treat the data container very well. When a message is received the daemon places the message in a local queue for processing.

0mq

CFEngine Enterprise offers messaging to some degree. Nodes share information they hold with the Policy Hub. This information can be

  • Patches of installed software
  • State of the Nodes with regards to promises fulfilled
  • Configuration information (CMDB)

Outside this context, however, there is no method available to the agent to share any information it is willing to share, with a monitoring system for instance.
There are many messaging systems around and CFEngine should be able to interface with any of them, but in the initial stages the need is for a very light weight messaging system. The search for a suitable messaging mechanism eventually led to 0mq, just a library not an application. Could it be less in weight?
For reasons of simplicity the code examples, so far are in Python. CFEngine is independent of anything but libc and thus the end results of CFEngine messaging have to be developed in C.

Information sources

Basic patterns

ZeroMQ comes with five basic patterns

  • Synchronous Request/Response
  • Asynchronous Request/Response
  • Publish/Subscribe
  • Push/Pull
  • Exclusive Pair

Clients and servers can take the role of server, being the one that is listening on a certain socket, IP address and port number, to incoming requests. When it comes to exchanging messages the most stable one is chosen to be the listener.


Synchronous Request/Response

This is the client server model and the most basic pattern. The client sends a request to the server and the server replies to the request. Other names are Request Reply

Sample Message

Example Code

Client Requests

socket = context.socket(zmq.REQ)
socket.connect("tcp://wbhs-pkg.webhuis.nl:8080")

#  Do 10 requests, waiting each time for a response
print("Sending request %s ..." % request)
socket.send(b"Hello")

#  Get the reply.
message = socket.recv()
print("Received reply %s [ %s ]" % (request, message))

Server Replies

socket = context.socket(zmq.REP)
socket.bind("tcp://10.68.71.184:8080")

#  Wait for next request from client
message = socket.recv()
print("Received request: %s" % message)

#  Send reply back to client
socket.send(b"World")

Asynchronous Request/Response

Example Code

Publish/Subscribe

This is comparable to broadcasting.

Example Code

Server publishes

ctx = zmq.Context()
publisher = ctx.socket(zmq.PUB)

publisher.bind("tcp://10.68.71.184:5556")

time.sleep(5)

sequence = 0
id = 0
data =1000

while sequence < 20:
  id += 1;
  data += 1000;
  publisher.send("%i %i %i" % (sequence, id, data));
  sequence += 1

Client subscriber receives

context = zmq.Context()
socket = context.socket(zmq.SUB)

print("Collecting updates from server...")
socket.connect("tcp://10.68.71.184:5556")

socket.setsockopt(zmq.SUBSCRIBE, b'')

for update_nbr in range(5):
    string = socket.recv_string()
    print string

Output

0 1 2000
1 2 3000
2 3 4000
3 4 5000
4 5 6000

Push/Pull

In the official 0mq documentation this pattern is referred to as Parallel Pipeline.

Example Code

# The ventilator
# Binds PUSH socket to tcp://localhost:5557
# Sends batch of tasks to workers via that socket

import zmq
import random
import time

context = zmq.Context()

# Socket to send messages on
sender = context.socket(zmq.PUSH)
sender.bind("tcp://10.68.71.184:5557")

# Socket with direct access to the sink: used to syncronize start of batch
sink = context.socket(zmq.PUSH)
sink.connect("tcp://10.68.71.184:5558")

try:
    raw_input
except NameError:
    # Python 3
    raw_input = input

print("Press Enter when the workers are ready: ")
_ = raw_input()
print("Sending tasks to workers...")

# The first message is "0" and signals start of batch
sink.send(b'0')

# Initialize random number generator
random.seed()

# Send 100 tasks
total_msec = 0
for task_nbr in range(100):

    # Random workload from 1 to 100 msecs
    workload = random.randint(1, 100)
    total_msec += workload

    sender.send_string(u'%i' % workload)

print("Total expected cost: %s msec" % total_msec)

# Give 0MQ time to deliver
time.sleep(1)

# Worker
# Connects PULL socket to tcp://10.68.71.184:5557
# Collects workloads from ventilator via that socket
# Connects PUSH socket to tcp://10.68.71.184:5558
# Sends results to sink via that socket

import sys
import time
import zmq


context = zmq.Context()

# Socket to receive messages on
receiver = context.socket(zmq.PULL)
receiver.connect("tcp://10.68.71.184:5557")

# Socket to send messages to
sender = context.socket(zmq.PUSH)
sender.connect("tcp://10.68.71.184:5558")

# Process tasks forever
while True:
    s = receiver.recv()

    # Simple progress indicator for the viewer
    sys.stdout.write('.')
    sys.stdout.flush()

    # Do the work
    time.sleep(int(s)*0.001)

    # Send results to sink
    sender.send(b'')

# Sink
# Binds PULL socket to tcp://10.68.71.184:5558
# Collects results from worker via that socket

import sys
import time
import zmq


context = zmq.Context()

# Socket to receive messages on
receiver = context.socket(zmq.PULL)
receiver.bind("tcp://10.68.71.184:5558")

# Wait for start of batch
s = receiver.recv()

# Start our clock now
tstart = time.time()

# Process 100 confirmations
total_msec = 0
for task_nbr in range(100):
    s = receiver.recv()
    if task_nbr % 10 == 0:
        sys.stdout.write(':')
    else:
        sys.stdout.write('.')
    sys.stdout.flush()

# Calculate and report duration of batch
tend = time.time()
print("Total elapsed time: %d msec" % ((tend-tstart)*1000))

Exclusive Pair

Example Code

exclusive-bind
import zmq

# ZeroMQ Context
context = zmq.Context()

# Define the socket using the "Context"
socket = context.socket(zmq.PAIR)
socket.bind("tcp://127.0.0.1:5696")
import zmq

# ZeroMQ Context
context = zmq.Context()

# Define the socket using the "Context"
socket = context.socket(zmq.PAIR)
socket.connect("tcp://127.0.0.1:5696")

Return to: Cfengine