Commit 428870e3 authored by Xavier Thompson's avatar Xavier Thompson

promise/plugin: Add check_socket_listening promise

This promise generalizes the check_port_listening promise to include
AF_UNIX sockets in addition to AF_INET and AF_INET6 host/port sockets.

The socket family and address is deduced from the set of arguments:
- `host` & `port`: AF_INET or AF_INET6
- `pathname`: filesystem pathname address for AF_UNIX
- `abstract`: '\0' is prefixed to build an abstract AF_UNIX address

All other combinations of arguments are forbidden.

The promise creates a socket accordingly and attempts to connect to the
provided address. The promise fails if the connection fails.

The check_port_listening promise has been removed.

See merge request nexedi/slapos.toolbox!97
parent b0c5cb3f
from zope.interface import implementer
from slapos.grid.promise import interface
from slapos.grid.promise.generic import GenericPromise
import socket
import sys
@implementer(interface.IPromise)
class RunPromise(GenericPromise):
def __init__(self, config):
super(RunPromise, self).__init__(config)
# check port is listening at least every 2 minutes
self.setPeriodicity(minute=2)
def sense(self):
"""
Simply test if we can connect to specified host:port.
"""
hostname = self.getConfig('hostname')
# if type of port should be int or str, unicode is not accepted.
port = int(self.getConfig('port'))
addr = (hostname , port)
# in case of any error, we call "logger.error"
# note that we could simply let the function raise an error, it would have the same effect
# in this case the following code would be enough:
# socket.create_connection(addr).close()
# self.logger.info("port connection OK")
try:
socket.create_connection(addr).close()
except (socket.herror, socket.gaierror) as e:
self.logger.error("ERROR hostname/port ({}) is not correct: {}".format(addr, e))
except (socket.error, socket.timeout) as e:
self.logger.error("ERROR while connecting to {}: {}".format(addr, e))
else:
self.logger.info("port connection OK ({})".format(addr))
# no need to define Test as it is the default implementation
#def test(self):
# """
# Test is failing if last sense was bad.
# """
# return self._test(result_count=1, failure_amount=1)
def anomaly(self):
"""
There is an anomaly if last 3 senses were bad.
"""
return self._anomaly(result_count=3, failure_amount=3)
from zope.interface import implementer
from slapos.grid.promise import interface
from slapos.grid.promise.generic import GenericPromise
import socket
ADDRESS_USAGE = (
"Address must be specified in 1 of the following 3 forms:"
" (host, port), path or abstract")
@implementer(interface.IPromise)
class RunPromise(GenericPromise):
def __init__(self, config):
super(RunPromise, self).__init__(config)
self.setPeriodicity(minute=2)
self.result_count = int(self.getConfig('result-count', 3))
self.failure_amount = int(self.getConfig('failure-amount', 3))
def sense(self):
"""
Check the state of a socket.
"""
host = self.getConfig('host')
port = self.getConfig('port')
path = self.getConfig('pathname')
abstract = self.getConfig('abstract')
if host:
if path or abstract or not port:
self.logger.error(ADDRESS_USAGE)
return
family, _, _, _, addr = socket.getaddrinfo(host, port)[0]
else:
if bool(path) == bool(abstract):
self.logger.error(ADDRESS_USAGE)
return
family = socket.AF_UNIX
addr = path or '\0' + abstract
s = socket.socket(family, socket.SOCK_STREAM)
try:
s.connect(addr)
except socket.error as e:
self.logger.error('%s: %s', type(e).__name__, e)
else:
self.logger.info("socket connection OK %r", addr)
finally:
s.close()
def anomaly(self):
"""
By default, there is an anomaly if last 3 senses were bad.
"""
return self._anomaly(result_count=self.result_count, failure_amount=self.failure_amount)
# -*- coding: utf-8 -*-
##############################################################################
# Copyright (c) 2018 Vifib SARL and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly advised to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
import os
import socket
import time
from slapos.grid.promise import PromiseError
from slapos.promise.plugin.check_socket_listening import RunPromise
from . import TestPromisePluginMixin
SLAPOS_TEST_IPV4 = os.environ.get('SLAPOS_TEST_IPV4', '127.0.0.1')
SLAPOS_TEST_IPV6 = os.environ.get('SLAPOS_TEST_IPV6', '::1')
class TestCheckSocketListening(TestPromisePluginMixin):
promise_name = "check-socket-listening.py"
promise_template = "from %s import %s\nextra_config_dict = %%r\n" % (
RunPromise.__module__, RunPromise.__name__)
def writePromise(self, **kw):
super(TestCheckSocketListening, self).writePromise(
self.promise_name, self.promise_template % kw)
def test_input_conflict(self):
for keys in (
(), ('port',),
('host',), ('host', 'pathname'), ('host', 'abstract'),
('host', 'abstract', 'pathname'), ('pathname', 'abstract')):
self.writePromise(**dict.fromkeys(keys, 'a'))
self.configureLauncher(force=True)
self.assertRaises(PromiseError, self.launcher.run)
def test_host_port(self):
for host, family in ((SLAPOS_TEST_IPV4, socket.AF_INET),
(SLAPOS_TEST_IPV6, socket.AF_INET6)):
s = socket.socket(family)
try:
s.bind((host, 0))
port = s.getsockname()[1]
self.writePromise(host=host, port=str(port))
self.configureLauncher(force=True)
self.assertRaises(PromiseError, self.launcher.run)
s.listen(5)
time.sleep(1)
self.launcher.run()
finally:
s.close()
def test_unix_socket(self):
name = os.path.join(self.partition_dir, 'test_unix_socket')
for addr, abstract in ((name, False), (name, True)):
s = socket.socket(socket.AF_UNIX)
try:
if abstract:
s.bind('\0' + addr)
self.writePromise(abstract=addr)
else:
s.bind(addr)
self.writePromise(pathname=addr)
self.configureLauncher(force=True)
self.assertRaises(PromiseError, self.launcher.run)
s.listen(5)
time.sleep(1)
self.launcher.run()
finally:
s.close()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment