Skip to content

Commit

Permalink
(python3 support) Fix support to new way of sending inventories
Browse files Browse the repository at this point in the history
In medulla we removed the inventory server. Now we have a xmpp server on
the package-server.

(cherry picked from commit 784fc10)
(cherry picked from commit b93dc01)
  • Loading branch information
neoclust committed Feb 19, 2024
1 parent 91ac727 commit 218932a
Show file tree
Hide file tree
Showing 16 changed files with 495 additions and 152 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

## [5.0.1](https://github.com/medulla-tech/medulla/releases/tag/5.0.1) (unreleased)
- [FEATURE] Add auto-refresh on the deployment page
- [FEATURE] Make the imaging inventory use xmpp to send inventory.
- [BUGFIX] Fix Translations
- [BUGFIX] Fix display of the inventory of a machine when it is offline
- [BUGFIX] Fix creating group from a boolean
Expand Down
1 change: 1 addition & 0 deletions debian/pulse2-package-server.install
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
usr/sbin/pulse2-package-server
usr/sbin/pulse2-package-server-register-imaging
usr/sbin/message-sender.py
usr/lib/python3/*-packages/pulse2/package_server
etc/mmc/pulse2/package-server
etc/mmc/pulse2/atftpd/pcre.conf
Expand Down
2 changes: 1 addition & 1 deletion services/bin/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ dist_sbin_SCRIPTS = pulse2-package-server pulse2-package-server-register-imaging
pulse2-packageparser.py pulse2-setup pulse2-load-defaults \
pulse2-extract-glpi-search-options \
pulse2-backup-servers pulse2-debug pulse2-dbupdate pulse2-migration_old_package.py\
pulse2-msc-clean-database pulse2-inventory-clean-database \
pulse2-msc-clean-database pulse2-inventory-clean-database message-sender.py\
pulse2-collect-info pulse2-create-group pulse2-register-pxe.py \
pulse2-connect-machine-backuppc pulse2-disconnect-machine-backuppc \
restart-pulse-services pulse2-inscription_packages_in_base.py pulse2-generation_package.py \
Expand Down
180 changes: 180 additions & 0 deletions services/bin/message-sender.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#!/usr/bin/python3
# -*- coding: utf-8; -*-
# SPDX-FileCopyrightText: 2016-2023 Siveo <support@siveo.net>
# SPDX-License-Identifier: GPL-3.0-or-later

import sys

from slixmpp import ClientXMPP
from slixmpp import jid
from slixmpp.xmlstream import handler, matcher
from slixmpp.xmlstream.stanzabase import ET
from slixmpp.exceptions import *
import slixmpp
import asyncio
import os
import logging
from argparse import ArgumentParser

logger=logging.getLogger('message-sender')
# Additionnal path

class MyXMPPClient(ClientXMPP):
def __init__(self,
jid,
password,
message,
timeout=20,
toagent="master@pulse",
ipv4="127.0.0.1",
port=5222,):
super().__init__(jid, password)
self.stanzamessage = message
self.timeout =timeout
self.toagent =toagent
self.ipv4 = ipv4
self.port = port

self.add_event_handler("session_start", self.start)
self.add_event_handler("message", self.message)
self.add_event_handler("register", self.register)
self.add_event_handler("connecting", self.handle_connecting)
self.add_event_handler("connection_failed", self.connection_failed)
self.add_event_handler("disconnected", self.handle_disconnected)
self.add_event_handler("connected", self.handle_connected)


async def start(self, event):
self.send_presence()
await self.get_roster()
# We're only concerned about registering, so nothing more to do here.
self.send_message( mto=self.toagent, mbody=self.stanzamessage, mtype="chat")
self.disconnect()


async def message(self, msg):
# msg["body"]
# msg["type"]
# msg["from"]
logging.debug("Message : %s"%msg["body"])


async def stop(self):
logger.debug("Stopping XMPP client...")
self.disconnect()


def handle_connecting(self, data):
"""
success connecting agent
"""
logger.debug("Connected")

def connection_failed(self, data):
"""
on connection failed on libere la connection
"""
logger.debug("Failed to connect to %s : %s"%(self.boundjid, data))
self.disconnect()


def handle_disconnected(self, data):
logger.debug("Disconnected")


def handle_connected(self, data):
"""
success connecting agentconnect(
"""
logger.debug("connected to %s"%(self.boundjid))


async def register(self, iq):
"""
Fill out and submit a registration form.
The form may be composed of basic registration fields, a data form,
an out-of-band link, or any combination thereof. Data forms and OOB
links can be checked for as so:
if iq.match('iq/register/form'):
# do stuff with data form
# iq['register']['form']['fields']
if iq.match('iq/register/oob'):
# do stuff with OOB URL
# iq['register']['oob']['url']
To get the list of basic registration fields, you can use:
iq['register']['fields']
"""
resp = self.Iq()
resp['type'] = 'set'
resp['register']['username'] = self.boundjid.user
resp['register']['password'] = self.password
try:
await resp.send()
logger.info("Account created for %s!" % self.boundjid)
except IqError as e:
logger.error("Could not register account: %s" %
e.iq['error']['text'])
self.disconnect()
except IqTimeout:
logger.error("No response from server.")
self.disconnect()


async def main():
parser = ArgumentParser()
parser.add_argument("-P", "--port", dest="port", type=int, default=5222, help="port utilisé (par défaut: 5222)")
parser.add_argument("-I", "--ipv4", dest="ipv4", default="127.0.0.1", help="adresse IPv4 de destination")
parser.add_argument("-j", "--jid", dest="jid", help="JID à utiliser")
parser.add_argument("-p", "--password", dest="password", help="mot de passe à utiliser")
parser.add_argument("-m", "--message", dest="message", help="message envoyé")
parser.add_argument("-T", "--timeout", dest="timeout", type=int, default=5, help="timeout en secondes (par défaut: 5)")
parser.add_argument("-t", "--toagent", dest="toagent", default="master@pulse", help="agent destinataire du message)")
parser.add_argument("-L", "--levellog", dest="levellog", default="DEBUG", help="niveau de journalisation (par défaut: DEBUG)")
parser.add_argument("-F", "--filelog", dest="filelog", default="/var/log/pulse2/message-sender.log", help="fichier de journal (par défaut: /var/log/pulse2/message-sender.log)")
args = parser.parse_args()

if len(sys.argv) == 1:
parser.print_help()
sys.exit(0)
log_dir = os.path.dirname(args.filelog)
print (log_dir)
if not os.path.exists(log_dir):
os.makedirs(log_dir)

logging.basicConfig(level=args.levellog.upper(), format='%(levelname)-8s %(message)s', filename=args.filelog)
#self.logger = logging.getLogger('message-sender')
if sys.platform.startswith("linux") and os.getuid() != 0:
logging.error("Agent must be running as root")
sys.exit(0)
elif sys.platform.startswith("win") and isWinUserAdmin() == 0:
logging.error("Medulla agent must be running as Administrator")
sys.exit(0)
elif sys.platform.startswith("darwin") and not isMacOsUserAdmin():
logging.error("Medulla agent must be running as root")
sys.exit(0)
#xmpp = MyXMPPClient(jid, password, message, timeout, toagent, ipv4, port)
xmpp = MyXMPPClient(args.jid, args.password, args.message, args.timeout, args.toagent,args.ipv4,args.port)
xmpp.register_plugin("xep_0030") # Service Discovery
xmpp.register_plugin("xep_0045") # Multi-User Chat
xmpp.register_plugin("xep_0004") # Data Forms
xmpp.register_plugin("xep_0050") # Adhoc Commands
xmpp.register_plugin(
"xep_0199",
{"keepalive": True, "frequency": 600, "interval": 600, "timeout": 500},
)
xmpp.register_plugin("xep_0077") # In-band Registration
xmpp["xep_0077"].force_registration = True
# Démarre le client XMPP dans une boucle asyncio

xmpp.connect()
# Temporisateur pour déclencher l'événement après 10 secondes
await asyncio.sleep(10)
# Déclenche l'événement d'arrêt pour déconnecter proprement le client XMPP
await xmpp.stop()


if __name__ == "__main__":
asyncio.run(main())
16 changes: 8 additions & 8 deletions services/bin/pulse2-register-pxe.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,12 +417,11 @@ def parsejsoninventory(file, file_content):
def senddata(query, ip="127.0.0.1", port=1001):
adresse = (ip, port)
monSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
logging.getLogger().debug("Send PXE xml for registration :%s" % query)
monSocket.sendto("\xBB%s" % query, adresse)
logging.getLogger().error("Send PXE xml for registration :%s" % query.encode('utf-8'))
monSocket.sendto(bytes("\xBB%s"%query, "utf-8"), adresse)
time.sleep(conf["pxe_timesenddata"])
monSocket.sendto("\xBA%s" % query, adresse)
monSocket.sendto(bytes("\xBA%s"%query, "utf-8"), adresse)
time.sleep(conf["pxe_timesenddata"])
monSocket.sendto("\xBA%s" % query, adresse)
monSocket.close()


Expand Down Expand Up @@ -462,11 +461,12 @@ def macadressclear(file_content, interface_mac_clear):
logging.getLogger().debug(
"Clear interface with macadress %s " % interface_mac_clear
)
xml_str = ET.tostring(root).encode("ASCII", "ignore")
logging.getLogger().debug("New xml netwrok : %s" % xml_str)
xml_str = ET.tostring(root)
logging.getLogger().debug("New xml network : %s" % xml_str)
xml_str = xml_str.decode("utf-8")
xml_str = xml_str.replace("\n", "")

return xml_str
pass
return file_content


Expand Down Expand Up @@ -524,7 +524,6 @@ def process_IN_DELETE(self, event):

def process_IN_MODIFY(self, event):
logging.getLogger().debug("MODIFY event: %s" % event.pathname)
time.sleep(0.025)
self.traitement(event.pathname)

def process_IN_OPEN(self, event):
Expand Down Expand Up @@ -555,6 +554,7 @@ def traitement(self, name):
header = '<?xml version="1.0" encoding="utf-8"?>'
file_content = file_content[:-10]
xmldata = "%s%sMc:%s</REQUEST>" % (header, file_content, mac)

logging.getLogger().debug(
"XML recv from pxe client %s" % xmldata
)
Expand Down
10 changes: 10 additions & 0 deletions services/conf/pulse2/package-server/package-server.ini
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ host = 0.0.0.0
# The public IP send when a client agent is wondering how to reach this package server, set to the "host" parameter value if not defined
public_ip =
public_mask = 255.255.255.0

[connection]
jid =
password =
recipient = master_inv@pulse
server=127.0.0.1
port=5222
timeout=40
domain =

#
# --------
# PACKAGES
Expand Down
4 changes: 3 additions & 1 deletion services/mmc/plugins/imaging/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3935,6 +3935,9 @@ def injectInventory(self, imaging_server_uuid, computer_uuid, inventory):
except Exception as e:
logging.getLogger().exception(e)
ret = [False, str(e)]

self.synchroComputer(computer_uuid)

return ret

def getPartitionsToBackupRestore(self, computer_uuid):
Expand Down Expand Up @@ -4286,7 +4289,6 @@ def treatRegister(
logger.error("couldn't initialize the ImagingApi to %s" % (url))

distinct_loc = xmlrpcCleanup(distinct_loc)
# if defer_list == False:
if len(defer_list) == 0:
distinct_locs = distinct_loc
keyvaleur = list(distinct_loc.keys())
Expand Down
69 changes: 33 additions & 36 deletions services/pulse2/database/imaging/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1535,7 +1535,7 @@ def __fillMenuItem(self, session, mi, menu_id, params):
def __addMenuDefaults(self, session, menu, mi, params):
is_menu_modified = False
# if 'default' in params and params['default']:
if "default" in params and "default" in params and params["default"]:
if "default" in params and params["default"]:
is_menu_modified = True
menu.fk_default_item = mi.id
# if 'default_WOL' in params and params['default_WOL']:
Expand Down Expand Up @@ -5896,17 +5896,33 @@ def injectInventory(self, imaging_server_uuid, computer_uuid, inventory):
Inject a computer inventory into the dabatase.
For now only the ComputerDisk and ComputerPartition tables are used.
"""

if not isUUID(imaging_server_uuid):
raise TypeError("Bad imaging server UUID: %s" % imaging_server_uuid)
if not isUUID(computer_uuid):
raise TypeError("Bad computer UUID: %s" % computer_uuid)
session = create_session()
session.begin()

locationServerImaging = self.getLocationImagingServerByServerUUID(
imaging_server_uuid
)
target = None
session.query(Target).filter_by(uuid=computer_uuid).delete()
menu = self.getEntityDefaultMenu("UUID%s" % locationServerImaging)
new_menu = self.__duplicateMenu(
session, menu, "UUID%s" % locationServerImaging, None, False
)
target = Target()
target.fk_menu = new_menu.id
target.is_registered_in_package_server = 1
new_menu.fk_synchrostate = 1
target.name = inventory["shortname"]
target.uuid = computer_uuid
target.type = 1
target.fk_entity = locationServerImaging

try:
# First remove old computer inventory
target = session.query(Target).filter_by(uuid=computer_uuid).one()
for current_disk in target.disks:
session.delete(current_disk)
# Then push a new inventory
if "disk" in inventory:
for disknum in inventory["disk"]:
Expand All @@ -5926,44 +5942,25 @@ def injectInventory(self, imaging_server_uuid, computer_uuid, inventory):
cp.start = int(part["start"])
cd.partitions.append(cp)
target.disks.append(cd)
locationServerImaging = self.getLocationImagingServerByServerUUID(
imaging_server_uuid
)
target.fk_entity = locationServerImaging

self.logger.debug(
"Attribution location %s for computer %s"
% (target.fk_entity, target.name)
)
session.add(target)
session.commit()
except InvalidRequestError as e:
session.rollback()
if hasattr(e, "message"):
if e.message == "No rows returned for one()":
self.logger.warn(
"Can't get the computer %s, we can't inject an inventory. This happen when the computer exists in the backend but is not declared in the imaging."
% (computer_uuid)
)
return [
False,
"Can't get the computer %s, we can't inject an inventory. This happen when the computer exists in the backend but is not declared in the imaging."
% (computer_uuid),
]
elif e == "No rows returned for one()":
self.logger.warn(
"Can't get the computer %s, we can't inject an inventory. This happen when the computer exists in the backend but is not declared in the imaging."
% (computer_uuid)
)
return [
False,
"Can't get the computer %s, we can't inject an inventory. This happen when the computer exists in the backend but is not declared in the imaging."
% (computer_uuid),
]
else:
raise
except BaseException:
# except InvalidRequestError as e:
except Exception as e:
session.rollback()
raise
self.logger.warn(
"Can't get the computer %s, we can't inject an inventory. This happen when the computer exists in the backend but is not declared in the imaging."
% (computer_uuid)
)
return [
False,
"Can't get the computer %s, we can't inject an inventory. This happen when the computer exists in the backend but is not declared in the imaging."
% (computer_uuid),
]
session.close()
return [True, True]

Expand Down
Loading

0 comments on commit 218932a

Please sign in to comment.