diff --git a/common/src/foundation/python-packages/pyproject.toml b/common/src/foundation/python-packages/pyproject.toml index cfd8d45b6..9c5f3bc72 100644 --- a/common/src/foundation/python-packages/pyproject.toml +++ b/common/src/foundation/python-packages/pyproject.toml @@ -108,5 +108,5 @@ cryptography = { appliances = ["server"] } jinja2 = { appliances = ["server"], bootstrap = true } markupsafe = { appliances = ["server"] } pycparser = { appliances = ["server"] } -pyyaml = { appliances = ["server"], bootstrap = true } +pyyaml = { appliances = ["server", "client"], bootstrap = true } six = { appliances = ["server"] } diff --git a/common/src/stack/command/stack/commands/sync/host/config/plugin_smq.py b/common/src/stack/command/stack/commands/sync/host/config/plugin_smq.py new file mode 100644 index 000000000..ffde40358 --- /dev/null +++ b/common/src/stack/command/stack/commands/sync/host/config/plugin_smq.py @@ -0,0 +1,30 @@ +import stack.commands +from pathlib import Path +from stack.util import _exec + +class Plugin(stack.commands.Plugin): + """ + Sync smq settings to hosts and restart all smq related services + """ + + def provides(self): + return 'smq' + + def requires(self): + return [] + + def run(self, args): + hosts = args['hosts'] + settings_file = Path('/opt/stack/etc/stacki.yml') + services = ['smq-processor', 'smq-producer', 'smq-publisher', 'smq-shipper'] + + for host in hosts: + self.owner.notify('Sync SMQ Settings') + if settings_file.is_file(): + copy_settings = _exec(f'scp {settings_file} {host}:{settings_file}', shlexsplit=True) + if copy_settings.returncode != 0: + self.owner.notify(f'Failed to copy settings file to {host}:{copy_settings.stderr}') + for service in services: + restart_smq = _exec(f'ssh -t {host} "systemctl restart {service}" ', shlexsplit=True) + if restart_smq.returncode != 0: + self.owner.notify(f'Failed to restart service {service} on host {host}') diff --git a/common/src/stack/mq/pylib/mq/__init__.py b/common/src/stack/mq/pylib/mq/__init__.py index 882075769..3b84448c9 100644 --- a/common/src/stack/mq/pylib/mq/__init__.py +++ b/common/src/stack/mq/pylib/mq/__init__.py @@ -10,6 +10,8 @@ import socket import sys import os +import yaml +from pathlib import Path # We need the "ports" class when we are inside the installer, but don't need to # setup and zmq based services. The alternative is to add zmq and dependencies @@ -23,18 +25,49 @@ -class ports: +class PortsMeta(type): """ Socket port numbers used by the Stack Message Queue daemons. - :var publish: UDP socket service for publishing a message - :var subscribe: zmq.SUB socket for subscribing to a channel - :var control: TCP socket service for enabling/disabling channel propagation + :publish: UDP socket service for publishing a message + :subscribe: zmq.SUB socket for subscribing to a channel + :control: TCP socket service for enabling/disabling channel propagation """ - publish = 5000 - subscribe = 5001 - control = 5002 + # Stacki settings file location + # and default ports + SETTINGS = Path('/opt/stack/etc/stacki.yml') + pub_port = 5000 + sub_port = 5001 + con_port = 5002 + + def load_settings(cls): + config = {} + if cls.SETTINGS.is_file(): + with cls.SETTINGS.open() as f: + config = yaml.safe_load(f) + return config + + @property + def publish(cls): + config = cls.load_settings() + port = int(config.get('smq.pub.port', cls.pub_port)) + return port + + @property + def subscribe(cls): + config = cls.load_settings() + port = int(config.get('smq.sub.port', cls.sub_port)) + return port + + @property + def control(cls): + config = cls.load_settings() + port = int(config.get('smq.control.port', cls.con_port)) + return port + +class ports(metaclass=PortsMeta): + pass class Message(): """ @@ -42,7 +75,7 @@ class Message(): A Message is composed of header fields and the *message* text body. For many applications only body is manipulated and other fields are controlled by - lower software levels. + lower software levels. For simple Messages the *message* body can be a string. For more complex Messages the body should be a json encoded python dictionary. @@ -87,7 +120,7 @@ def __init__(self, payload=None, *, message=None, channel=None, hops=None, ttl=N # JSON does not override parameters, this allows loading an # existing Message and overwriting some of the fields - + self.channel = channel if channel else msg.get('channel') self.id = id if id else msg.get('id') self.payload = payload if payload else msg.get('payload') @@ -160,7 +193,7 @@ def getPayload(self): def setPayload(self, payload): """ Sets the payload text - + :param payload: text :type payload: string """ @@ -169,7 +202,7 @@ def setPayload(self, payload): def getHops(self): """ - :returns: number of software hops + :returns: number of software hops """ return self.hops @@ -189,7 +222,7 @@ def setSource(self, addr): """ Set the source host address. This address can be a hostname or an IP Address. - + :param addr: source address :type addr: string """ @@ -234,9 +267,9 @@ def setID(self, id): def addHop(self): """ Increments the hop count for the :class:`Message`. A hop is - defined as a software hop not a physical network hop. + defined as a software hop not a physical network hop. Every time an application receives and retransmits a message the - hop should be incremented. + hop should be incremented. This value is used to debugging. """ self.hops += 1 @@ -289,12 +322,12 @@ def unsubscribe(self, channel): :type channel: string """ self.sub.setsockopt_string(zmq.UNSUBSCRIBE, channel) - + def run(self): while True: try: channel, payload = self.sub.recv_multipart() - msg = Message(message=payload.decode(), + msg = Message(message=payload.decode(), channel=channel.decode()) except: continue @@ -341,10 +374,10 @@ def run(self): # # Note the callback() always pushes data as # stack.mq.Message objects. This is the only - # part of the code where we handle receiving + # part of the code where we handle receiving # unstructured data. # - # Design point here was to keep the clients + # Design point here was to keep the clients # simple so we don't need an API to write to # the message queue. diff --git a/common/src/stack/pylib/stack/settings.py b/common/src/stack/pylib/stack/settings.py index f16e30060..41292217e 100644 --- a/common/src/stack/pylib/stack/settings.py +++ b/common/src/stack/pylib/stack/settings.py @@ -35,6 +35,11 @@ # these settings control the starting points for discover-nodes (nee insert ethers) discovery.base.rack: '0' discovery.base.rank: '0' + +# smq ports +smq.pub.port: '5000' +smq.sub.port: '5001' +smq.control.port: '5002' ''' def get_settings(): diff --git a/redhat/nodes/backend.xml b/redhat/nodes/backend.xml index 4d26dd6d4..c34a23dcb 100644 --- a/redhat/nodes/backend.xml +++ b/redhat/nodes/backend.xml @@ -37,7 +37,7 @@ for o in stack.api.Call('list cart'): - + /opt/stack/bin/stacki-status.py Running preinstall @@ -50,11 +50,21 @@ include ld.so.conf.d/*.conf /opt/stack/bin/stacki-status.py install complete rebooting + +cat /opt/stack/etc/stacki.yml; + + + + + +import stack.settings + +stack.settings.write_default_settings_file(overwrite=True) cp /run/install/tmp/stack.conf /tmp/stack.conf - + diff --git a/sles/nodes/backend.xml b/sles/nodes/backend.xml index e38ed3d05..dbb125cd0 100644 --- a/sles/nodes/backend.xml +++ b/sles/nodes/backend.xml @@ -1,5 +1,5 @@ - + Handle for Backend Appliance @@ -56,6 +56,11 @@ rm -f /etc/zypp/repos.d/* &hostname; + + +cat /opt/stack/etc/stacki.yml; + + &hostname; @@ -69,6 +74,9 @@ rm -f /etc/zypp/repos.d/* &hostname; &hostname; &hostname; + +cat /opt/stack/etc/stacki.yml; + # # the above command (report host resolv) writes /etc/resolv.conf. make sure @@ -102,5 +110,5 @@ rm -rf /tmp/ipmisetup - +