From e20838c641a05d0ddd7c867dba764c3fe62c4bac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Mon, 18 Jun 2018 13:54:44 +0200 Subject: [PATCH 01/48] #750: Process to build OGAgent for ogLive package. --- oglive/Makefile | 57 +++++ oglive/build-packages.sh | 11 + oglive/debian/changelog | 6 + oglive/debian/compat | 1 + oglive/debian/control | 15 ++ oglive/debian/copyright | 26 ++ oglive/debian/docs | 1 + oglive/debian/ogagent-oglive.links | 2 + oglive/debian/ogagent-oglive.postinst | 21 ++ .../debian/ogagent-oglive.postinst.debhelper | 5 + oglive/debian/ogagent-oglive.postrm | 10 + oglive/debian/ogagent-oglive.postrm.debhelper | 12 + oglive/debian/ogagent-oglive.substvars | 2 + oglive/debian/ogagent.init | 23 ++ oglive/debian/rules | 44 ++++ oglive/debian/source/format | 1 + oglive/readme.txt | 3 + oglive/scripts/OGAgentTool | 6 + oglive/scripts/OGAgentTool-startup | 10 + oglive/scripts/ogagent | 6 + src/opengnsys/linux/operations.py | 12 +- src/opengnsys/oglive/__init__.py | 32 +++ src/opengnsys/oglive/daemon.py | 182 ++++++++++++++ src/opengnsys/oglive/operations.py | 230 ++++++++++++++++++ src/opengnsys/operations.py | 13 +- 25 files changed, 718 insertions(+), 13 deletions(-) create mode 100644 oglive/Makefile create mode 100755 oglive/build-packages.sh create mode 100644 oglive/debian/changelog create mode 100644 oglive/debian/compat create mode 100644 oglive/debian/control create mode 100644 oglive/debian/copyright create mode 100644 oglive/debian/docs create mode 100644 oglive/debian/ogagent-oglive.links create mode 100644 oglive/debian/ogagent-oglive.postinst create mode 100644 oglive/debian/ogagent-oglive.postinst.debhelper create mode 100644 oglive/debian/ogagent-oglive.postrm create mode 100644 oglive/debian/ogagent-oglive.postrm.debhelper create mode 100644 oglive/debian/ogagent-oglive.substvars create mode 100644 oglive/debian/ogagent.init create mode 100755 oglive/debian/rules create mode 100644 oglive/debian/source/format create mode 100644 oglive/readme.txt create mode 100644 oglive/scripts/OGAgentTool create mode 100644 oglive/scripts/OGAgentTool-startup create mode 100644 oglive/scripts/ogagent create mode 100644 src/opengnsys/oglive/__init__.py create mode 100644 src/opengnsys/oglive/daemon.py create mode 100644 src/opengnsys/oglive/operations.py diff --git a/oglive/Makefile b/oglive/Makefile new file mode 100644 index 0000000..832e2dc --- /dev/null +++ b/oglive/Makefile @@ -0,0 +1,57 @@ +#!/usr/bin/make -f +# -*- makefile -*- + +# Directories +SOURCEDIR := ../src +LIBDIR := $(DESTDIR)/usr/share/OGAgent +BINDIR := $(DESTDIR)/usr/bin +SBINDIR = $(DESTDIR)/usr/sbin +APPSDIR := $(DESTDIR)/usr/share/applications +CFGDIR := $(DESTDIR)/etc/ogagent +INITDIR := $(DESTDIR)/etc/init.d + +PYC := $(shell find $(SOURCEDIR) -name '*.py[co]') +CACHES := $(shell find $(SOURCEDIR) -name '__pycache__') + +clean: + rm -rf $(PYC) $(CACHES) $(DESTDIR) +install-ogagent: + rm -rf $(DESTDIR) + mkdir -p $(LIBDIR) + mkdir -p $(BINDIR) + mkdir -p $(SBINDIR) + mkdir -p $(APPSDIR) + mkdir -p $(CFGDIR) + + mkdir $(LIBDIR)/img + + # Cleans up .pyc and cache folders + rm -f $(PYC) $(CACHES) + + cp -r $(SOURCEDIR)/opengnsys $(LIBDIR)/opengnsys + cp -r $(SOURCEDIR)/cfg $(LIBDIR)/cfg + + # scripts + cp scripts/ogagent $(BINDIR) + cp scripts/OGAgentTool-startup $(BINDIR) + cp scripts/OGAgentTool $(BINDIR) + + # Fix permissions + chmod 755 $(BINDIR)/ogagent + chmod 755 $(BINDIR)/OGAgentTool-startup + chmod 600 $(LIBDIR)/cfg/ogagent.cfg + + # If for red hat based, copy init.d +ifeq ($(DISTRO),rh) + mkdir -p $(INITDIR) + cp debian/ogagent.init $(INITDIR)/ogagent + chmod +x $(INITDIR)/ogagent + ln -fs /usr/share/OGAgent/cfg/ogagent.cfg $(CFGDIR) + ln -fs /usr/share/OGAgent/cfg/ogclient.cfg $(CFGDIR) +endif + + # chmod 0755 $(BINDIR)/ogagent +uninstall: + rm -rf $(LIBDIR) + # rm -f $(BINDIR)/ogagent + rm -rf $(CFGDIR) diff --git a/oglive/build-packages.sh b/oglive/build-packages.sh new file mode 100755 index 0000000..4d5eb8d --- /dev/null +++ b/oglive/build-packages.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +cd $(dirname "$0") +top=`pwd` + +[ -r ../src/VERSION ] && VERSION="$(cat ../src/VERSION)" || VERSION="1.1.0" +RELEASE="1" + +# Debian based +dpkg-buildpackage -b -d + diff --git a/oglive/debian/changelog b/oglive/debian/changelog new file mode 100644 index 0000000..57a05ab --- /dev/null +++ b/oglive/debian/changelog @@ -0,0 +1,6 @@ +ogagent-oglive (1.1.1) unstable; urgency=medium + + * Initial release. + + -- Ramón M. Gómez Mon, 18 Jun 2018 13:00:00 +0200 + diff --git a/oglive/debian/compat b/oglive/debian/compat new file mode 100644 index 0000000..f11c82a --- /dev/null +++ b/oglive/debian/compat @@ -0,0 +1 @@ +9 \ No newline at end of file diff --git a/oglive/debian/control b/oglive/debian/control new file mode 100644 index 0000000..374bc55 --- /dev/null +++ b/oglive/debian/control @@ -0,0 +1,15 @@ +Source: ogagent-oglive +Section: admin +Priority: optional +Maintainer: Ramón M. Gómez +Build-Depends: debhelper (>= 7), po-debconf +Standards-Version: 3.9.2 +Homepage: https://opengnsys.es + +Package: ogagent-oglive +Section: admin +Priority: optional +Architecture: all +Depends: python-requests (>=0.8.2), python-six(>=1.1), python-prctl(>=1.1.1), python (>=2.7), libxss1, ${misc:Depends} +Description: OpenGnsys Agent for ogLive client + This package provides the required components to allow this machine to work on an environment managed by OpenGnsys. diff --git a/oglive/debian/copyright b/oglive/debian/copyright new file mode 100644 index 0000000..7b6ef31 --- /dev/null +++ b/oglive/debian/copyright @@ -0,0 +1,26 @@ +Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135 +Name: ogagent +Maintainer: Ramón M. Gómez +Source: https://opengnsys.es + +Copyright: 2014 Virtual Cable S.L.U. +License: BSD-3-clause + +License: GPL-2+ +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. +. +On Debian systems, the full text of the GNU General Public +License version 2 can be found in the file +`/usr/share/common-licenses/GPL-2'. diff --git a/oglive/debian/docs b/oglive/debian/docs new file mode 100644 index 0000000..b2b2a78 --- /dev/null +++ b/oglive/debian/docs @@ -0,0 +1 @@ +readme.txt diff --git a/oglive/debian/ogagent-oglive.links b/oglive/debian/ogagent-oglive.links new file mode 100644 index 0000000..9b970d7 --- /dev/null +++ b/oglive/debian/ogagent-oglive.links @@ -0,0 +1,2 @@ +/usr/share/OGAgent/cfg/ogagent.cfg /etc/ogagent/ogagent.cfg +/usr/share/OGAgent/cfg/ogclient.cfg /etc/ogagent/ogclient.cfg diff --git a/oglive/debian/ogagent-oglive.postinst b/oglive/debian/ogagent-oglive.postinst new file mode 100644 index 0000000..b59cfa6 --- /dev/null +++ b/oglive/debian/ogagent-oglive.postinst @@ -0,0 +1,21 @@ +#!/bin/sh + +. /usr/share/debconf/confmodule + +set -e +case "$1" in + configure) + chmod 600 /usr/share/OGAgent/cfg/ogagent.cfg + ;; + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 diff --git a/oglive/debian/ogagent-oglive.postinst.debhelper b/oglive/debian/ogagent-oglive.postinst.debhelper new file mode 100644 index 0000000..e75924d --- /dev/null +++ b/oglive/debian/ogagent-oglive.postinst.debhelper @@ -0,0 +1,5 @@ +# Automatically added by dh_installinit +if [ -x "/etc/init.d/ogagent" ]; then + update-rc.d ogagent defaults >/dev/null || exit $? +fi +# End automatically added section diff --git a/oglive/debian/ogagent-oglive.postrm b/oglive/debian/ogagent-oglive.postrm new file mode 100644 index 0000000..a46fa48 --- /dev/null +++ b/oglive/debian/ogagent-oglive.postrm @@ -0,0 +1,10 @@ +#!/bin/sh -e + +. /usr/share/debconf/confmodule + +set -e + +if [ "$1" = "purge" ] ; then + rm -rf /usr/share/OGAgent || true > /dev/null 2>&1 +fi + diff --git a/oglive/debian/ogagent-oglive.postrm.debhelper b/oglive/debian/ogagent-oglive.postrm.debhelper new file mode 100644 index 0000000..3167f1f --- /dev/null +++ b/oglive/debian/ogagent-oglive.postrm.debhelper @@ -0,0 +1,12 @@ +# Automatically added by dh_installinit +if [ "$1" = "purge" ] ; then + update-rc.d ogagent remove >/dev/null +fi + + +# In case this system is running systemd, we make systemd reload the unit files +# to pick up changes. +if [ -d /run/systemd/system ] ; then + systemctl --system daemon-reload >/dev/null || true +fi +# End automatically added section diff --git a/oglive/debian/ogagent-oglive.substvars b/oglive/debian/ogagent-oglive.substvars new file mode 100644 index 0000000..978fc8b --- /dev/null +++ b/oglive/debian/ogagent-oglive.substvars @@ -0,0 +1,2 @@ +misc:Depends= +misc:Pre-Depends= diff --git a/oglive/debian/ogagent.init b/oglive/debian/ogagent.init new file mode 100644 index 0000000..3eee35c --- /dev/null +++ b/oglive/debian/ogagent.init @@ -0,0 +1,23 @@ +#!/bin/sh -e +### BEGIN INIT INFO +# Provides: ogagent +# Required-Start: $local_fs $remote_fs $network $syslog $named +# Required-Stop: $local_fs $remote_fs $network $syslog $named +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: OpenGnsys Agent Service +### END INIT INFO +# + +# . /lib/lsb/init-functions + +case "$1" in + start|stop|restart) + /usr/bin/ogagent $1 + ;; + force-reload) + /usr/bin/ogagent restart + ;; + *) echo "Usage: $0 {start|stop|restart|force-reload}" >&2; exit 1 ;; +esac + diff --git a/oglive/debian/rules b/oglive/debian/rules new file mode 100755 index 0000000..0a5c3e5 --- /dev/null +++ b/oglive/debian/rules @@ -0,0 +1,44 @@ +#!/usr/bin/make -f +# -*- makefile -*- +configure: configure-stamp +configure-stamp: + dh_testdir + touch configure-stamp +build: build-arch build-indep +build-arch: build-stamp +build-indep: build-stamp +build-stamp: configure-stamp + dh_testdir + $(MAKE) + touch $@ +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + dh_clean +install: build + dh_testdir + dh_testroot + dh_prep + dh_installdirs + $(MAKE) DESTDIR=$(CURDIR)/debian/ogagent-oglive install-ogagent +binary-arch: build install + # emptyness +binary-indep: build install + dh_testdir + dh_testroot + dh_installchangelogs + dh_installdocs + dh_installdebconf + dh_installinit --no-start + dh_python2=python + dh_compress + dh_link + dh_fixperms + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb +binary: binary-indep +.PHONY: build clean binary-indep binary install configure diff --git a/oglive/debian/source/format b/oglive/debian/source/format new file mode 100644 index 0000000..9f67427 --- /dev/null +++ b/oglive/debian/source/format @@ -0,0 +1 @@ +3.0 (native) \ No newline at end of file diff --git a/oglive/readme.txt b/oglive/readme.txt new file mode 100644 index 0000000..a2771de --- /dev/null +++ b/oglive/readme.txt @@ -0,0 +1,3 @@ +OGAgent is the agent intended for OpengGnsys interaction. + +Please, visit https://opengnsys.es for more information diff --git a/oglive/scripts/OGAgentTool b/oglive/scripts/OGAgentTool new file mode 100644 index 0000000..5b30052 --- /dev/null +++ b/oglive/scripts/OGAgentTool @@ -0,0 +1,6 @@ +#!/bin/sh + +FOLDER=/usr/share/OGAgent + +cd $FOLDER +python OGAgentUser.py $@ diff --git a/oglive/scripts/OGAgentTool-startup b/oglive/scripts/OGAgentTool-startup new file mode 100644 index 0000000..bb3a848 --- /dev/null +++ b/oglive/scripts/OGAgentTool-startup @@ -0,0 +1,10 @@ +#!/bin/sh + +# Simple hack to wait for systray to be present +# Exec tool if not already runned by session manager +ps -ef | grep "$USER" | grep -v grep | grep -v OGAgentTool-startup | grep 'OGAgentTool' -q +# If not already running +if [ $? -eq 1 ]; then + sleep 5 + exec /usr/bin/OGAgentTool +fi \ No newline at end of file diff --git a/oglive/scripts/ogagent b/oglive/scripts/ogagent new file mode 100644 index 0000000..1bcc29b --- /dev/null +++ b/oglive/scripts/ogagent @@ -0,0 +1,6 @@ +#!/bin/sh + +FOLDER=/usr/share/OGAgent + +cd $FOLDER +python -m opengnsys.linux.OGAgentService $@ diff --git a/src/opengnsys/linux/operations.py b/src/opengnsys/linux/operations.py index f9534d6..cd6f4b9 100644 --- a/src/opengnsys/linux/operations.py +++ b/src/opengnsys/linux/operations.py @@ -152,11 +152,7 @@ def reboot(flags=0): import threading threading._DummyThread._Thread__stop = lambda x: 42 - # Check for OpenGnsys Client or GNU/Linux distribution. - if os.path.exists('/scripts/oginit'): - subprocess.call('source /opt/opengnsys/etc/preinit/loadenviron.sh; /opt/opengnsys/scripts/reboot', shell=True) - else: - subprocess.call(['/sbin/reboot']) + subprocess.call(['/sbin/reboot']) def poweroff(flags=0): @@ -168,11 +164,7 @@ def poweroff(flags=0): import threading threading._DummyThread._Thread__stop = lambda x: 42 - # Check for OpenGnsys Client or GNU/Linux distribution. - if os.path.exists('/scripts/oginit'): - subprocess.call('source /opt/opengnsys/etc/preinit/loadenviron.sh; /opt/opengnsys/scripts/poweroff', shell=True) - else: - subprocess.call(['/sbin/poweroff']) + subprocess.call(['/sbin/poweroff']) def logoff(): diff --git a/src/opengnsys/oglive/__init__.py b/src/opengnsys/oglive/__init__.py new file mode 100644 index 0000000..3a98c78 --- /dev/null +++ b/src/opengnsys/oglive/__init__.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2014 Virtual Cable S.L. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Virtual Cable S.L. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +''' +@author: Adolfo Gómez, dkmaster at dkmon dot com +''' +from __future__ import unicode_literals diff --git a/src/opengnsys/oglive/daemon.py b/src/opengnsys/oglive/daemon.py new file mode 100644 index 0000000..3753808 --- /dev/null +++ b/src/opengnsys/oglive/daemon.py @@ -0,0 +1,182 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2014 Virtual Cable S.L. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Virtual Cable S.L. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +''' +@author: : http://www.jejik.com/authors/sander_marechal/ +@see: : http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ +''' + +from __future__ import unicode_literals +import sys +import os +import time +import atexit +from opengnsys.log import logger + +from signal import SIGTERM + + +class Daemon: + """ + A generic daemon class. + + Usage: subclass the Daemon class and override the run() method + """ + def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): + self.stdin = stdin + self.stdout = stdout + self.stderr = stderr + self.pidfile = pidfile + + def daemonize(self): + """ + do the UNIX double-fork magic, see Stevens' "Advanced + Programming in the UNIX Environment" for details (ISBN 0201563177) + http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 + """ + try: + pid = os.fork() + if pid > 0: + # exit first parent + sys.exit(0) + except OSError as e: + logger.error("fork #1 error: {}".format(e)) + sys.stderr.write("fork #1 failed: {}\n".format(e)) + sys.exit(1) + + # decouple from parent environment + os.chdir("/") + os.setsid() + os.umask(0) + + # do second fork + try: + pid = os.fork() + if pid > 0: + # exit from second parent + sys.exit(0) + except OSError as e: + logger.error("fork #2 error: {}".format(e)) + sys.stderr.write("fork #2 failed: {}\n".format(e)) + sys.exit(1) + + # redirect standard file descriptors + sys.stdout.flush() + sys.stderr.flush() + si = open(self.stdin, 'r') + so = open(self.stdout, 'a+') + se = open(self.stderr, 'a+', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + # write pidfile + atexit.register(self.delpid) + pid = str(os.getpid()) + with open(self.pidfile, 'w+') as f: + f.write("{}\n".format(pid)) + + def delpid(self): + try: + os.remove(self.pidfile) + except Exception: + # Not found/not permissions or whatever... + pass + + def start(self): + """ + Start the daemon + """ + logger.debug('Starting daemon') + # Check for a pidfile to see if the daemon already runs + try: + pf = open(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if pid: + message = "pidfile {} already exist. Daemon already running?\n".format(pid) + logger.error(message) + sys.stderr.write(message) + sys.exit(1) + + # Start the daemon + self.daemonize() + try: + self.run() + except Exception as e: + logger.error('Exception running process: {}'.format(e)) + + if os.path.exists(self.pidfile): + os.remove(self.pidfile) + + def stop(self): + """ + Stop the daemon + """ + # Get the pid from the pidfile + try: + pf = open(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if pid is None: + message = "pidfile {} does not exist. Daemon not running?\n".format(self.pidfile) + logger.info(message) + # sys.stderr.write(message) + return # not an error in a restart + + # Try killing the daemon process + try: + for i in range(10): + os.kill(pid, SIGTERM) + time.sleep(1) + except OSError as err: + if err.errno == 3: # No such process + if os.path.exists(self.pidfile): + os.remove(self.pidfile) + else: + sys.stderr.write(err) + sys.exit(1) + + def restart(self): + """ + Restart the daemon + """ + self.stop() + self.start() + + # Overridables + def run(self): + """ + You should override this method when you subclass Daemon. It will be called after the process has been + daemonized by start() or restart(). + """ diff --git a/src/opengnsys/oglive/operations.py b/src/opengnsys/oglive/operations.py new file mode 100644 index 0000000..ded4379 --- /dev/null +++ b/src/opengnsys/oglive/operations.py @@ -0,0 +1,230 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2014 Virtual Cable S.L. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Virtual Cable S.L. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +''' +@author: Ramón M. Gómez, ramongomez at us dot es +''' +from __future__ import unicode_literals + +import socket +import platform +import fcntl +import os +import ctypes # @UnusedImport +import ctypes.util +import subprocess +import struct +import array +import six +from opengnsys import utils + + +def checkLockedPartition(sync=False): + ''' + Decorator to check if a partition is locked + ''' + def outer(fnc): + def wrapper(*args, **kwargs): + partId = 'None' + try: + this, path, getParams, postParams = args # @UnusedVariable + partId = postParams['disk'] + postParams['part'] + if this.locked.get(partId, False): + this.locked[partId] = True + fnc(*args, **kwargs) + else: + return 'partition locked' + except Exception as e: + this.locked[partId] = False + return 'error {}'.format(e) + finally: + if sync is True: + this.locked[partId] = False + logger.debug('Lock status: {} {}'.format(fnc, this.locked)) + return wrapper + return outer + + +def _getMacAddr(ifname): + ''' + Returns the mac address of an interface + Mac is returned as unicode utf-8 encoded + ''' + if isinstance(ifname, list): + return dict([(name, _getMacAddr(name)) for name in ifname]) + if isinstance(ifname, six.text_type): + ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7) + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + info = bytearray(fcntl.ioctl(s.fileno(), 0x8927, struct.pack(str('256s'), ifname[:15]))) + return six.text_type(''.join(['%02x:' % char for char in info[18:24]])[:-1]) + except Exception: + return None + + +def _getIpAddr(ifname): + ''' + Returns the ip address of an interface + Ip is returned as unicode utf-8 encoded + ''' + if isinstance(ifname, list): + return dict([(name, _getIpAddr(name)) for name in ifname]) + if isinstance(ifname, six.text_type): + ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7) + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + return six.text_type(socket.inet_ntoa(fcntl.ioctl( + s.fileno(), + 0x8915, # SIOCGIFADDR + struct.pack(str('256s'), ifname[:15]) + )[20:24])) + except Exception: + return None + + +def _getInterfaces(): + ''' + Returns a list of interfaces names coded in utf-8 + ''' + max_possible = 128 # arbitrary. raise if needed. + space = max_possible * 16 + if platform.architecture()[0] == '32bit': + offset, length = 32, 32 + elif platform.architecture()[0] == '64bit': + offset, length = 16, 40 + else: + raise OSError('Unknown arquitecture {0}'.format(platform.architecture()[0])) + + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + names = array.array(str('B'), b'\0' * space) + outbytes = struct.unpack(str('iL'), fcntl.ioctl( + s.fileno(), + 0x8912, # SIOCGIFCONF + struct.pack(str('iL'), space, names.buffer_info()[0]) + ))[0] + namestr = names.tostring() + # return namestr, outbytes + return [namestr[i:i + offset].split(b'\0', 1)[0].decode('utf-8') for i in range(0, outbytes, length)] + + +def _getIpAndMac(ifname): + ip, mac = _getIpAddr(ifname), _getMacAddr(ifname) + return (ip, mac) + + +def _exec_ogcommand(self, ogcmd): + ''' + Loads OpenGnsys environment variables, executes the command and returns the result + ''' + ret = subprocess.check_output('source /opt/opengnsys/etc/preinit/loadenviron.sh >/dev/null; {}'.format(ogcmd), shell=True) + return ret + + +def getComputerName(): + ''' + Returns computer name, with no domain + ''' + return socket.gethostname().split('.')[0] + + +def getNetworkInfo(): + ''' + Obtains a list of network interfaces + @return: A "generator" of elements, that are dict-as-object, with this elements: + name: Name of the interface + mac: mac of the interface + ip: ip of the interface + ''' + for ifname in _getInterfaces(): + ip, mac = _getIpAndMac(ifname) + if mac != '00:00:00:00:00:00': # Skips local interfaces + yield utils.Bunch(name=ifname, mac=mac, ip=ip) + + +def getDomainName(): + return '' + + +def getOgliveVersion(): + lv = platform.linux_distribution() + return lv[0] + ', ' + lv[1] + + +def reboot(): + ''' + Simple reboot using OpenGnsys script + ''' + # Workaround for dummy thread + if six.PY3 is False: + import threading + threading._DummyThread._Thread__stop = lambda x: 42 + + _exec_ogcommand('/opt/opengnsys/scripts/reboot', shell=True) + + +def poweroff(): + ''' + Simple poweroff using OpenGnsys script + ''' + # Workaround for dummy thread + if six.PY3 is False: + import threading + threading._DummyThread._Thread__stop = lambda x: 42 + + _exec_ogcommand('/opt/opengnsys/scripts/poweroff', shell=True) + + +def logoff(): + pass + + +def renameComputer(newName): + pass + + +def joinDomain(domain, ou, account, password, executeInOneStep=False): + pass + + +def changeUserPassword(user, oldPassword, newPassword): + pass + + +def diskconfig(): + ''' + Returns disk configuration. + Warning: this operation may take some time. + ''' + try: + _exec_ogcommand('/opt/opengnsys/interfaceAdm/getConfiguration') + # Returns content of configuration file. + cfgdata = open('/tmp/getconfig', 'r').read() + except IOError: + cfgdata = '' + return cfgdata + diff --git a/src/opengnsys/operations.py b/src/opengnsys/operations.py index dcfa40c..9015cbe 100644 --- a/src/opengnsys/operations.py +++ b/src/opengnsys/operations.py @@ -34,6 +34,8 @@ from __future__ import unicode_literals import sys +import os + # Importing platform operations and getting operating system data. if sys.platform == 'win32': from .windows.operations import * # @UnusedWildImport @@ -45,6 +47,11 @@ else: osType = 'MacOS' osVersion = getMacosVersion().replace(',','') else: - from .linux.operations import * # @UnusedWildImport - osType = 'Linux' - osVersion = getLinuxVersion().replace(',','') + if os.path.exists('/scripts/oginit'): + from .oglive.operations import * # @UnusedWildImport + osType = 'ogLive' + osVersion = getOgliveVersion().replace(',','') + else: + from .linux.operations import * # @UnusedWildImport + osType = 'Linux' + osVersion = getLinuxVersion().replace(',','') From 0a085de59269d8f89f14ef3cff4e3b88b3b08244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Mon, 18 Jun 2018 20:47:35 +0200 Subject: [PATCH 02/48] #750: Using PEP 8 Style Guide for Python in new module; implementing basic {{{getconfig}}} operation. --- oglive/build-package.sh | 7 + oglive/build-packages.sh | 11 -- .../modules/server/OpenGnSys/__init__.py | 162 +++++++++++------- src/opengnsys/oglive/operations.py | 113 ++++-------- src/opengnsys/operations.py | 22 +-- 5 files changed, 155 insertions(+), 160 deletions(-) create mode 100755 oglive/build-package.sh delete mode 100755 oglive/build-packages.sh diff --git a/oglive/build-package.sh b/oglive/build-package.sh new file mode 100755 index 0000000..c0e03c5 --- /dev/null +++ b/oglive/build-package.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +cd $(dirname "$0") + +# Build package +dpkg-buildpackage -b -d + diff --git a/oglive/build-packages.sh b/oglive/build-packages.sh deleted file mode 100755 index 4d5eb8d..0000000 --- a/oglive/build-packages.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -cd $(dirname "$0") -top=`pwd` - -[ -r ../src/VERSION ] && VERSION="$(cat ../src/VERSION)" || VERSION="1.1.0" -RELEASE="1" - -# Debian based -dpkg-buildpackage -b -d - diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 1438e46..01fa107 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -25,29 +25,27 @@ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -''' +""" @author: Ramón M. Gómez, ramongomez at us dot es -''' +""" from __future__ import unicode_literals -import subprocess -import threading -import thread import os -import platform -import time import random import string +import threading +import time import urllib -from opengnsys.workers import ServerWorker -from opengnsys import REST, RESTError +from opengnsys import REST from opengnsys import operations from opengnsys.log import logger from opengnsys.scriptThread import ScriptExecutorThread +from opengnsys.workers import ServerWorker + # Error handler decorator. -def catchBackgroundError(fnc): +def catch_background_error(fnc): def wrapper(*args, **kwargs): this = args[0] try: @@ -56,18 +54,45 @@ def catchBackgroundError(fnc): this.REST.sendMessage('error?id={}'.format(kwargs.get('requestId', 'error')), {'error': '{}'.format(e)}) return wrapper + +def check_locked_partition(sync=False): + """ + Decorator to check if a partition is locked + """ + def outer(fnc): + def wrapper(*args, **kwargs): + part_id = 'None' + try: + this, path, get_params, post_params = args # @UnusedVariable + part_id = post_params['disk'] + post_params['part'] + if this.locked.get(part_id, False): + this.locked[part_id] = True + fnc(*args, **kwargs) + else: + return 'partition locked' + except Exception as e: + this.locked[part_id] = False + return 'error {}'.format(e) + finally: + if sync is True: + this.locked[part_id] = False + logger.debug('Lock status: {} {}'.format(fnc, this.locked)) + return wrapper + return outer + + class OpenGnSysWorker(ServerWorker): name = 'opengnsys' interface = None # Binded interface for OpenGnsys loggedin = False # User session flag - locked = {} + locked = {} # Locked partitions random = None # Random string for secure connections length = 32 # Random string length def checkSecret(self, server): - ''' + """ Checks for received secret key and raise exception if it isn't valid. - ''' + """ try: if self.random != server.headers['Authorization']: raise Exception('Unauthorized operation') @@ -76,9 +101,9 @@ class OpenGnSysWorker(ServerWorker): raise Exception(e) def onActivation(self): - ''' + """ Sends OGAgent activation notification to OpenGnsys server - ''' + """ self.cmd = None # Ensure cfg has required configuration variables or an exception will be thrown self.REST = REST(self.service.config.get('opengnsys', 'remote')) @@ -107,52 +132,59 @@ class OpenGnSysWorker(ServerWorker): # Generate random secret to send on activation self.random = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(self.length)) # Send initalization message - self.REST.sendMessage('ogagent/started', {'mac': self.interface.mac, 'ip': self.interface.ip, 'secret': self.random, 'ostype': operations.osType, 'osversion': operations.osVersion}) + self.REST.sendMessage('ogagent/started', {'mac': self.interface.mac, 'ip': self.interface.ip, + 'secret': self.random, 'ostype': operations.os_type, + 'osversion': operations.os_version}) def onDeactivation(self): - ''' + """ Sends OGAgent stopping notification to OpenGnsys server - ''' + """ logger.debug('onDeactivation') - self.REST.sendMessage('ogagent/stopped', {'mac': self.interface.mac, 'ip': self.interface.ip, 'ostype': operations.osType, 'osversion': operations.osVersion}) + self.REST.sendMessage('ogagent/stopped', {'mac': self.interface.mac, 'ip': self.interface.ip, + 'ostype': operations.os_type, 'osversion': operations.os_version}) def processClientMessage(self, message, data): logger.debug('Got OpenGnsys message from client: {}, data {}'.format(message, data)) def onLogin(self, userData): - ''' + """ Sends session login notification to OpenGnsys server - ''' + """ user, sep, language = userData.partition(',') logger.debug('Received login for {} with language {}'.format(user, language)) self.loggedin = True - self.REST.sendMessage('ogagent/loggedin', {'ip': self.interface.ip, 'user': user, 'language': language, 'ostype': operations.osType, 'osversion': operations.osVersion}) + self.REST.sendMessage('ogagent/loggedin', {'ip': self.interface.ip, 'user': user, 'language': language, + 'ostype': operations.os_type, 'osversion': operations.os_version}) def onLogout(self, user): - ''' + """ Sends session logout notification to OpenGnsys server - ''' + """ logger.debug('Received logout for {}'.format(user)) self.loggedin = False self.REST.sendMessage('ogagent/loggedout', {'ip': self.interface.ip, 'user': user}) def process_ogclient(self, path, getParams, postParams, server): - ''' - This method can be overriden to provide your own message proccessor, or better you can - implement a method that is called exactly as "process_" + path[0] (module name has been removed from path array) and this default processMessage will invoke it + """ + his method can be overriden to provide your own message proccessor, or better you can implement a + method that is called exactly as "process_" + path[0] (module name has been removed from patharray) and + this default processMessage will invoke it * Example: Imagine this invocation url (no matter if GET or POST): http://example.com:9999/Sample/mazinger/Z The HTTP Server will remove "Sample" from path, parse arguments and invoke this method as this: module.processMessage(["mazinger","Z"], getParams, postParams) - This method will process "mazinger", and look for a "self" method that is called "process_mazinger", and invoke it this way: + This method will process "mazinger", and look for a "self" method that is called "process_mazinger", + and invoke it this way: return self.process_mazinger(["Z"], getParams, postParams) - In the case path is empty (that is, the path is composed only by the module name, like in "http://example.com/Sample", the "process" method - will be invoked directly + In the case path is empty (that is, the path is composed only by the module name, like in + "http://example.com/Sample", the "process" method will be invoked directly - The methods must return data that can be serialized to json (i.e. Ojects are not serializable to json, basic type are) - ''' + The methods must return data that can be serialized to json (i.e. Ojects are not serializable to json, + basic type are) + """ if not path: return "ok" try: @@ -162,34 +194,27 @@ class OpenGnSysWorker(ServerWorker): return operation(path[1:], getParams, postParams) def process_status(self, path, getParams, postParams, server): - ''' + """ Returns client status. - ''' - res = {'status': '', 'loggedin': self.loggedin} - if platform.system() == 'Linux': # GNU/Linux - # Check if it's OpenGnsys Client. - if os.path.exists('/scripts/oginit'): - # Check if OpenGnsys Client is busy. - if self.locked: - res['status'] = 'BSY' - else: - res['status'] = 'OPG' - else: - # Check if there is an active session. - res['status'] = 'LNX' - elif platform.system() == 'Windows': # Windows - # Check if there is an active session. - res['status'] = 'WIN' - elif platform.system() == 'Darwin': # Mac OS X ?? - res['status'] = 'OSX' + """ + st = {'linux': 'LNX', 'macos': 'OSX', 'oglive': 'OPG', 'windows': 'WIN'} + res = {'loggedin': self.loggedin} + try: + res['status'] = st[operations.os_type.lower()] + except KeyError: + res['status'] = '' + # Check if OpenGnsys Client is busy + if res['status'] == 'OPG' and self.locked: + res['status'] = 'BSY' return res def process_reboot(self, path, getParams, postParams, server): - ''' + """ Launches a system reboot operation. - ''' + """ logger.debug('Received reboot operation') self.checkSecret(server) + # Rebooting thread. def rebt(): operations.reboot() @@ -197,11 +222,12 @@ class OpenGnSysWorker(ServerWorker): return {'op': 'launched'} def process_poweroff(self, path, getParams, postParams, server): - ''' + """ Launches a system power off operation. - ''' + """ logger.debug('Received poweroff operation') self.checkSecret(server) + # Powering off thread. def pwoff(): time.sleep(2) @@ -210,9 +236,9 @@ class OpenGnSysWorker(ServerWorker): return {'op': 'launched'} def process_script(self, path, getParams, postParams, server): - ''' + """ Processes an script execution (script should be encoded in base64) - ''' + """ logger.debug('Processing script request') self.checkSecret(server) # Decoding script. @@ -227,9 +253,9 @@ class OpenGnSysWorker(ServerWorker): return {'op': 'launched'} def process_logoff(self, path, getParams, postParams, server): - ''' + """ Closes user session. - ''' + """ logger.debug('Received logoff operation') self.checkSecret(server) # Sending log off message to OGAgent client. @@ -237,9 +263,9 @@ class OpenGnSysWorker(ServerWorker): return {'op': 'sended to client'} def process_popup(self, path, getParams, postParams, server): - ''' + """ Shows a message popup on the user's session. - ''' + """ logger.debug('Received message operation') self.checkSecret(server) # Sending popup message to OGAgent client. @@ -248,3 +274,17 @@ class OpenGnSysWorker(ServerWorker): def process_client_popup(self, params): self.REST.sendMessage('popup_done', params) + + def process_getconfig(self, path, get_params, post_params, server): + """ + Returns client configuration + :param path: + :param get_params: + :param post_params: + :param server: + :return: object + """ + logger.debug('Recieved getconfig operation') + self.checkSecret(server) + # Returns raw data + return {'config': operations.get_disk_config()} diff --git a/src/opengnsys/oglive/operations.py b/src/opengnsys/oglive/operations.py index ded4379..6c67421 100644 --- a/src/opengnsys/oglive/operations.py +++ b/src/opengnsys/oglive/operations.py @@ -26,17 +26,14 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -''' +""" @author: Ramón M. Gómez, ramongomez at us dot es -''' +""" from __future__ import unicode_literals import socket import platform import fcntl -import os -import ctypes # @UnusedImport -import ctypes.util import subprocess import struct import array @@ -44,37 +41,11 @@ import six from opengnsys import utils -def checkLockedPartition(sync=False): - ''' - Decorator to check if a partition is locked - ''' - def outer(fnc): - def wrapper(*args, **kwargs): - partId = 'None' - try: - this, path, getParams, postParams = args # @UnusedVariable - partId = postParams['disk'] + postParams['part'] - if this.locked.get(partId, False): - this.locked[partId] = True - fnc(*args, **kwargs) - else: - return 'partition locked' - except Exception as e: - this.locked[partId] = False - return 'error {}'.format(e) - finally: - if sync is True: - this.locked[partId] = False - logger.debug('Lock status: {} {}'.format(fnc, this.locked)) - return wrapper - return outer - - def _getMacAddr(ifname): - ''' + """ Returns the mac address of an interface Mac is returned as unicode utf-8 encoded - ''' + """ if isinstance(ifname, list): return dict([(name, _getMacAddr(name)) for name in ifname]) if isinstance(ifname, six.text_type): @@ -88,10 +59,10 @@ def _getMacAddr(ifname): def _getIpAddr(ifname): - ''' + """ Returns the ip address of an interface Ip is returned as unicode utf-8 encoded - ''' + """ if isinstance(ifname, list): return dict([(name, _getIpAddr(name)) for name in ifname]) if isinstance(ifname, six.text_type): @@ -108,9 +79,9 @@ def _getIpAddr(ifname): def _getInterfaces(): - ''' + """ Returns a list of interfaces names coded in utf-8 - ''' + """ max_possible = 128 # arbitrary. raise if needed. space = max_possible * 16 if platform.architecture()[0] == '32bit': @@ -134,32 +105,32 @@ def _getInterfaces(): def _getIpAndMac(ifname): ip, mac = _getIpAddr(ifname), _getMacAddr(ifname) - return (ip, mac) + return ip, mac -def _exec_ogcommand(self, ogcmd): - ''' +def _exec_ogcommand(ogcmd): + """ Loads OpenGnsys environment variables, executes the command and returns the result - ''' - ret = subprocess.check_output('source /opt/opengnsys/etc/preinit/loadenviron.sh >/dev/null; {}'.format(ogcmd), shell=True) + """ + ret = subprocess.check_output(ogcmd, shell=True) return ret def getComputerName(): - ''' + """ Returns computer name, with no domain - ''' + """ return socket.gethostname().split('.')[0] def getNetworkInfo(): - ''' + """ Obtains a list of network interfaces - @return: A "generator" of elements, that are dict-as-object, with this elements: + :return: A "generator" of elements, that are dict-as-object, with this elements: name: Name of the interface mac: mac of the interface ip: ip of the interface - ''' + """ for ifname in _getInterfaces(): ip, mac = _getIpAndMac(ifname) if mac != '00:00:00:00:00:00': # Skips local interfaces @@ -170,56 +141,44 @@ def getDomainName(): return '' -def getOgliveVersion(): - lv = platform.linux_distribution() - return lv[0] + ', ' + lv[1] +def get_oglive_version(): + """ + Returns ogLive Kernel version and architecture + :return: kernel version + """ + kv = platform.os.uname() + return kv[2] + ', ' + kv[4] def reboot(): - ''' + """ Simple reboot using OpenGnsys script - ''' + """ # Workaround for dummy thread if six.PY3 is False: import threading threading._DummyThread._Thread__stop = lambda x: 42 - _exec_ogcommand('/opt/opengnsys/scripts/reboot', shell=True) + _exec_ogcommand('/opt/opengnsys/scripts/reboot') def poweroff(): - ''' + """ Simple poweroff using OpenGnsys script - ''' + """ # Workaround for dummy thread if six.PY3 is False: import threading threading._DummyThread._Thread__stop = lambda x: 42 - _exec_ogcommand('/opt/opengnsys/scripts/poweroff', shell=True) + _exec_ogcommand('/opt/opengnsys/scripts/poweroff') -def logoff(): - pass - - -def renameComputer(newName): - pass - - -def joinDomain(domain, ou, account, password, executeInOneStep=False): - pass - - -def changeUserPassword(user, oldPassword, newPassword): - pass - - -def diskconfig(): - ''' - Returns disk configuration. - Warning: this operation may take some time. - ''' +def get_disk_config(): + """ + Returns disk configuration + Warning: this operation may take some time + """ try: _exec_ogcommand('/opt/opengnsys/interfaceAdm/getConfiguration') # Returns content of configuration file. diff --git a/src/opengnsys/operations.py b/src/opengnsys/operations.py index 9015cbe..926e5a2 100644 --- a/src/opengnsys/operations.py +++ b/src/opengnsys/operations.py @@ -26,9 +26,9 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -''' -@author: Adolfo Gómez, dkmaster at dkmon dot com -''' +""" +@author: Ramón M. Gómez, ramongomez at us dot es +""" # pylint: disable=unused-wildcard-import,wildcard-import from __future__ import unicode_literals @@ -39,19 +39,19 @@ import os # Importing platform operations and getting operating system data. if sys.platform == 'win32': from .windows.operations import * # @UnusedWildImport - osType = 'Windows' - osVersion = getWindowsVersion() + os_type = 'Windows' + os_version = getWindowsVersion() else: if sys.platform == 'darwin': from .macos.operations import * # @UnusedWildImport - osType = 'MacOS' - osVersion = getMacosVersion().replace(',','') + os_type = 'MacOS' + os_version = getMacosVersion().replace(',', '') else: if os.path.exists('/scripts/oginit'): from .oglive.operations import * # @UnusedWildImport - osType = 'ogLive' - osVersion = getOgliveVersion().replace(',','') + os_type = 'ogLive' + os_version = get_oglive_version() else: from .linux.operations import * # @UnusedWildImport - osType = 'Linux' - osVersion = getLinuxVersion().replace(',','') + os_type = 'Linux' + os_version = getLinuxVersion().replace(',', '') From 4272d559e7b0699d3108872a4800d32542916699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Wed, 20 Jun 2018 19:48:49 +0200 Subject: [PATCH 03/48] #750: OGAgent for ogLive looks for {{{oglive}}} environ variable; route {{{GET /getconfig}}} returns data in JSON format. --- .../modules/server/OpenGnSys/__init__.py | 43 +++++++++++++++++-- src/opengnsys/oglive/operations.py | 5 +-- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 01fa107..70c09c2 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -42,6 +42,7 @@ from opengnsys import operations from opengnsys.log import logger from opengnsys.scriptThread import ScriptExecutorThread from opengnsys.workers import ServerWorker +from six.moves.urllib import parse # Error handler decorator. @@ -106,7 +107,12 @@ class OpenGnSysWorker(ServerWorker): """ self.cmd = None # Ensure cfg has required configuration variables or an exception will be thrown - self.REST = REST(self.service.config.get('opengnsys', 'remote')) + url = self.service.config.get('opengnsys', 'remote') + if operations.os_type == 'ogLive' and 'oglive' in os.environ: + # Replacing server IP if its running on ogLive clinet + logger.debug('Activating on ogLive client, new server is {}'.format(os.environ['oglive'])) + url = parse.urlsplit(url)._replace(netloc=os.environ['oglive']).geturl() + self.REST = REST(url) # Get network interfaces until they are active or timeout (5 minutes) for t in range(0, 300): try: @@ -284,7 +290,38 @@ class OpenGnSysWorker(ServerWorker): :param server: :return: object """ + serialno = '' # Serial number + storage = [] # Storage configuration + warnings = 0 # Number of warnings logger.debug('Recieved getconfig operation') self.checkSecret(server) - # Returns raw data - return {'config': operations.get_disk_config()} + # Processing data + for row in operations.get_disk_config().strip().split(';'): + cols = row.split(':') + if len(cols) == 1: + if cols[0] != '': + # Serial number + serialno = cols[0] + else: + # Skip blank rows + pass + elif len(cols) == 7: + disk, npart, tpart, fs, os, size, usage = cols + try: + if int(npart) == 0: + # Disk information + storage.append({'disk': int(disk), 'parttable': int(tpart), 'size': int(size)}) + else: + # Partition information + storage.append({'disk': int(disk), 'partition': int(npart), 'parttype': tpart, + 'filesystem': fs, 'operatingsystem': os, 'size': int(size), + 'usage': int(usage)}) + except ValueError: + logger.warn('Configuration parameter error: {}'.format(cols)) + warnings += 1 + else: + # Logging warnings + logger.warn('Configuration data error: {}'.format(cols)) + warnings += 1 + # Returning configuration data and count of warnings + return {'serialno': serialno, 'storage': storage, 'warnings': warnings} diff --git a/src/opengnsys/oglive/operations.py b/src/opengnsys/oglive/operations.py index 6c67421..e24e0b9 100644 --- a/src/opengnsys/oglive/operations.py +++ b/src/opengnsys/oglive/operations.py @@ -181,9 +181,8 @@ def get_disk_config(): """ try: _exec_ogcommand('/opt/opengnsys/interfaceAdm/getConfiguration') - # Returns content of configuration file. - cfgdata = open('/tmp/getconfig', 'r').read() + # Returns content of configuration file + cfgdata = open('/tmp/getconfig', 'r').read().strip() except IOError: cfgdata = '' return cfgdata - From e5ba6cfc10996d57fe8f5244056fc4e9a9fc59ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Wed, 20 Jun 2018 20:25:30 +0200 Subject: [PATCH 04/48] #750: New route {{{GET /hardware}}}. --- .../modules/server/OpenGnSys/__init__.py | 15 ++++++++++++++- src/opengnsys/oglive/operations.py | 18 ++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 70c09c2..70c8973 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -296,7 +296,7 @@ class OpenGnSysWorker(ServerWorker): logger.debug('Recieved getconfig operation') self.checkSecret(server) # Processing data - for row in operations.get_disk_config().strip().split(';'): + for row in operations.get_configuration().split(';'): cols = row.split(':') if len(cols) == 1: if cols[0] != '': @@ -325,3 +325,16 @@ class OpenGnSysWorker(ServerWorker): warnings += 1 # Returning configuration data and count of warnings return {'serialno': serialno, 'storage': storage, 'warnings': warnings} + + def process_hardware(self, path, get_params, post_params, server): + """ + Returns client's hardware profile + :param path: + :param get_params: + :param post_params: + :param server: + """ + logger.debug('Recieved {} operation'.format(path)) + self.checkSecret(server) + # Returning raw data + return operations.get_hardware() diff --git a/src/opengnsys/oglive/operations.py b/src/opengnsys/oglive/operations.py index e24e0b9..b22a607 100644 --- a/src/opengnsys/oglive/operations.py +++ b/src/opengnsys/oglive/operations.py @@ -174,9 +174,9 @@ def poweroff(): _exec_ogcommand('/opt/opengnsys/scripts/poweroff') -def get_disk_config(): +def get_configuration(): """ - Returns disk configuration + Returns client's configuration Warning: this operation may take some time """ try: @@ -186,3 +186,17 @@ def get_disk_config(): except IOError: cfgdata = '' return cfgdata + + +def get_hardware(): + """ + Returns client's hardware list + """ + try: + filepath = _exec_ogcommand('/opt/opengnsys/scripts/listHardwareInfo').strip() + # Returns content of configuration file, skipping the header line and newline characters + with open(filepath, 'r') as f: + harddata = map(str.strip, f.readlines()[1:]) + except IOError: + harddata = '' + return harddata From a108f36cffadb6926e393337be7ac3a431e8efa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Thu, 21 Jun 2018 19:23:10 +0200 Subject: [PATCH 05/48] #750: Route {{{GET /getconfig}}} renamed as {{{GET /config}}}; route {{{GET /hardware}}} returns data in JSON format; new basic route {{{GET /software?disk=NDisk&part=NPart}}} --- .../modules/server/OpenGnSys/__init__.py | 27 ++++++++++++++++--- src/opengnsys/oglive/operations.py | 21 ++++++++++++++- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 70c8973..9182472 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -281,7 +281,7 @@ class OpenGnSysWorker(ServerWorker): def process_client_popup(self, params): self.REST.sendMessage('popup_done', params) - def process_getconfig(self, path, get_params, post_params, server): + def process_config(self, path, get_params, post_params, server): """ Returns client configuration :param path: @@ -334,7 +334,26 @@ class OpenGnSysWorker(ServerWorker): :param post_params: :param server: """ - logger.debug('Recieved {} operation'.format(path)) + data = [] + logger.debug('Recieved hardware operation') self.checkSecret(server) - # Returning raw data - return operations.get_hardware() + # Processing data + try: + for comp in operations.get_hardware(): + data.append({'component': comp.split('=')[0], 'value': comp.split('=')[1]}) + except: + pass + # Return list of hardware components + return data + + def process_software(self, path, get_params, post_params, server): + """ + Returns software profile installed on an operating system + :param path: + :param get_params: + :param post_params: + :param server: + :return: + """ + logger.debug('Recieved software operation with params: {}'.format(post_params)) + return operations.get_software(post_params['disk'], post_params['part']) diff --git a/src/opengnsys/oglive/operations.py b/src/opengnsys/oglive/operations.py index b22a607..5541780 100644 --- a/src/opengnsys/oglive/operations.py +++ b/src/opengnsys/oglive/operations.py @@ -164,7 +164,7 @@ def reboot(): def poweroff(): """ - Simple poweroff using OpenGnsys script + Simple power off using OpenGnsys script """ # Workaround for dummy thread if six.PY3 is False: @@ -178,6 +178,7 @@ def get_configuration(): """ Returns client's configuration Warning: this operation may take some time + :return: """ try: _exec_ogcommand('/opt/opengnsys/interfaceAdm/getConfiguration') @@ -191,6 +192,7 @@ def get_configuration(): def get_hardware(): """ Returns client's hardware list + :return: """ try: filepath = _exec_ogcommand('/opt/opengnsys/scripts/listHardwareInfo').strip() @@ -200,3 +202,20 @@ def get_hardware(): except IOError: harddata = '' return harddata + + +def get_software(disk, part): + """ + Returns software list installed on an operating system + :param disk: + :param part: + :return: + """ + try: + filepath = _exec_ogcommand('/opt/opengnsys/scripts/listSoftwareInfo {} {}'.format(disk, part)).strip() + # Returns content of configuration file, skipping the header line and newline characters + with open(filepath, 'r') as f: + softdata = map(str.strip, f.readlines()) + except IOError: + softdata = '' + return softdata From 0c8032d13790a0a96c34c1d8d844b37bae1049ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Sat, 30 Jun 2018 17:13:45 +0200 Subject: [PATCH 06/48] #750: Using more descriptive status; new route {{{POST /command}}} to launch command/script in a callback thread that returns all data to a server route. --- .../modules/server/OpenGnSys/__init__.py | 49 +++++++++++++++---- src/opengnsys/oglive/operations.py | 15 ++++++ src/setup.py | 2 +- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 9182472..da1410d 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -64,7 +64,7 @@ def check_locked_partition(sync=False): def wrapper(*args, **kwargs): part_id = 'None' try: - this, path, get_params, post_params = args # @UnusedVariable + this, path, get_params, post_params, server = args # @UnusedVariable part_id = post_params['disk'] + post_params['part'] if this.locked.get(part_id, False): this.locked[part_id] = True @@ -201,17 +201,16 @@ class OpenGnSysWorker(ServerWorker): def process_status(self, path, getParams, postParams, server): """ - Returns client status. + Returns client status (OS type and login status). """ - st = {'linux': 'LNX', 'macos': 'OSX', 'oglive': 'OPG', 'windows': 'WIN'} res = {'loggedin': self.loggedin} try: - res['status'] = st[operations.os_type.lower()] + res['status'] = operations.os_type.lower() except KeyError: res['status'] = '' # Check if OpenGnsys Client is busy - if res['status'] == 'OPG' and self.locked: - res['status'] = 'BSY' + if res['status'] == 'oglive' and self.locked: + res['status'] = 'busy' return res def process_reboot(self, path, getParams, postParams, server): @@ -306,7 +305,7 @@ class OpenGnSysWorker(ServerWorker): # Skip blank rows pass elif len(cols) == 7: - disk, npart, tpart, fs, os, size, usage = cols + disk, npart, tpart, fs, opsys, size, usage = cols try: if int(npart) == 0: # Disk information @@ -314,7 +313,7 @@ class OpenGnSysWorker(ServerWorker): else: # Partition information storage.append({'disk': int(disk), 'partition': int(npart), 'parttype': tpart, - 'filesystem': fs, 'operatingsystem': os, 'size': int(size), + 'filesystem': fs, 'operatingsystem': opsys, 'size': int(size), 'usage': int(usage)}) except ValueError: logger.warn('Configuration parameter error: {}'.format(cols)) @@ -326,6 +325,38 @@ class OpenGnSysWorker(ServerWorker): # Returning configuration data and count of warnings return {'serialno': serialno, 'storage': storage, 'warnings': warnings} + def task_command(self, code, route): + """ + Task to execute a command + :param code: Code to execute + :param route: server REST route to return results (including its parameters) + """ + (stat, out, err) = operations.exec_command(code) + self.REST.sendMessage(route, {'status': stat, 'output': out, 'error': err}) + + def process_command(self, path, get_params, post_params, server): + """ + Launches a thread to executing a command + :param path: ignored + :param get_params: ignored + :param post_params: object with format {"id": OperationId, "code": "Code", url: "ReturnURL"} + :param server: ignored + :rtype: object with launching status + """ + logger.debug('Recieved command operation with params: {}'.format(post_params)) + self.checkSecret(server) + # Processing data + try: + code = post_params.get('code') + cmd_id = post_params.get('id') + route = '{}?id={}'.format(post_params.get('route'), cmd_id) + # Launching new thread + threading.Thread(target=self.task_command, args=(code, route)).start() + except Exception as e: + logger.error('Got exception {}'.format(e)) + return {'error': e} + return {'op': 'launched'} + def process_hardware(self, path, get_params, post_params, server): """ Returns client's hardware profile @@ -356,4 +387,4 @@ class OpenGnSysWorker(ServerWorker): :return: """ logger.debug('Recieved software operation with params: {}'.format(post_params)) - return operations.get_software(post_params['disk'], post_params['part']) + return operations.get_software(post_params.get('disk'), post_params.get('part')) diff --git a/src/opengnsys/oglive/operations.py b/src/opengnsys/oglive/operations.py index 5541780..342cfb0 100644 --- a/src/opengnsys/oglive/operations.py +++ b/src/opengnsys/oglive/operations.py @@ -189,6 +189,21 @@ def get_configuration(): return cfgdata +def exec_command(cmd): + """ + Executing a shell command + :param cmd: + :return: object with components: + output: standard output + error: error output + exit: exit code + """ + proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (out, err) = proc.communicate() + stat = proc.returncode + return stat, out, err + + def get_hardware(): """ Returns client's hardware list diff --git a/src/setup.py b/src/setup.py index 15254f4..617d518 100644 --- a/src/setup.py +++ b/src/setup.py @@ -139,5 +139,5 @@ setup( description='OpenGnsys Agent', author='Adolfo Gomez', author_email='agomez@virtualcable.es', - zipfile='OGAgent.zip', + zipfile='OGAgent.zip', requires=['six'] ) From 3806f120c6a605e66d5953e0e8307dd07415b999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Tue, 3 Jul 2018 17:55:25 +0200 Subject: [PATCH 07/48] #750: Simple REST route to get the list of running commands. --- .../modules/server/OpenGnSys/__init__.py | 55 +++++++++++++++---- src/opengnsys/operations.py | 2 +- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index da1410d..7a2ac6a 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -87,6 +87,7 @@ class OpenGnSysWorker(ServerWorker): interface = None # Binded interface for OpenGnsys loggedin = False # User session flag locked = {} # Locked partitions + commands = [] # Running commands random = None # Random string for secure connections length = 32 # Random string length @@ -292,7 +293,7 @@ class OpenGnSysWorker(ServerWorker): serialno = '' # Serial number storage = [] # Storage configuration warnings = 0 # Number of warnings - logger.debug('Recieved getconfig operation') + logger.debug('Received getconfig operation') self.checkSecret(server) # Processing data for row in operations.get_configuration().split(';'): @@ -325,38 +326,70 @@ class OpenGnSysWorker(ServerWorker): # Returning configuration data and count of warnings return {'serialno': serialno, 'storage': storage, 'warnings': warnings} - def task_command(self, code, route): + def task_command(self, code, route, op_id): """ Task to execute a command :param code: Code to execute - :param route: server REST route to return results (including its parameters) + :param route: server callback REST route to return results + :param op_id: operation id. """ + # Executing command (stat, out, err) = operations.exec_command(code) - self.REST.sendMessage(route, {'status': stat, 'output': out, 'error': err}) + # Removing command from the list + for c in self.commands: + if c.has_key('id') and c['id'] == op_id: + self.commands.remove(c) + # Sending results + self.REST.sendMessage(route, {'id': op_id, 'status': stat, 'output': out, 'error': err}) def process_command(self, path, get_params, post_params, server): """ Launches a thread to executing a command :param path: ignored :param get_params: ignored - :param post_params: object with format {"id": OperationId, "code": "Code", url: "ReturnURL"} - :param server: ignored + :param post_params: object with format: + id: operation id. + code: command code + route: callback URL + :param server: headers data :rtype: object with launching status """ - logger.debug('Recieved command operation with params: {}'.format(post_params)) + logger.debug('Received command operation with params: {}'.format(post_params)) self.checkSecret(server) # Processing data try: code = post_params.get('code') - cmd_id = post_params.get('id') - route = '{}?id={}'.format(post_params.get('route'), cmd_id) - # Launching new thread - threading.Thread(target=self.task_command, args=(code, route)).start() + op_id = post_params.get('id') + route = post_params.get('route') + # Checking if the thread id. exists + for c in self.commands: + if c.has_key('id') and c['id'] == op_id: + raise Exception('Task id. already exists: {}'.format(op_id)) + # Launching a new thread + thr = threading.Thread(name=op_id, target=self.task_command, args=(code, route, op_id)) + thr.start() + self.commands.append({'id': op_id, 'code': code}) except Exception as e: logger.error('Got exception {}'.format(e)) return {'error': e} return {'op': 'launched'} + def process_execinfo(self, path, get_params, post_params, server): + """ + Returns running commands information + :param path: + :param get_params: + :param post_params: + :param server: + :return: object + """ + #data = [] + #for c in self.commands: + # if c.is_alive(): + # data.append({'name': c.getName(), 'code': c.__dict__['_Thread__args']}) + #return data + return self.commands + def process_hardware(self, path, get_params, post_params, server): """ Returns client's hardware profile diff --git a/src/opengnsys/operations.py b/src/opengnsys/operations.py index 926e5a2..a81d0c0 100644 --- a/src/opengnsys/operations.py +++ b/src/opengnsys/operations.py @@ -50,7 +50,7 @@ else: if os.path.exists('/scripts/oginit'): from .oglive.operations import * # @UnusedWildImport os_type = 'ogLive' - os_version = get_oglive_version() + os_version = get_oglive_version().replace(',', '') else: from .linux.operations import * # @UnusedWildImport os_type = 'Linux' From 91bdf9040dcf771c258959fd4d0dfd399ebb3677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Wed, 4 Jul 2018 12:15:50 +0200 Subject: [PATCH 08/48] #750: Adapting route {{GET /command}}} parameters. --- .../modules/server/OpenGnSys/__init__.py | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 7a2ac6a..1832652 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -85,6 +85,7 @@ def check_locked_partition(sync=False): class OpenGnSysWorker(ServerWorker): name = 'opengnsys' interface = None # Binded interface for OpenGnsys + REST = None # REST object loggedin = False # User session flag locked = {} # Locked partitions commands = [] # Running commands @@ -106,7 +107,6 @@ class OpenGnSysWorker(ServerWorker): """ Sends OGAgent activation notification to OpenGnsys server """ - self.cmd = None # Ensure cfg has required configuration variables or an exception will be thrown url = self.service.config.get('opengnsys', 'remote') if operations.os_type == 'ogLive' and 'oglive' in os.environ: @@ -337,10 +337,11 @@ class OpenGnSysWorker(ServerWorker): (stat, out, err) = operations.exec_command(code) # Removing command from the list for c in self.commands: - if c.has_key('id') and c['id'] == op_id: + if c.getName() == op_id: self.commands.remove(c) # Sending results - self.REST.sendMessage(route, {'id': op_id, 'status': stat, 'output': out, 'error': err}) + self.REST.sendMessage(route, {'client': self.interface.ip, 'trace': op_id, 'status': stat, 'output': out, + 'error': err}) def process_command(self, path, get_params, post_params, server): """ @@ -349,8 +350,8 @@ class OpenGnSysWorker(ServerWorker): :param get_params: ignored :param post_params: object with format: id: operation id. - code: command code - route: callback URL + script: command code + redirect_url: callback REST route :param server: headers data :rtype: object with launching status """ @@ -358,17 +359,17 @@ class OpenGnSysWorker(ServerWorker): self.checkSecret(server) # Processing data try: - code = post_params.get('code') + script = post_params.get('script') op_id = post_params.get('id') - route = post_params.get('route') + route = post_params.get('redirect_url') # Checking if the thread id. exists for c in self.commands: - if c.has_key('id') and c['id'] == op_id: + if c.getName() == op_id: raise Exception('Task id. already exists: {}'.format(op_id)) # Launching a new thread - thr = threading.Thread(name=op_id, target=self.task_command, args=(code, route, op_id)) + thr = threading.Thread(name=op_id, target=self.task_command, args=(script, route, op_id)) thr.start() - self.commands.append({'id': op_id, 'code': code}) + self.commands.append(thr) except Exception as e: logger.error('Got exception {}'.format(e)) return {'error': e} @@ -383,12 +384,14 @@ class OpenGnSysWorker(ServerWorker): :param server: :return: object """ - #data = [] - #for c in self.commands: - # if c.is_alive(): - # data.append({'name': c.getName(), 'code': c.__dict__['_Thread__args']}) - #return data - return self.commands + data = [] + logger.debug('Received execinfo operation') + self.checkSecret(server) + # Returning the arguments of all running threads + for c in self.commands: + if c.is_alive(): + data.append(c.__dict__['_Thread__args']) + return data def process_hardware(self, path, get_params, post_params, server): """ @@ -399,7 +402,7 @@ class OpenGnSysWorker(ServerWorker): :param server: """ data = [] - logger.debug('Recieved hardware operation') + logger.debug('Received hardware operation') self.checkSecret(server) # Processing data try: @@ -419,5 +422,5 @@ class OpenGnSysWorker(ServerWorker): :param server: :return: """ - logger.debug('Recieved software operation with params: {}'.format(post_params)) + logger.debug('Received software operation with params: {}'.format(post_params)) return operations.get_software(post_params.get('disk'), post_params.get('part')) From da9bd96ec83e4b8c3c4c9390008bbe93b9e88468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Thu, 5 Jul 2018 13:50:17 +0200 Subject: [PATCH 09/48] #750: Renaming server REST route {{{GET /done}}} to {{{GET /command_done}}} to log commands executed on clients. --- src/opengnsys/modules/server/OpenGnSys/__init__.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 1832652..abeeaae 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -364,7 +364,7 @@ class OpenGnSysWorker(ServerWorker): route = post_params.get('redirect_url') # Checking if the thread id. exists for c in self.commands: - if c.getName() == op_id: + if c.getName() == str(op_id): raise Exception('Task id. already exists: {}'.format(op_id)) # Launching a new thread thr = threading.Thread(name=op_id, target=self.task_command, args=(script, route, op_id)) @@ -393,6 +393,16 @@ class OpenGnSysWorker(ServerWorker): data.append(c.__dict__['_Thread__args']) return data + def process_stopcmd(self, path, get_params, post_params, server): + logger.debug('Received stopcmd operation with params {}:'.format(post_params)) + self.checkSecret(server) + op_id = post_params.get('trace') + for c in self.commands: + if c.is_alive() and c.getName() == str(op_id): + c._Thread__stop() + return {"stopped": op_id} + return {} + def process_hardware(self, path, get_params, post_params, server): """ Returns client's hardware profile From cf4de0c2d2e61fceac074219c7601a26c33b37df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Mon, 18 Jun 2018 13:54:44 +0200 Subject: [PATCH 10/48] #750: Process to build OGAgent for ogLive package. --- oglive/Makefile | 57 +++++ oglive/build-packages.sh | 11 + oglive/debian/changelog | 6 + oglive/debian/compat | 1 + oglive/debian/control | 15 ++ oglive/debian/copyright | 26 ++ oglive/debian/docs | 1 + oglive/debian/ogagent-oglive.links | 2 + oglive/debian/ogagent-oglive.postinst | 21 ++ .../debian/ogagent-oglive.postinst.debhelper | 5 + oglive/debian/ogagent-oglive.postrm | 10 + oglive/debian/ogagent-oglive.postrm.debhelper | 12 + oglive/debian/ogagent-oglive.substvars | 2 + oglive/debian/ogagent.init | 23 ++ oglive/debian/rules | 44 ++++ oglive/debian/source/format | 1 + oglive/readme.txt | 3 + oglive/scripts/OGAgentTool | 6 + oglive/scripts/OGAgentTool-startup | 10 + oglive/scripts/ogagent | 6 + src/opengnsys/linux/operations.py | 12 +- src/opengnsys/oglive/__init__.py | 32 +++ src/opengnsys/oglive/daemon.py | 182 ++++++++++++++ src/opengnsys/oglive/operations.py | 230 ++++++++++++++++++ src/opengnsys/operations.py | 12 +- 25 files changed, 717 insertions(+), 13 deletions(-) create mode 100644 oglive/Makefile create mode 100755 oglive/build-packages.sh create mode 100644 oglive/debian/changelog create mode 100644 oglive/debian/compat create mode 100644 oglive/debian/control create mode 100644 oglive/debian/copyright create mode 100644 oglive/debian/docs create mode 100644 oglive/debian/ogagent-oglive.links create mode 100644 oglive/debian/ogagent-oglive.postinst create mode 100644 oglive/debian/ogagent-oglive.postinst.debhelper create mode 100644 oglive/debian/ogagent-oglive.postrm create mode 100644 oglive/debian/ogagent-oglive.postrm.debhelper create mode 100644 oglive/debian/ogagent-oglive.substvars create mode 100644 oglive/debian/ogagent.init create mode 100755 oglive/debian/rules create mode 100644 oglive/debian/source/format create mode 100644 oglive/readme.txt create mode 100644 oglive/scripts/OGAgentTool create mode 100644 oglive/scripts/OGAgentTool-startup create mode 100644 oglive/scripts/ogagent create mode 100644 src/opengnsys/oglive/__init__.py create mode 100644 src/opengnsys/oglive/daemon.py create mode 100644 src/opengnsys/oglive/operations.py diff --git a/oglive/Makefile b/oglive/Makefile new file mode 100644 index 0000000..832e2dc --- /dev/null +++ b/oglive/Makefile @@ -0,0 +1,57 @@ +#!/usr/bin/make -f +# -*- makefile -*- + +# Directories +SOURCEDIR := ../src +LIBDIR := $(DESTDIR)/usr/share/OGAgent +BINDIR := $(DESTDIR)/usr/bin +SBINDIR = $(DESTDIR)/usr/sbin +APPSDIR := $(DESTDIR)/usr/share/applications +CFGDIR := $(DESTDIR)/etc/ogagent +INITDIR := $(DESTDIR)/etc/init.d + +PYC := $(shell find $(SOURCEDIR) -name '*.py[co]') +CACHES := $(shell find $(SOURCEDIR) -name '__pycache__') + +clean: + rm -rf $(PYC) $(CACHES) $(DESTDIR) +install-ogagent: + rm -rf $(DESTDIR) + mkdir -p $(LIBDIR) + mkdir -p $(BINDIR) + mkdir -p $(SBINDIR) + mkdir -p $(APPSDIR) + mkdir -p $(CFGDIR) + + mkdir $(LIBDIR)/img + + # Cleans up .pyc and cache folders + rm -f $(PYC) $(CACHES) + + cp -r $(SOURCEDIR)/opengnsys $(LIBDIR)/opengnsys + cp -r $(SOURCEDIR)/cfg $(LIBDIR)/cfg + + # scripts + cp scripts/ogagent $(BINDIR) + cp scripts/OGAgentTool-startup $(BINDIR) + cp scripts/OGAgentTool $(BINDIR) + + # Fix permissions + chmod 755 $(BINDIR)/ogagent + chmod 755 $(BINDIR)/OGAgentTool-startup + chmod 600 $(LIBDIR)/cfg/ogagent.cfg + + # If for red hat based, copy init.d +ifeq ($(DISTRO),rh) + mkdir -p $(INITDIR) + cp debian/ogagent.init $(INITDIR)/ogagent + chmod +x $(INITDIR)/ogagent + ln -fs /usr/share/OGAgent/cfg/ogagent.cfg $(CFGDIR) + ln -fs /usr/share/OGAgent/cfg/ogclient.cfg $(CFGDIR) +endif + + # chmod 0755 $(BINDIR)/ogagent +uninstall: + rm -rf $(LIBDIR) + # rm -f $(BINDIR)/ogagent + rm -rf $(CFGDIR) diff --git a/oglive/build-packages.sh b/oglive/build-packages.sh new file mode 100755 index 0000000..4d5eb8d --- /dev/null +++ b/oglive/build-packages.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +cd $(dirname "$0") +top=`pwd` + +[ -r ../src/VERSION ] && VERSION="$(cat ../src/VERSION)" || VERSION="1.1.0" +RELEASE="1" + +# Debian based +dpkg-buildpackage -b -d + diff --git a/oglive/debian/changelog b/oglive/debian/changelog new file mode 100644 index 0000000..57a05ab --- /dev/null +++ b/oglive/debian/changelog @@ -0,0 +1,6 @@ +ogagent-oglive (1.1.1) unstable; urgency=medium + + * Initial release. + + -- Ramón M. Gómez Mon, 18 Jun 2018 13:00:00 +0200 + diff --git a/oglive/debian/compat b/oglive/debian/compat new file mode 100644 index 0000000..f11c82a --- /dev/null +++ b/oglive/debian/compat @@ -0,0 +1 @@ +9 \ No newline at end of file diff --git a/oglive/debian/control b/oglive/debian/control new file mode 100644 index 0000000..374bc55 --- /dev/null +++ b/oglive/debian/control @@ -0,0 +1,15 @@ +Source: ogagent-oglive +Section: admin +Priority: optional +Maintainer: Ramón M. Gómez +Build-Depends: debhelper (>= 7), po-debconf +Standards-Version: 3.9.2 +Homepage: https://opengnsys.es + +Package: ogagent-oglive +Section: admin +Priority: optional +Architecture: all +Depends: python-requests (>=0.8.2), python-six(>=1.1), python-prctl(>=1.1.1), python (>=2.7), libxss1, ${misc:Depends} +Description: OpenGnsys Agent for ogLive client + This package provides the required components to allow this machine to work on an environment managed by OpenGnsys. diff --git a/oglive/debian/copyright b/oglive/debian/copyright new file mode 100644 index 0000000..7b6ef31 --- /dev/null +++ b/oglive/debian/copyright @@ -0,0 +1,26 @@ +Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135 +Name: ogagent +Maintainer: Ramón M. Gómez +Source: https://opengnsys.es + +Copyright: 2014 Virtual Cable S.L.U. +License: BSD-3-clause + +License: GPL-2+ +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. +. +On Debian systems, the full text of the GNU General Public +License version 2 can be found in the file +`/usr/share/common-licenses/GPL-2'. diff --git a/oglive/debian/docs b/oglive/debian/docs new file mode 100644 index 0000000..b2b2a78 --- /dev/null +++ b/oglive/debian/docs @@ -0,0 +1 @@ +readme.txt diff --git a/oglive/debian/ogagent-oglive.links b/oglive/debian/ogagent-oglive.links new file mode 100644 index 0000000..9b970d7 --- /dev/null +++ b/oglive/debian/ogagent-oglive.links @@ -0,0 +1,2 @@ +/usr/share/OGAgent/cfg/ogagent.cfg /etc/ogagent/ogagent.cfg +/usr/share/OGAgent/cfg/ogclient.cfg /etc/ogagent/ogclient.cfg diff --git a/oglive/debian/ogagent-oglive.postinst b/oglive/debian/ogagent-oglive.postinst new file mode 100644 index 0000000..b59cfa6 --- /dev/null +++ b/oglive/debian/ogagent-oglive.postinst @@ -0,0 +1,21 @@ +#!/bin/sh + +. /usr/share/debconf/confmodule + +set -e +case "$1" in + configure) + chmod 600 /usr/share/OGAgent/cfg/ogagent.cfg + ;; + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 diff --git a/oglive/debian/ogagent-oglive.postinst.debhelper b/oglive/debian/ogagent-oglive.postinst.debhelper new file mode 100644 index 0000000..e75924d --- /dev/null +++ b/oglive/debian/ogagent-oglive.postinst.debhelper @@ -0,0 +1,5 @@ +# Automatically added by dh_installinit +if [ -x "/etc/init.d/ogagent" ]; then + update-rc.d ogagent defaults >/dev/null || exit $? +fi +# End automatically added section diff --git a/oglive/debian/ogagent-oglive.postrm b/oglive/debian/ogagent-oglive.postrm new file mode 100644 index 0000000..a46fa48 --- /dev/null +++ b/oglive/debian/ogagent-oglive.postrm @@ -0,0 +1,10 @@ +#!/bin/sh -e + +. /usr/share/debconf/confmodule + +set -e + +if [ "$1" = "purge" ] ; then + rm -rf /usr/share/OGAgent || true > /dev/null 2>&1 +fi + diff --git a/oglive/debian/ogagent-oglive.postrm.debhelper b/oglive/debian/ogagent-oglive.postrm.debhelper new file mode 100644 index 0000000..3167f1f --- /dev/null +++ b/oglive/debian/ogagent-oglive.postrm.debhelper @@ -0,0 +1,12 @@ +# Automatically added by dh_installinit +if [ "$1" = "purge" ] ; then + update-rc.d ogagent remove >/dev/null +fi + + +# In case this system is running systemd, we make systemd reload the unit files +# to pick up changes. +if [ -d /run/systemd/system ] ; then + systemctl --system daemon-reload >/dev/null || true +fi +# End automatically added section diff --git a/oglive/debian/ogagent-oglive.substvars b/oglive/debian/ogagent-oglive.substvars new file mode 100644 index 0000000..978fc8b --- /dev/null +++ b/oglive/debian/ogagent-oglive.substvars @@ -0,0 +1,2 @@ +misc:Depends= +misc:Pre-Depends= diff --git a/oglive/debian/ogagent.init b/oglive/debian/ogagent.init new file mode 100644 index 0000000..3eee35c --- /dev/null +++ b/oglive/debian/ogagent.init @@ -0,0 +1,23 @@ +#!/bin/sh -e +### BEGIN INIT INFO +# Provides: ogagent +# Required-Start: $local_fs $remote_fs $network $syslog $named +# Required-Stop: $local_fs $remote_fs $network $syslog $named +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: OpenGnsys Agent Service +### END INIT INFO +# + +# . /lib/lsb/init-functions + +case "$1" in + start|stop|restart) + /usr/bin/ogagent $1 + ;; + force-reload) + /usr/bin/ogagent restart + ;; + *) echo "Usage: $0 {start|stop|restart|force-reload}" >&2; exit 1 ;; +esac + diff --git a/oglive/debian/rules b/oglive/debian/rules new file mode 100755 index 0000000..0a5c3e5 --- /dev/null +++ b/oglive/debian/rules @@ -0,0 +1,44 @@ +#!/usr/bin/make -f +# -*- makefile -*- +configure: configure-stamp +configure-stamp: + dh_testdir + touch configure-stamp +build: build-arch build-indep +build-arch: build-stamp +build-indep: build-stamp +build-stamp: configure-stamp + dh_testdir + $(MAKE) + touch $@ +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + dh_clean +install: build + dh_testdir + dh_testroot + dh_prep + dh_installdirs + $(MAKE) DESTDIR=$(CURDIR)/debian/ogagent-oglive install-ogagent +binary-arch: build install + # emptyness +binary-indep: build install + dh_testdir + dh_testroot + dh_installchangelogs + dh_installdocs + dh_installdebconf + dh_installinit --no-start + dh_python2=python + dh_compress + dh_link + dh_fixperms + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb +binary: binary-indep +.PHONY: build clean binary-indep binary install configure diff --git a/oglive/debian/source/format b/oglive/debian/source/format new file mode 100644 index 0000000..9f67427 --- /dev/null +++ b/oglive/debian/source/format @@ -0,0 +1 @@ +3.0 (native) \ No newline at end of file diff --git a/oglive/readme.txt b/oglive/readme.txt new file mode 100644 index 0000000..a2771de --- /dev/null +++ b/oglive/readme.txt @@ -0,0 +1,3 @@ +OGAgent is the agent intended for OpengGnsys interaction. + +Please, visit https://opengnsys.es for more information diff --git a/oglive/scripts/OGAgentTool b/oglive/scripts/OGAgentTool new file mode 100644 index 0000000..5b30052 --- /dev/null +++ b/oglive/scripts/OGAgentTool @@ -0,0 +1,6 @@ +#!/bin/sh + +FOLDER=/usr/share/OGAgent + +cd $FOLDER +python OGAgentUser.py $@ diff --git a/oglive/scripts/OGAgentTool-startup b/oglive/scripts/OGAgentTool-startup new file mode 100644 index 0000000..bb3a848 --- /dev/null +++ b/oglive/scripts/OGAgentTool-startup @@ -0,0 +1,10 @@ +#!/bin/sh + +# Simple hack to wait for systray to be present +# Exec tool if not already runned by session manager +ps -ef | grep "$USER" | grep -v grep | grep -v OGAgentTool-startup | grep 'OGAgentTool' -q +# If not already running +if [ $? -eq 1 ]; then + sleep 5 + exec /usr/bin/OGAgentTool +fi \ No newline at end of file diff --git a/oglive/scripts/ogagent b/oglive/scripts/ogagent new file mode 100644 index 0000000..1bcc29b --- /dev/null +++ b/oglive/scripts/ogagent @@ -0,0 +1,6 @@ +#!/bin/sh + +FOLDER=/usr/share/OGAgent + +cd $FOLDER +python -m opengnsys.linux.OGAgentService $@ diff --git a/src/opengnsys/linux/operations.py b/src/opengnsys/linux/operations.py index 0c08f95..db6ea18 100644 --- a/src/opengnsys/linux/operations.py +++ b/src/opengnsys/linux/operations.py @@ -152,11 +152,7 @@ def reboot(flags=0): import threading threading._DummyThread._Thread__stop = lambda x: 42 - # Check for OpenGnsys Client or GNU/Linux distribution. - if os.path.exists('/scripts/oginit'): - subprocess.call('source /opt/opengnsys/etc/preinit/loadenviron.sh; /opt/opengnsys/scripts/reboot', shell=True) - else: - subprocess.call(['/sbin/reboot']) + subprocess.call(['/sbin/reboot']) def poweroff(flags=0): @@ -168,11 +164,7 @@ def poweroff(flags=0): import threading threading._DummyThread._Thread__stop = lambda x: 42 - # Check for OpenGnsys Client or GNU/Linux distribution. - if os.path.exists('/scripts/oginit'): - subprocess.call('source /opt/opengnsys/etc/preinit/loadenviron.sh; /opt/opengnsys/scripts/poweroff', shell=True) - else: - subprocess.call(['/sbin/poweroff']) + subprocess.call(['/sbin/poweroff']) def logoff(): diff --git a/src/opengnsys/oglive/__init__.py b/src/opengnsys/oglive/__init__.py new file mode 100644 index 0000000..3a98c78 --- /dev/null +++ b/src/opengnsys/oglive/__init__.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2014 Virtual Cable S.L. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Virtual Cable S.L. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +''' +@author: Adolfo Gómez, dkmaster at dkmon dot com +''' +from __future__ import unicode_literals diff --git a/src/opengnsys/oglive/daemon.py b/src/opengnsys/oglive/daemon.py new file mode 100644 index 0000000..3753808 --- /dev/null +++ b/src/opengnsys/oglive/daemon.py @@ -0,0 +1,182 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2014 Virtual Cable S.L. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Virtual Cable S.L. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +''' +@author: : http://www.jejik.com/authors/sander_marechal/ +@see: : http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ +''' + +from __future__ import unicode_literals +import sys +import os +import time +import atexit +from opengnsys.log import logger + +from signal import SIGTERM + + +class Daemon: + """ + A generic daemon class. + + Usage: subclass the Daemon class and override the run() method + """ + def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): + self.stdin = stdin + self.stdout = stdout + self.stderr = stderr + self.pidfile = pidfile + + def daemonize(self): + """ + do the UNIX double-fork magic, see Stevens' "Advanced + Programming in the UNIX Environment" for details (ISBN 0201563177) + http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 + """ + try: + pid = os.fork() + if pid > 0: + # exit first parent + sys.exit(0) + except OSError as e: + logger.error("fork #1 error: {}".format(e)) + sys.stderr.write("fork #1 failed: {}\n".format(e)) + sys.exit(1) + + # decouple from parent environment + os.chdir("/") + os.setsid() + os.umask(0) + + # do second fork + try: + pid = os.fork() + if pid > 0: + # exit from second parent + sys.exit(0) + except OSError as e: + logger.error("fork #2 error: {}".format(e)) + sys.stderr.write("fork #2 failed: {}\n".format(e)) + sys.exit(1) + + # redirect standard file descriptors + sys.stdout.flush() + sys.stderr.flush() + si = open(self.stdin, 'r') + so = open(self.stdout, 'a+') + se = open(self.stderr, 'a+', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + # write pidfile + atexit.register(self.delpid) + pid = str(os.getpid()) + with open(self.pidfile, 'w+') as f: + f.write("{}\n".format(pid)) + + def delpid(self): + try: + os.remove(self.pidfile) + except Exception: + # Not found/not permissions or whatever... + pass + + def start(self): + """ + Start the daemon + """ + logger.debug('Starting daemon') + # Check for a pidfile to see if the daemon already runs + try: + pf = open(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if pid: + message = "pidfile {} already exist. Daemon already running?\n".format(pid) + logger.error(message) + sys.stderr.write(message) + sys.exit(1) + + # Start the daemon + self.daemonize() + try: + self.run() + except Exception as e: + logger.error('Exception running process: {}'.format(e)) + + if os.path.exists(self.pidfile): + os.remove(self.pidfile) + + def stop(self): + """ + Stop the daemon + """ + # Get the pid from the pidfile + try: + pf = open(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if pid is None: + message = "pidfile {} does not exist. Daemon not running?\n".format(self.pidfile) + logger.info(message) + # sys.stderr.write(message) + return # not an error in a restart + + # Try killing the daemon process + try: + for i in range(10): + os.kill(pid, SIGTERM) + time.sleep(1) + except OSError as err: + if err.errno == 3: # No such process + if os.path.exists(self.pidfile): + os.remove(self.pidfile) + else: + sys.stderr.write(err) + sys.exit(1) + + def restart(self): + """ + Restart the daemon + """ + self.stop() + self.start() + + # Overridables + def run(self): + """ + You should override this method when you subclass Daemon. It will be called after the process has been + daemonized by start() or restart(). + """ diff --git a/src/opengnsys/oglive/operations.py b/src/opengnsys/oglive/operations.py new file mode 100644 index 0000000..ded4379 --- /dev/null +++ b/src/opengnsys/oglive/operations.py @@ -0,0 +1,230 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2014 Virtual Cable S.L. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Virtual Cable S.L. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +''' +@author: Ramón M. Gómez, ramongomez at us dot es +''' +from __future__ import unicode_literals + +import socket +import platform +import fcntl +import os +import ctypes # @UnusedImport +import ctypes.util +import subprocess +import struct +import array +import six +from opengnsys import utils + + +def checkLockedPartition(sync=False): + ''' + Decorator to check if a partition is locked + ''' + def outer(fnc): + def wrapper(*args, **kwargs): + partId = 'None' + try: + this, path, getParams, postParams = args # @UnusedVariable + partId = postParams['disk'] + postParams['part'] + if this.locked.get(partId, False): + this.locked[partId] = True + fnc(*args, **kwargs) + else: + return 'partition locked' + except Exception as e: + this.locked[partId] = False + return 'error {}'.format(e) + finally: + if sync is True: + this.locked[partId] = False + logger.debug('Lock status: {} {}'.format(fnc, this.locked)) + return wrapper + return outer + + +def _getMacAddr(ifname): + ''' + Returns the mac address of an interface + Mac is returned as unicode utf-8 encoded + ''' + if isinstance(ifname, list): + return dict([(name, _getMacAddr(name)) for name in ifname]) + if isinstance(ifname, six.text_type): + ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7) + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + info = bytearray(fcntl.ioctl(s.fileno(), 0x8927, struct.pack(str('256s'), ifname[:15]))) + return six.text_type(''.join(['%02x:' % char for char in info[18:24]])[:-1]) + except Exception: + return None + + +def _getIpAddr(ifname): + ''' + Returns the ip address of an interface + Ip is returned as unicode utf-8 encoded + ''' + if isinstance(ifname, list): + return dict([(name, _getIpAddr(name)) for name in ifname]) + if isinstance(ifname, six.text_type): + ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7) + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + return six.text_type(socket.inet_ntoa(fcntl.ioctl( + s.fileno(), + 0x8915, # SIOCGIFADDR + struct.pack(str('256s'), ifname[:15]) + )[20:24])) + except Exception: + return None + + +def _getInterfaces(): + ''' + Returns a list of interfaces names coded in utf-8 + ''' + max_possible = 128 # arbitrary. raise if needed. + space = max_possible * 16 + if platform.architecture()[0] == '32bit': + offset, length = 32, 32 + elif platform.architecture()[0] == '64bit': + offset, length = 16, 40 + else: + raise OSError('Unknown arquitecture {0}'.format(platform.architecture()[0])) + + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + names = array.array(str('B'), b'\0' * space) + outbytes = struct.unpack(str('iL'), fcntl.ioctl( + s.fileno(), + 0x8912, # SIOCGIFCONF + struct.pack(str('iL'), space, names.buffer_info()[0]) + ))[0] + namestr = names.tostring() + # return namestr, outbytes + return [namestr[i:i + offset].split(b'\0', 1)[0].decode('utf-8') for i in range(0, outbytes, length)] + + +def _getIpAndMac(ifname): + ip, mac = _getIpAddr(ifname), _getMacAddr(ifname) + return (ip, mac) + + +def _exec_ogcommand(self, ogcmd): + ''' + Loads OpenGnsys environment variables, executes the command and returns the result + ''' + ret = subprocess.check_output('source /opt/opengnsys/etc/preinit/loadenviron.sh >/dev/null; {}'.format(ogcmd), shell=True) + return ret + + +def getComputerName(): + ''' + Returns computer name, with no domain + ''' + return socket.gethostname().split('.')[0] + + +def getNetworkInfo(): + ''' + Obtains a list of network interfaces + @return: A "generator" of elements, that are dict-as-object, with this elements: + name: Name of the interface + mac: mac of the interface + ip: ip of the interface + ''' + for ifname in _getInterfaces(): + ip, mac = _getIpAndMac(ifname) + if mac != '00:00:00:00:00:00': # Skips local interfaces + yield utils.Bunch(name=ifname, mac=mac, ip=ip) + + +def getDomainName(): + return '' + + +def getOgliveVersion(): + lv = platform.linux_distribution() + return lv[0] + ', ' + lv[1] + + +def reboot(): + ''' + Simple reboot using OpenGnsys script + ''' + # Workaround for dummy thread + if six.PY3 is False: + import threading + threading._DummyThread._Thread__stop = lambda x: 42 + + _exec_ogcommand('/opt/opengnsys/scripts/reboot', shell=True) + + +def poweroff(): + ''' + Simple poweroff using OpenGnsys script + ''' + # Workaround for dummy thread + if six.PY3 is False: + import threading + threading._DummyThread._Thread__stop = lambda x: 42 + + _exec_ogcommand('/opt/opengnsys/scripts/poweroff', shell=True) + + +def logoff(): + pass + + +def renameComputer(newName): + pass + + +def joinDomain(domain, ou, account, password, executeInOneStep=False): + pass + + +def changeUserPassword(user, oldPassword, newPassword): + pass + + +def diskconfig(): + ''' + Returns disk configuration. + Warning: this operation may take some time. + ''' + try: + _exec_ogcommand('/opt/opengnsys/interfaceAdm/getConfiguration') + # Returns content of configuration file. + cfgdata = open('/tmp/getconfig', 'r').read() + except IOError: + cfgdata = '' + return cfgdata + diff --git a/src/opengnsys/operations.py b/src/opengnsys/operations.py index 1a274b2..218d8df 100644 --- a/src/opengnsys/operations.py +++ b/src/opengnsys/operations.py @@ -33,6 +33,7 @@ from __future__ import unicode_literals import sys +import os # Importing platform operations and getting operating system data. if sys.platform == 'win32': @@ -45,6 +46,11 @@ else: os_type = 'MacOS' os_version = getMacosVersion().replace(',', '') else: - from .linux.operations import * # @UnusedWildImport - os_type = 'Linux' - os_version = getLinuxVersion().replace(',', '') + if os.path.exists('/scripts/oginit'): + from .oglive.operations import * # @UnusedWildImport + osType = 'ogLive' + osVersion = getOgliveVersion().replace(',','') + else: + from .linux.operations import * # @UnusedWildImport + osType = 'Linux' + osVersion = getLinuxVersion().replace(',','') From 70ce2377da0b419aa88efd4b8bb92426d94b48f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Mon, 18 Jun 2018 20:47:35 +0200 Subject: [PATCH 11/48] #750: Using PEP 8 Style Guide for Python in new module; implementing basic {{{getconfig}}} operation. --- oglive/build-package.sh | 7 ++ oglive/build-packages.sh | 11 -- .../modules/server/OpenGnSys/__init__.py | 24 +++- src/opengnsys/oglive/operations.py | 113 ++++++------------ src/opengnsys/operations.py | 10 +- 5 files changed, 67 insertions(+), 98 deletions(-) create mode 100755 oglive/build-package.sh delete mode 100755 oglive/build-packages.sh diff --git a/oglive/build-package.sh b/oglive/build-package.sh new file mode 100755 index 0000000..c0e03c5 --- /dev/null +++ b/oglive/build-package.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +cd $(dirname "$0") + +# Build package +dpkg-buildpackage -b -d + diff --git a/oglive/build-packages.sh b/oglive/build-packages.sh deleted file mode 100755 index 4d5eb8d..0000000 --- a/oglive/build-packages.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -cd $(dirname "$0") -top=`pwd` - -[ -r ../src/VERSION ] && VERSION="$(cat ../src/VERSION)" || VERSION="1.1.0" -RELEASE="1" - -# Debian based -dpkg-buildpackage -b -d - diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 24d69ee..e175bcf 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -30,20 +30,20 @@ """ from __future__ import unicode_literals -import threading import os -import platform -import time import random import shutil import string +import threading +import time import urllib -from opengnsys.workers import ServerWorker -from opengnsys import REST, RESTError +from opengnsys import REST from opengnsys import operations from opengnsys.log import logger from opengnsys.scriptThread import ScriptExecutorThread +from opengnsys.workers import ServerWorker + # Check authorization header decorator @@ -313,3 +313,17 @@ class OpenGnSysWorker(ServerWorker): def process_client_popup(self, params): self.REST.sendMessage('popup_done', params) + + def process_getconfig(self, path, get_params, post_params, server): + """ + Returns client configuration + :param path: + :param get_params: + :param post_params: + :param server: + :return: object + """ + logger.debug('Recieved getconfig operation') + self.checkSecret(server) + # Returns raw data + return {'config': operations.get_disk_config()} diff --git a/src/opengnsys/oglive/operations.py b/src/opengnsys/oglive/operations.py index ded4379..6c67421 100644 --- a/src/opengnsys/oglive/operations.py +++ b/src/opengnsys/oglive/operations.py @@ -26,17 +26,14 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -''' +""" @author: Ramón M. Gómez, ramongomez at us dot es -''' +""" from __future__ import unicode_literals import socket import platform import fcntl -import os -import ctypes # @UnusedImport -import ctypes.util import subprocess import struct import array @@ -44,37 +41,11 @@ import six from opengnsys import utils -def checkLockedPartition(sync=False): - ''' - Decorator to check if a partition is locked - ''' - def outer(fnc): - def wrapper(*args, **kwargs): - partId = 'None' - try: - this, path, getParams, postParams = args # @UnusedVariable - partId = postParams['disk'] + postParams['part'] - if this.locked.get(partId, False): - this.locked[partId] = True - fnc(*args, **kwargs) - else: - return 'partition locked' - except Exception as e: - this.locked[partId] = False - return 'error {}'.format(e) - finally: - if sync is True: - this.locked[partId] = False - logger.debug('Lock status: {} {}'.format(fnc, this.locked)) - return wrapper - return outer - - def _getMacAddr(ifname): - ''' + """ Returns the mac address of an interface Mac is returned as unicode utf-8 encoded - ''' + """ if isinstance(ifname, list): return dict([(name, _getMacAddr(name)) for name in ifname]) if isinstance(ifname, six.text_type): @@ -88,10 +59,10 @@ def _getMacAddr(ifname): def _getIpAddr(ifname): - ''' + """ Returns the ip address of an interface Ip is returned as unicode utf-8 encoded - ''' + """ if isinstance(ifname, list): return dict([(name, _getIpAddr(name)) for name in ifname]) if isinstance(ifname, six.text_type): @@ -108,9 +79,9 @@ def _getIpAddr(ifname): def _getInterfaces(): - ''' + """ Returns a list of interfaces names coded in utf-8 - ''' + """ max_possible = 128 # arbitrary. raise if needed. space = max_possible * 16 if platform.architecture()[0] == '32bit': @@ -134,32 +105,32 @@ def _getInterfaces(): def _getIpAndMac(ifname): ip, mac = _getIpAddr(ifname), _getMacAddr(ifname) - return (ip, mac) + return ip, mac -def _exec_ogcommand(self, ogcmd): - ''' +def _exec_ogcommand(ogcmd): + """ Loads OpenGnsys environment variables, executes the command and returns the result - ''' - ret = subprocess.check_output('source /opt/opengnsys/etc/preinit/loadenviron.sh >/dev/null; {}'.format(ogcmd), shell=True) + """ + ret = subprocess.check_output(ogcmd, shell=True) return ret def getComputerName(): - ''' + """ Returns computer name, with no domain - ''' + """ return socket.gethostname().split('.')[0] def getNetworkInfo(): - ''' + """ Obtains a list of network interfaces - @return: A "generator" of elements, that are dict-as-object, with this elements: + :return: A "generator" of elements, that are dict-as-object, with this elements: name: Name of the interface mac: mac of the interface ip: ip of the interface - ''' + """ for ifname in _getInterfaces(): ip, mac = _getIpAndMac(ifname) if mac != '00:00:00:00:00:00': # Skips local interfaces @@ -170,56 +141,44 @@ def getDomainName(): return '' -def getOgliveVersion(): - lv = platform.linux_distribution() - return lv[0] + ', ' + lv[1] +def get_oglive_version(): + """ + Returns ogLive Kernel version and architecture + :return: kernel version + """ + kv = platform.os.uname() + return kv[2] + ', ' + kv[4] def reboot(): - ''' + """ Simple reboot using OpenGnsys script - ''' + """ # Workaround for dummy thread if six.PY3 is False: import threading threading._DummyThread._Thread__stop = lambda x: 42 - _exec_ogcommand('/opt/opengnsys/scripts/reboot', shell=True) + _exec_ogcommand('/opt/opengnsys/scripts/reboot') def poweroff(): - ''' + """ Simple poweroff using OpenGnsys script - ''' + """ # Workaround for dummy thread if six.PY3 is False: import threading threading._DummyThread._Thread__stop = lambda x: 42 - _exec_ogcommand('/opt/opengnsys/scripts/poweroff', shell=True) + _exec_ogcommand('/opt/opengnsys/scripts/poweroff') -def logoff(): - pass - - -def renameComputer(newName): - pass - - -def joinDomain(domain, ou, account, password, executeInOneStep=False): - pass - - -def changeUserPassword(user, oldPassword, newPassword): - pass - - -def diskconfig(): - ''' - Returns disk configuration. - Warning: this operation may take some time. - ''' +def get_disk_config(): + """ + Returns disk configuration + Warning: this operation may take some time + """ try: _exec_ogcommand('/opt/opengnsys/interfaceAdm/getConfiguration') # Returns content of configuration file. diff --git a/src/opengnsys/operations.py b/src/opengnsys/operations.py index 218d8df..2798cb1 100644 --- a/src/opengnsys/operations.py +++ b/src/opengnsys/operations.py @@ -27,7 +27,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ -@author: Adolfo Gómez, dkmaster at dkmon dot com +@author: Ramón M. Gómez, ramongomez at us dot es """ # pylint: disable=unused-wildcard-import,wildcard-import @@ -48,9 +48,9 @@ else: else: if os.path.exists('/scripts/oginit'): from .oglive.operations import * # @UnusedWildImport - osType = 'ogLive' - osVersion = getOgliveVersion().replace(',','') + os_type = 'ogLive' + os_version = get_oglive_version() else: from .linux.operations import * # @UnusedWildImport - osType = 'Linux' - osVersion = getLinuxVersion().replace(',','') + os_type = 'Linux' + os_version = getLinuxVersion().replace(',', '') From 149d1bdc4fe603a9e02dd6d19a31faba4c56d4bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Wed, 20 Jun 2018 19:48:49 +0200 Subject: [PATCH 12/48] #750: OGAgent for ogLive looks for {{{oglive}}} environ variable; route {{{GET /getconfig}}} returns data in JSON format. --- .../modules/server/OpenGnSys/__init__.py | 40 ++++++++++++++++++- src/opengnsys/oglive/operations.py | 5 +-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index e175bcf..c6bd951 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -43,6 +43,7 @@ from opengnsys import operations from opengnsys.log import logger from opengnsys.scriptThread import ScriptExecutorThread from opengnsys.workers import ServerWorker +from six.moves.urllib import parse @@ -94,6 +95,10 @@ class OpenGnSysWorker(ServerWorker): self.random = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(self.length)) # Ensure cfg has required configuration variables or an exception will be thrown url = self.service.config.get('opengnsys', 'remote') + if operations.os_type == 'ogLive' and 'oglive' in os.environ: + # Replacing server IP if its running on ogLive clinet + logger.debug('Activating on ogLive client, new server is {}'.format(os.environ['oglive'])) + url = parse.urlsplit(url)._replace(netloc=os.environ['oglive']).geturl() self.REST = REST(url) # Get network interfaces until they are active or timeout (5 minutes) for t in range(0, 300): @@ -323,7 +328,38 @@ class OpenGnSysWorker(ServerWorker): :param server: :return: object """ + serialno = '' # Serial number + storage = [] # Storage configuration + warnings = 0 # Number of warnings logger.debug('Recieved getconfig operation') self.checkSecret(server) - # Returns raw data - return {'config': operations.get_disk_config()} + # Processing data + for row in operations.get_disk_config().strip().split(';'): + cols = row.split(':') + if len(cols) == 1: + if cols[0] != '': + # Serial number + serialno = cols[0] + else: + # Skip blank rows + pass + elif len(cols) == 7: + disk, npart, tpart, fs, os, size, usage = cols + try: + if int(npart) == 0: + # Disk information + storage.append({'disk': int(disk), 'parttable': int(tpart), 'size': int(size)}) + else: + # Partition information + storage.append({'disk': int(disk), 'partition': int(npart), 'parttype': tpart, + 'filesystem': fs, 'operatingsystem': os, 'size': int(size), + 'usage': int(usage)}) + except ValueError: + logger.warn('Configuration parameter error: {}'.format(cols)) + warnings += 1 + else: + # Logging warnings + logger.warn('Configuration data error: {}'.format(cols)) + warnings += 1 + # Returning configuration data and count of warnings + return {'serialno': serialno, 'storage': storage, 'warnings': warnings} diff --git a/src/opengnsys/oglive/operations.py b/src/opengnsys/oglive/operations.py index 6c67421..e24e0b9 100644 --- a/src/opengnsys/oglive/operations.py +++ b/src/opengnsys/oglive/operations.py @@ -181,9 +181,8 @@ def get_disk_config(): """ try: _exec_ogcommand('/opt/opengnsys/interfaceAdm/getConfiguration') - # Returns content of configuration file. - cfgdata = open('/tmp/getconfig', 'r').read() + # Returns content of configuration file + cfgdata = open('/tmp/getconfig', 'r').read().strip() except IOError: cfgdata = '' return cfgdata - From a5905575e2eed65050d72832b49e950c8c773005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Wed, 20 Jun 2018 20:25:30 +0200 Subject: [PATCH 13/48] #750: New route {{{GET /hardware}}}. --- .../modules/server/OpenGnSys/__init__.py | 15 ++++++++++++++- src/opengnsys/oglive/operations.py | 18 ++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index c6bd951..9e8d8ef 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -334,7 +334,7 @@ class OpenGnSysWorker(ServerWorker): logger.debug('Recieved getconfig operation') self.checkSecret(server) # Processing data - for row in operations.get_disk_config().strip().split(';'): + for row in operations.get_configuration().split(';'): cols = row.split(':') if len(cols) == 1: if cols[0] != '': @@ -363,3 +363,16 @@ class OpenGnSysWorker(ServerWorker): warnings += 1 # Returning configuration data and count of warnings return {'serialno': serialno, 'storage': storage, 'warnings': warnings} + + def process_hardware(self, path, get_params, post_params, server): + """ + Returns client's hardware profile + :param path: + :param get_params: + :param post_params: + :param server: + """ + logger.debug('Recieved {} operation'.format(path)) + self.checkSecret(server) + # Returning raw data + return operations.get_hardware() diff --git a/src/opengnsys/oglive/operations.py b/src/opengnsys/oglive/operations.py index e24e0b9..b22a607 100644 --- a/src/opengnsys/oglive/operations.py +++ b/src/opengnsys/oglive/operations.py @@ -174,9 +174,9 @@ def poweroff(): _exec_ogcommand('/opt/opengnsys/scripts/poweroff') -def get_disk_config(): +def get_configuration(): """ - Returns disk configuration + Returns client's configuration Warning: this operation may take some time """ try: @@ -186,3 +186,17 @@ def get_disk_config(): except IOError: cfgdata = '' return cfgdata + + +def get_hardware(): + """ + Returns client's hardware list + """ + try: + filepath = _exec_ogcommand('/opt/opengnsys/scripts/listHardwareInfo').strip() + # Returns content of configuration file, skipping the header line and newline characters + with open(filepath, 'r') as f: + harddata = map(str.strip, f.readlines()[1:]) + except IOError: + harddata = '' + return harddata From 9e81e9020c81c332a248edab39d3bc5b719dd3e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Thu, 21 Jun 2018 19:23:10 +0200 Subject: [PATCH 14/48] #750: Route {{{GET /getconfig}}} renamed as {{{GET /config}}}; route {{{GET /hardware}}} returns data in JSON format; new basic route {{{GET /software?disk=NDisk&part=NPart}}} --- .../modules/server/OpenGnSys/__init__.py | 27 ++++++++++++++++--- src/opengnsys/oglive/operations.py | 21 ++++++++++++++- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 9e8d8ef..45b0c6a 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -319,7 +319,7 @@ class OpenGnSysWorker(ServerWorker): def process_client_popup(self, params): self.REST.sendMessage('popup_done', params) - def process_getconfig(self, path, get_params, post_params, server): + def process_config(self, path, get_params, post_params, server): """ Returns client configuration :param path: @@ -372,7 +372,26 @@ class OpenGnSysWorker(ServerWorker): :param post_params: :param server: """ - logger.debug('Recieved {} operation'.format(path)) + data = [] + logger.debug('Recieved hardware operation') self.checkSecret(server) - # Returning raw data - return operations.get_hardware() + # Processing data + try: + for comp in operations.get_hardware(): + data.append({'component': comp.split('=')[0], 'value': comp.split('=')[1]}) + except: + pass + # Return list of hardware components + return data + + def process_software(self, path, get_params, post_params, server): + """ + Returns software profile installed on an operating system + :param path: + :param get_params: + :param post_params: + :param server: + :return: + """ + logger.debug('Recieved software operation with params: {}'.format(post_params)) + return operations.get_software(post_params['disk'], post_params['part']) diff --git a/src/opengnsys/oglive/operations.py b/src/opengnsys/oglive/operations.py index b22a607..5541780 100644 --- a/src/opengnsys/oglive/operations.py +++ b/src/opengnsys/oglive/operations.py @@ -164,7 +164,7 @@ def reboot(): def poweroff(): """ - Simple poweroff using OpenGnsys script + Simple power off using OpenGnsys script """ # Workaround for dummy thread if six.PY3 is False: @@ -178,6 +178,7 @@ def get_configuration(): """ Returns client's configuration Warning: this operation may take some time + :return: """ try: _exec_ogcommand('/opt/opengnsys/interfaceAdm/getConfiguration') @@ -191,6 +192,7 @@ def get_configuration(): def get_hardware(): """ Returns client's hardware list + :return: """ try: filepath = _exec_ogcommand('/opt/opengnsys/scripts/listHardwareInfo').strip() @@ -200,3 +202,20 @@ def get_hardware(): except IOError: harddata = '' return harddata + + +def get_software(disk, part): + """ + Returns software list installed on an operating system + :param disk: + :param part: + :return: + """ + try: + filepath = _exec_ogcommand('/opt/opengnsys/scripts/listSoftwareInfo {} {}'.format(disk, part)).strip() + # Returns content of configuration file, skipping the header line and newline characters + with open(filepath, 'r') as f: + softdata = map(str.strip, f.readlines()) + except IOError: + softdata = '' + return softdata From 2af37100e0448c5cff6aaebe38f216db886284ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Sat, 30 Jun 2018 17:13:45 +0200 Subject: [PATCH 15/48] #750: Using more descriptive status; new route {{{POST /command}}} to launch command/script in a callback thread that returns all data to a server route. --- .../modules/server/OpenGnSys/__init__.py | 63 +++++++++++++------ src/opengnsys/oglive/operations.py | 15 +++++ src/setup.py | 2 +- 3 files changed, 59 insertions(+), 21 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 45b0c6a..77e9755 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -218,23 +218,14 @@ class OpenGnSysWorker(ServerWorker): :param server: :return: JSON object {"status": "status_code", "loggedin": boolean} """ - res = {'status': '', 'loggedin': self.logged_in} - if platform.system() == 'Linux': # GNU/Linux - # Check if it's OpenGnsys Client. - if os.path.exists('/scripts/oginit'): - # Check if OpenGnsys Client is busy. - if self.locked: - res['status'] = 'BSY' - else: - res['status'] = 'OPG' - else: - # Check if there is an active session. - res['status'] = 'LNX' - elif platform.system() == 'Windows': # Windows - # Check if there is an active session. - res['status'] = 'WIN' - elif platform.system() == 'Darwin': # Mac OS X ?? - res['status'] = 'OSX' + res = {'loggedin': self.loggedin} + try: + res['status'] = operations.os_type.lower() + except KeyError: + res['status'] = '' + # Check if OpenGnsys Client is busy + if res['status'] == 'oglive' and self.locked: + res['status'] = 'busy' return res @check_secret @@ -344,7 +335,7 @@ class OpenGnSysWorker(ServerWorker): # Skip blank rows pass elif len(cols) == 7: - disk, npart, tpart, fs, os, size, usage = cols + disk, npart, tpart, fs, opsys, size, usage = cols try: if int(npart) == 0: # Disk information @@ -352,7 +343,7 @@ class OpenGnSysWorker(ServerWorker): else: # Partition information storage.append({'disk': int(disk), 'partition': int(npart), 'parttype': tpart, - 'filesystem': fs, 'operatingsystem': os, 'size': int(size), + 'filesystem': fs, 'operatingsystem': opsys, 'size': int(size), 'usage': int(usage)}) except ValueError: logger.warn('Configuration parameter error: {}'.format(cols)) @@ -364,6 +355,38 @@ class OpenGnSysWorker(ServerWorker): # Returning configuration data and count of warnings return {'serialno': serialno, 'storage': storage, 'warnings': warnings} + def task_command(self, code, route): + """ + Task to execute a command + :param code: Code to execute + :param route: server REST route to return results (including its parameters) + """ + (stat, out, err) = operations.exec_command(code) + self.REST.sendMessage(route, {'status': stat, 'output': out, 'error': err}) + + def process_command(self, path, get_params, post_params, server): + """ + Launches a thread to executing a command + :param path: ignored + :param get_params: ignored + :param post_params: object with format {"id": OperationId, "code": "Code", url: "ReturnURL"} + :param server: ignored + :rtype: object with launching status + """ + logger.debug('Recieved command operation with params: {}'.format(post_params)) + self.checkSecret(server) + # Processing data + try: + code = post_params.get('code') + cmd_id = post_params.get('id') + route = '{}?id={}'.format(post_params.get('route'), cmd_id) + # Launching new thread + threading.Thread(target=self.task_command, args=(code, route)).start() + except Exception as e: + logger.error('Got exception {}'.format(e)) + return {'error': e} + return {'op': 'launched'} + def process_hardware(self, path, get_params, post_params, server): """ Returns client's hardware profile @@ -394,4 +417,4 @@ class OpenGnSysWorker(ServerWorker): :return: """ logger.debug('Recieved software operation with params: {}'.format(post_params)) - return operations.get_software(post_params['disk'], post_params['part']) + return operations.get_software(post_params.get('disk'), post_params.get('part')) diff --git a/src/opengnsys/oglive/operations.py b/src/opengnsys/oglive/operations.py index 5541780..342cfb0 100644 --- a/src/opengnsys/oglive/operations.py +++ b/src/opengnsys/oglive/operations.py @@ -189,6 +189,21 @@ def get_configuration(): return cfgdata +def exec_command(cmd): + """ + Executing a shell command + :param cmd: + :return: object with components: + output: standard output + error: error output + exit: exit code + """ + proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (out, err) = proc.communicate() + stat = proc.returncode + return stat, out, err + + def get_hardware(): """ Returns client's hardware list diff --git a/src/setup.py b/src/setup.py index 15254f4..617d518 100644 --- a/src/setup.py +++ b/src/setup.py @@ -139,5 +139,5 @@ setup( description='OpenGnsys Agent', author='Adolfo Gomez', author_email='agomez@virtualcable.es', - zipfile='OGAgent.zip', + zipfile='OGAgent.zip', requires=['six'] ) From aab7ec58aa8a50ebcc9a9f747f231768809bcb47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Tue, 3 Jul 2018 17:55:25 +0200 Subject: [PATCH 16/48] #750: Simple REST route to get the list of running commands. --- .../modules/server/OpenGnSys/__init__.py | 57 +++++++++++++++---- src/opengnsys/operations.py | 2 +- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 77e9755..ce432b5 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -82,7 +82,8 @@ class OpenGnSysWorker(ServerWorker): interface = None # Bound interface for OpenGnsys REST = None # REST object logged_in = False # User session flag - locked = {} + locked = {} # Locked partitions + commands = [] # Running commands random = None # Random string for secure connections length = 32 # Random string length @@ -322,7 +323,7 @@ class OpenGnSysWorker(ServerWorker): serialno = '' # Serial number storage = [] # Storage configuration warnings = 0 # Number of warnings - logger.debug('Recieved getconfig operation') + logger.debug('Received getconfig operation') self.checkSecret(server) # Processing data for row in operations.get_configuration().split(';'): @@ -355,38 +356,70 @@ class OpenGnSysWorker(ServerWorker): # Returning configuration data and count of warnings return {'serialno': serialno, 'storage': storage, 'warnings': warnings} - def task_command(self, code, route): + def task_command(self, code, route, op_id): """ Task to execute a command :param code: Code to execute - :param route: server REST route to return results (including its parameters) + :param route: server callback REST route to return results + :param op_id: operation id. """ + # Executing command (stat, out, err) = operations.exec_command(code) - self.REST.sendMessage(route, {'status': stat, 'output': out, 'error': err}) + # Removing command from the list + for c in self.commands: + if c.has_key('id') and c['id'] == op_id: + self.commands.remove(c) + # Sending results + self.REST.sendMessage(route, {'id': op_id, 'status': stat, 'output': out, 'error': err}) def process_command(self, path, get_params, post_params, server): """ Launches a thread to executing a command :param path: ignored :param get_params: ignored - :param post_params: object with format {"id": OperationId, "code": "Code", url: "ReturnURL"} - :param server: ignored + :param post_params: object with format: + id: operation id. + code: command code + route: callback URL + :param server: headers data :rtype: object with launching status """ - logger.debug('Recieved command operation with params: {}'.format(post_params)) + logger.debug('Received command operation with params: {}'.format(post_params)) self.checkSecret(server) # Processing data try: code = post_params.get('code') - cmd_id = post_params.get('id') - route = '{}?id={}'.format(post_params.get('route'), cmd_id) - # Launching new thread - threading.Thread(target=self.task_command, args=(code, route)).start() + op_id = post_params.get('id') + route = post_params.get('route') + # Checking if the thread id. exists + for c in self.commands: + if c.has_key('id') and c['id'] == op_id: + raise Exception('Task id. already exists: {}'.format(op_id)) + # Launching a new thread + thr = threading.Thread(name=op_id, target=self.task_command, args=(code, route, op_id)) + thr.start() + self.commands.append({'id': op_id, 'code': code}) except Exception as e: logger.error('Got exception {}'.format(e)) return {'error': e} return {'op': 'launched'} + def process_execinfo(self, path, get_params, post_params, server): + """ + Returns running commands information + :param path: + :param get_params: + :param post_params: + :param server: + :return: object + """ + #data = [] + #for c in self.commands: + # if c.is_alive(): + # data.append({'name': c.getName(), 'code': c.__dict__['_Thread__args']}) + #return data + return self.commands + def process_hardware(self, path, get_params, post_params, server): """ Returns client's hardware profile diff --git a/src/opengnsys/operations.py b/src/opengnsys/operations.py index 2798cb1..4bba806 100644 --- a/src/opengnsys/operations.py +++ b/src/opengnsys/operations.py @@ -49,7 +49,7 @@ else: if os.path.exists('/scripts/oginit'): from .oglive.operations import * # @UnusedWildImport os_type = 'ogLive' - os_version = get_oglive_version() + os_version = get_oglive_version().replace(',', '') else: from .linux.operations import * # @UnusedWildImport os_type = 'Linux' From a0ce0fc9eaa1420d58ebd1cb3c92592c1a5bb1f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Wed, 4 Jul 2018 12:15:50 +0200 Subject: [PATCH 17/48] #750: Adapting route {{GET /command}}} parameters. --- .../modules/server/OpenGnSys/__init__.py | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index ce432b5..ee381de 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -91,9 +91,12 @@ class OpenGnSysWorker(ServerWorker): """ Sends OGAgent activation notification to OpenGnsys server """ +<<<<<<< HEAD t = 0 # Generate random secret to send on activation self.random = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(self.length)) +======= +>>>>>>> #750: Adapting route {{GET /command}}} parameters. # Ensure cfg has required configuration variables or an exception will be thrown url = self.service.config.get('opengnsys', 'remote') if operations.os_type == 'ogLive' and 'oglive' in os.environ: @@ -367,10 +370,11 @@ class OpenGnSysWorker(ServerWorker): (stat, out, err) = operations.exec_command(code) # Removing command from the list for c in self.commands: - if c.has_key('id') and c['id'] == op_id: + if c.getName() == op_id: self.commands.remove(c) # Sending results - self.REST.sendMessage(route, {'id': op_id, 'status': stat, 'output': out, 'error': err}) + self.REST.sendMessage(route, {'client': self.interface.ip, 'trace': op_id, 'status': stat, 'output': out, + 'error': err}) def process_command(self, path, get_params, post_params, server): """ @@ -379,8 +383,8 @@ class OpenGnSysWorker(ServerWorker): :param get_params: ignored :param post_params: object with format: id: operation id. - code: command code - route: callback URL + script: command code + redirect_url: callback REST route :param server: headers data :rtype: object with launching status """ @@ -388,17 +392,17 @@ class OpenGnSysWorker(ServerWorker): self.checkSecret(server) # Processing data try: - code = post_params.get('code') + script = post_params.get('script') op_id = post_params.get('id') - route = post_params.get('route') + route = post_params.get('redirect_url') # Checking if the thread id. exists for c in self.commands: - if c.has_key('id') and c['id'] == op_id: + if c.getName() == op_id: raise Exception('Task id. already exists: {}'.format(op_id)) # Launching a new thread - thr = threading.Thread(name=op_id, target=self.task_command, args=(code, route, op_id)) + thr = threading.Thread(name=op_id, target=self.task_command, args=(script, route, op_id)) thr.start() - self.commands.append({'id': op_id, 'code': code}) + self.commands.append(thr) except Exception as e: logger.error('Got exception {}'.format(e)) return {'error': e} @@ -413,12 +417,14 @@ class OpenGnSysWorker(ServerWorker): :param server: :return: object """ - #data = [] - #for c in self.commands: - # if c.is_alive(): - # data.append({'name': c.getName(), 'code': c.__dict__['_Thread__args']}) - #return data - return self.commands + data = [] + logger.debug('Received execinfo operation') + self.checkSecret(server) + # Returning the arguments of all running threads + for c in self.commands: + if c.is_alive(): + data.append(c.__dict__['_Thread__args']) + return data def process_hardware(self, path, get_params, post_params, server): """ @@ -429,7 +435,7 @@ class OpenGnSysWorker(ServerWorker): :param server: """ data = [] - logger.debug('Recieved hardware operation') + logger.debug('Received hardware operation') self.checkSecret(server) # Processing data try: @@ -449,5 +455,5 @@ class OpenGnSysWorker(ServerWorker): :param server: :return: """ - logger.debug('Recieved software operation with params: {}'.format(post_params)) + logger.debug('Received software operation with params: {}'.format(post_params)) return operations.get_software(post_params.get('disk'), post_params.get('part')) From 46d3308a5f20702e4dcbb279ef5407784cca2502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Thu, 5 Jul 2018 13:50:17 +0200 Subject: [PATCH 18/48] #750: Renaming server REST route {{{GET /done}}} to {{{GET /command_done}}} to log commands executed on clients. --- src/opengnsys/modules/server/OpenGnSys/__init__.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index ee381de..db855f8 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -397,7 +397,7 @@ class OpenGnSysWorker(ServerWorker): route = post_params.get('redirect_url') # Checking if the thread id. exists for c in self.commands: - if c.getName() == op_id: + if c.getName() == str(op_id): raise Exception('Task id. already exists: {}'.format(op_id)) # Launching a new thread thr = threading.Thread(name=op_id, target=self.task_command, args=(script, route, op_id)) @@ -426,6 +426,16 @@ class OpenGnSysWorker(ServerWorker): data.append(c.__dict__['_Thread__args']) return data + def process_stopcmd(self, path, get_params, post_params, server): + logger.debug('Received stopcmd operation with params {}:'.format(post_params)) + self.checkSecret(server) + op_id = post_params.get('trace') + for c in self.commands: + if c.is_alive() and c.getName() == str(op_id): + c._Thread__stop() + return {"stopped": op_id} + return {} + def process_hardware(self, path, get_params, post_params, server): """ Returns client's hardware profile From 01adfee4939e0cb250ce93533922b6b5332e6586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Mon, 29 Apr 2019 10:48:33 +0200 Subject: [PATCH 19/48] #761: OGAGent checks for dobule slash before connecting to REST URL. --- oglive/Makefile | 21 +++---------------- .../debian/ogagent-oglive.postinst.debhelper | 5 ----- oglive/debian/ogagent-oglive.postrm.debhelper | 12 ----------- oglive/scripts/OGAgentTool | 6 ------ oglive/scripts/OGAgentTool-startup | 10 --------- .../modules/server/OpenGnSys/__init__.py | 20 ++++++++++++++++++ 6 files changed, 23 insertions(+), 51 deletions(-) delete mode 100644 oglive/debian/ogagent-oglive.postinst.debhelper delete mode 100644 oglive/debian/ogagent-oglive.postrm.debhelper delete mode 100644 oglive/scripts/OGAgentTool delete mode 100644 oglive/scripts/OGAgentTool-startup diff --git a/oglive/Makefile b/oglive/Makefile index 832e2dc..84e0610 100644 --- a/oglive/Makefile +++ b/oglive/Makefile @@ -30,28 +30,13 @@ install-ogagent: cp -r $(SOURCEDIR)/opengnsys $(LIBDIR)/opengnsys cp -r $(SOURCEDIR)/cfg $(LIBDIR)/cfg + ln -fs $(LIBDIR)/cfg/ogagent.cfg $(CFGDIR) + ln -fs $(LIBDIR)/cfg/ogclient.cfg $(CFGDIR) - # scripts cp scripts/ogagent $(BINDIR) - cp scripts/OGAgentTool-startup $(BINDIR) - cp scripts/OGAgentTool $(BINDIR) - - # Fix permissions chmod 755 $(BINDIR)/ogagent - chmod 755 $(BINDIR)/OGAgentTool-startup - chmod 600 $(LIBDIR)/cfg/ogagent.cfg - # If for red hat based, copy init.d -ifeq ($(DISTRO),rh) - mkdir -p $(INITDIR) - cp debian/ogagent.init $(INITDIR)/ogagent - chmod +x $(INITDIR)/ogagent - ln -fs /usr/share/OGAgent/cfg/ogagent.cfg $(CFGDIR) - ln -fs /usr/share/OGAgent/cfg/ogclient.cfg $(CFGDIR) -endif - - # chmod 0755 $(BINDIR)/ogagent uninstall: rm -rf $(LIBDIR) - # rm -f $(BINDIR)/ogagent + rm -f $(BINDIR)/ogagent rm -rf $(CFGDIR) diff --git a/oglive/debian/ogagent-oglive.postinst.debhelper b/oglive/debian/ogagent-oglive.postinst.debhelper deleted file mode 100644 index e75924d..0000000 --- a/oglive/debian/ogagent-oglive.postinst.debhelper +++ /dev/null @@ -1,5 +0,0 @@ -# Automatically added by dh_installinit -if [ -x "/etc/init.d/ogagent" ]; then - update-rc.d ogagent defaults >/dev/null || exit $? -fi -# End automatically added section diff --git a/oglive/debian/ogagent-oglive.postrm.debhelper b/oglive/debian/ogagent-oglive.postrm.debhelper deleted file mode 100644 index 3167f1f..0000000 --- a/oglive/debian/ogagent-oglive.postrm.debhelper +++ /dev/null @@ -1,12 +0,0 @@ -# Automatically added by dh_installinit -if [ "$1" = "purge" ] ; then - update-rc.d ogagent remove >/dev/null -fi - - -# In case this system is running systemd, we make systemd reload the unit files -# to pick up changes. -if [ -d /run/systemd/system ] ; then - systemctl --system daemon-reload >/dev/null || true -fi -# End automatically added section diff --git a/oglive/scripts/OGAgentTool b/oglive/scripts/OGAgentTool deleted file mode 100644 index 5b30052..0000000 --- a/oglive/scripts/OGAgentTool +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -FOLDER=/usr/share/OGAgent - -cd $FOLDER -python OGAgentUser.py $@ diff --git a/oglive/scripts/OGAgentTool-startup b/oglive/scripts/OGAgentTool-startup deleted file mode 100644 index bb3a848..0000000 --- a/oglive/scripts/OGAgentTool-startup +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -# Simple hack to wait for systray to be present -# Exec tool if not already runned by session manager -ps -ef | grep "$USER" | grep -v grep | grep -v OGAgentTool-startup | grep 'OGAgentTool' -q -# If not already running -if [ $? -eq 1 ]; then - sleep 5 - exec /usr/bin/OGAgentTool -fi \ No newline at end of file diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index b20ed86..5073fa8 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -100,6 +100,8 @@ class OpenGnSysWorker(ServerWorker): # Replacing server IP if its running on ogLive clinet logger.debug('Activating on ogLive client, new server is {}'.format(os.environ['oglive'])) url = parse.urlsplit(url)._replace(netloc=os.environ['oglive']).geturl() + if not url.endswith(os.path.sep): + url += os.path.sep self.REST = REST(url) # Get network interfaces until they are active or timeout (5 minutes) for t in range(0, 300): @@ -152,6 +154,24 @@ class OpenGnSysWorker(ServerWorker): new_hosts_file = hosts_file + '.' + self.interface.ip.split('.')[0] if os.path.isfile(new_hosts_file): shutil.copyfile(new_hosts_file, hosts_file) + ### Separate in a different function to launch browser while catching disk configuration + # Create HTML file (TEMPORARY) + message = """ + + + +

Initializing...

+ + +""" + #f = open('/tmp/init.html', 'w') + #f.write(message) + #f.close() + # Launch browser + #subprocess.Popen(['browser', '-qws', '/tmp/init.html']) + #config = operations.get_configuration() + #self.REST.sendMessage('clients/config', {'mac': self.interface.mac, 'ip': self.interface.ip, + # 'config': config}) def onDeactivation(self): """ From 221bcd14ccb80f77a041386df7105841f816f68a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Thu, 23 May 2019 13:30:23 +0200 Subject: [PATCH 20/48] #908 OGAgent for ogLive code clean up. --- oglive/Makefile | 8 +-- src/OGAgent.manifest | 5 +- .../modules/server/OpenGnSys/__init__.py | 67 +++++++++++++++++-- 3 files changed, 67 insertions(+), 13 deletions(-) diff --git a/oglive/Makefile b/oglive/Makefile index 84e0610..707bc67 100644 --- a/oglive/Makefile +++ b/oglive/Makefile @@ -22,12 +22,12 @@ install-ogagent: mkdir -p $(SBINDIR) mkdir -p $(APPSDIR) mkdir -p $(CFGDIR) - + mkdir $(LIBDIR)/img - + # Cleans up .pyc and cache folders rm -f $(PYC) $(CACHES) - + cp -r $(SOURCEDIR)/opengnsys $(LIBDIR)/opengnsys cp -r $(SOURCEDIR)/cfg $(LIBDIR)/cfg ln -fs $(LIBDIR)/cfg/ogagent.cfg $(CFGDIR) @@ -35,7 +35,7 @@ install-ogagent: cp scripts/ogagent $(BINDIR) chmod 755 $(BINDIR)/ogagent - + uninstall: rm -rf $(LIBDIR) rm -f $(BINDIR)/ogagent diff --git a/src/OGAgent.manifest b/src/OGAgent.manifest index 0e5ff97..0aecb2c 100644 --- a/src/OGAgent.manifest +++ b/src/OGAgent.manifest @@ -1,9 +1,10 @@ +<<<<<<< HEAD Description diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 5073fa8..13a42c5 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -82,10 +82,63 @@ class OpenGnSysWorker(ServerWorker): interface = None # Bound interface for OpenGnsys REST = None # REST object logged_in = False # User session flag - locked = {} # Locked partitions - commands = [] # Running commands - random = None # Random string for secure connections - length = 32 # Random string length + browser = {} # Browser info + commands = [] # Running commands + random = None # Random string for secure connections + length = 32 # Random string length + access_token = refresh_token = None # Server authorization tokens + grant_type = 'http://opengnsys.es/grants/og_client' + + def _launch_browser(self, url): + """ + Launchs the Browser with specified URL + :param url: URL to show + """ + logger.debug('Launching browser with URL: {}'.format(url)) + # Trying to kill an old browser + try: + os.kill(self.browser['process'].pid, signal.SIGKILL) + except OSError: + logger.warn('Cannot kill the old browser process') + except KeyError: + # There is no previous browser + pass + self.browser['url'] = url + self.browser['process'] = subprocess.Popen(['browser', '-qws', url]) + + def _task_command(self, route, code, op_id, send_config=False): + """ + Task to execute a command and return results to a server URI + :param route: server callback REST route to return results + :param code: code to execute + :param op_id: operation id. + """ + menu_url = '' + # Show execution tacking log, if OGAgent runs on ogLive + os_type = operations.os_type.lower() + if os_type == 'oglive': + menu_url = self.browser['url'] + self._launch_browser('http://localhost/cgi-bin/httpd-log.sh') + # Execute the code + (stat, out, err) = operations.exec_command(code) + # Remove command from the list + for c in self.commands: + if c.getName() == op_id: + self.commands.remove(c) + # Remove the REST API prefix, if needed + if route.startswith(self.REST.endpoint): + route = route[len(self.REST.endpoint):] + # Send back exit status and outputs (base64-encoded) + self.REST.sendMessage(route, {'mac': self.interface.mac, 'ip': self.interface.ip, 'trace': op_id, + 'status': stat, 'output': out.encode('utf8').encode('base64'), + 'error': err.encode('utf8').encode('base64')}) + # Show latest menu, if OGAgent runs on ogLive + if os_type == 'oglive': + # Send configuration data, if needed + if send_config: + self.REST.sendMessage('clients/configs', {'mac': self.interface.mac, 'ip': self.interface.ip, + 'config': operations.get_configuration()}) + self._launch_browser(menu_url) def onActivation(self): """ @@ -239,7 +292,7 @@ class OpenGnSysWorker(ServerWorker): :param server: :return: JSON object {"status": "status_code", "loggedin": boolean} """ - res = {'loggedin': self.loggedin} + res = {'loggedin': self.logged_in} try: res['status'] = operations.os_type.lower() except KeyError: @@ -314,7 +367,7 @@ class OpenGnSysWorker(ServerWorker): Closes user session """ logger.debug('Received logoff operation') - # Sending log off message to OGAgent client + # Send log off message to OGAgent client self.sendClientMessage('logoff', {}) return {'op': 'sent to client'} @@ -324,7 +377,7 @@ class OpenGnSysWorker(ServerWorker): Shows a message popup on the user's session """ logger.debug('Received message operation') - # Sending popup message to OGAgent client + # Send popup message to OGAgent client self.sendClientMessage('popup', post_params) return {'op': 'launched'} From f24fea2a9ccd6070e20fb7ab8b7f11c88c40e115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Tue, 30 Apr 2019 10:51:25 +0200 Subject: [PATCH 21/48] #761: OGAgent launchs client's default menu on activation process. --- src/opengnsys/oglive/operations.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/opengnsys/oglive/operations.py b/src/opengnsys/oglive/operations.py index 342cfb0..3cdeef0 100644 --- a/src/opengnsys/oglive/operations.py +++ b/src/opengnsys/oglive/operations.py @@ -33,6 +33,7 @@ from __future__ import unicode_literals import socket import platform +import os import fcntl import subprocess import struct @@ -174,6 +175,13 @@ def poweroff(): _exec_ogcommand('/opt/opengnsys/scripts/poweroff') +def get_etc_path(): + """ + Returns etc directory path. + """ + return os.sep + 'etc' + + def get_configuration(): """ Returns client's configuration From a5117cbf558f75c3e8cf196c7f6ef25e741a2e1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Thu, 23 May 2019 14:29:08 +0200 Subject: [PATCH 22/48] #908: Trying to fix a bug when obteining execution outputs. --- src/opengnsys/modules/server/OpenGnSys/__init__.py | 3 +-- src/opengnsys/oglive/operations.py | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 13a42c5..7a91e50 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -130,8 +130,7 @@ class OpenGnSysWorker(ServerWorker): route = route[len(self.REST.endpoint):] # Send back exit status and outputs (base64-encoded) self.REST.sendMessage(route, {'mac': self.interface.mac, 'ip': self.interface.ip, 'trace': op_id, - 'status': stat, 'output': out.encode('utf8').encode('base64'), - 'error': err.encode('utf8').encode('base64')}) + 'status': stat, 'output': out.encode('base64'), 'error': err.encode('base64')}) # Show latest menu, if OGAgent runs on ogLive if os_type == 'oglive': # Send configuration data, if needed diff --git a/src/opengnsys/oglive/operations.py b/src/opengnsys/oglive/operations.py index 3cdeef0..9ec907c 100644 --- a/src/opengnsys/oglive/operations.py +++ b/src/opengnsys/oglive/operations.py @@ -39,6 +39,7 @@ import subprocess import struct import array import six +import chardet from opengnsys import utils @@ -209,7 +210,8 @@ def exec_command(cmd): proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (out, err) = proc.communicate() stat = proc.returncode - return stat, out, err + return stat, out.decode(chardet.detect(out)['encoding']).encode('utf8'),\ + err.decode(chardet.detect(err)['encoding']).encode('utf8') def get_hardware(): From d0a7766b08d95cdaa75a29537c3fb5047bef052a Mon Sep 17 00:00:00 2001 From: "jm.bardallo" Date: Mon, 27 May 2019 10:48:30 +0200 Subject: [PATCH 23/48] Bug fixed when out and err are None in operation exec_command --- src/opengnsys/oglive/operations.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/opengnsys/oglive/operations.py b/src/opengnsys/oglive/operations.py index 9ec907c..8c74f74 100644 --- a/src/opengnsys/oglive/operations.py +++ b/src/opengnsys/oglive/operations.py @@ -41,6 +41,7 @@ import array import six import chardet from opengnsys import utils +from opengnsys.log import logger def _getMacAddr(ifname): @@ -209,9 +210,20 @@ def exec_command(cmd): """ proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (out, err) = proc.communicate() + try: + if out is not None: + encoding = chardet.detect(out)["encoding"] + if encoding is not None: + out = out.decode(encoding).encode("utf8") + if err is not None: + encoding = chardet.detect(err)["encoding"] + if encoding is not None: + err = err.decode(encoding).encode("utf8") + except Exception as e: + logger.debug("ERROR EXEC COMMAND: {}".format(str(e))) + stat = proc.returncode - return stat, out.decode(chardet.detect(out)['encoding']).encode('utf8'),\ - err.decode(chardet.detect(err)['encoding']).encode('utf8') + return stat, out, err def get_hardware(): From 1954ef5cfa07db885b1fda167703e0f8c10ed521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Fri, 24 May 2019 14:16:40 +0200 Subject: [PATCH 24/48] #908 OGAgent for ogLive launches the Browser with an animation while getting initial configuration. --- .../modules/server/OpenGnSys/__init__.py | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 7a91e50..572d1c5 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -211,8 +211,29 @@ class OpenGnSysWorker(ServerWorker): message = """ + -

Initializing...

+

+ OpenGnsys 3 +
+

+ """ From b3bdeb414ef4419993b4fba4ffa0410d16c75a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Mon, 30 Sep 2019 13:27:04 +0200 Subject: [PATCH 25/48] #930: OGAgent for ogLive can launch the Browser. --- .../modules/server/OpenGnSys/__init__.py | 163 +++++++++++------- 1 file changed, 101 insertions(+), 62 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 572d1c5..bcd5551 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -41,7 +41,6 @@ import urllib from opengnsys import REST from opengnsys import operations from opengnsys.log import logger -from opengnsys.scriptThread import ScriptExecutorThread from opengnsys.workers import ServerWorker from six.moves.urllib import parse @@ -139,6 +138,46 @@ class OpenGnSysWorker(ServerWorker): 'config': operations.get_configuration()}) self._launch_browser(menu_url) + def _launch_browser(self, url): + """ + Launchs the Browser with specified URL + :param url: URL to show + """ + logger.debug('Launching browser with URL: {}'.format(url)) + if hasattr(self.browser, 'process'): + self.browser['process'].kill() + self.browser['url'] = url + self.browser['process'] = subprocess.Popen(['browser', '-qws', url]) + + def _task_command(self, route, code, op_id): + """ + Task to execute a command and return results to a server URI + :param route: server callback REST route to return results + :param code: code to execute + :param op_id: operation id. + """ + menu_url = '' + # Show execution tacking log, if OGAgent runs on ogLive + os_type = operations.os_type.lower() + if os_type == 'oglive': + menu_url = self.browser['url'] + self._launch_browser('http://localhost/cgi-bin/httpd-log.sh') + # Executing command + (stat, out, err) = operations.exec_command(code) + # Removing command from the list + for c in self.commands: + if c.getName() == op_id: + self.commands.remove(c) + # Removing the REST API prefix, if needed + if route.startswith(self.REST.endpoint): + route = route[len(self.REST.endpoint):] + # Sending results + self.REST.sendMessage(route, {'mac': self.interface.mac, 'ip': self.interface.ip, 'trace': op_id, + 'status': stat, 'output': out, 'error': err}) + # Show latest menu, if OGAgent runs on ogLive + if os_type == 'oglive': + self._launch_browser(menu_url) + def onActivation(self): """ Sends OGAgent activation notification to OpenGnsys server @@ -194,21 +233,11 @@ class OpenGnSysWorker(ServerWorker): logger.debug('Successful connection after {} tries'.format(t)) elif t == 100: raise Exception('Initialization error: Cannot connect to remote server') - # Delete marking files - for f in ['ogboot.me', 'ogboot.firstboot', 'ogboot.secondboot']: - try: - os.remove(os.sep + f) - except OSError: - pass - # Copy file "HostsFile.FirstOctetOfIPAddress" to "HostsFile", if it exists - # (used in "exam mode" from the University of Seville) - hosts_file = os.path.join(operations.get_etc_path(), 'hosts') - new_hosts_file = hosts_file + '.' + self.interface.ip.split('.')[0] - if os.path.isfile(new_hosts_file): - shutil.copyfile(new_hosts_file, hosts_file) - ### Separate in a different function to launch browser while catching disk configuration - # Create HTML file (TEMPORARY) - message = """ + # Completing OGAgent initialization process + os_type = operations.os_type.lower() + if os_type == 'oglive': + ### Following code may be separated in a different function to launch browser while catching disk configuration + message = """

OpenGnsys 3 -
+

""" From 582ab7873b33b5ed2e0d7fd334ff7bda1567ab09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20M=2E=20G=C3=B3mez?= Date: Mon, 30 Sep 2019 13:27:04 +0200 Subject: [PATCH 46/48] #930: OGAgent for ogLive can launch the Browser. --- .../modules/server/OpenGnSys/__init__.py | 161 +++++++++++------- 1 file changed, 100 insertions(+), 61 deletions(-) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index 82ba7dd..cbfe122 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -41,7 +41,6 @@ import urllib from opengnsys import REST from opengnsys import operations from opengnsys.log import logger -from opengnsys.scriptThread import ScriptExecutorThread from opengnsys.workers import ServerWorker from six.moves.urllib import parse @@ -167,6 +166,46 @@ class OpenGnSysWorker(ServerWorker): 'config': operations.get_configuration()}) self._launch_browser(menu_url) + def _launch_browser(self, url): + """ + Launchs the Browser with specified URL + :param url: URL to show + """ + logger.debug('Launching browser with URL: {}'.format(url)) + if hasattr(self.browser, 'process'): + self.browser['process'].kill() + self.browser['url'] = url + self.browser['process'] = subprocess.Popen(['browser', '-qws', url]) + + def _task_command(self, route, code, op_id): + """ + Task to execute a command and return results to a server URI + :param route: server callback REST route to return results + :param code: code to execute + :param op_id: operation id. + """ + menu_url = '' + # Show execution tacking log, if OGAgent runs on ogLive + os_type = operations.os_type.lower() + if os_type == 'oglive': + menu_url = self.browser['url'] + self._launch_browser('http://localhost/cgi-bin/httpd-log.sh') + # Executing command + (stat, out, err) = operations.exec_command(code) + # Removing command from the list + for c in self.commands: + if c.getName() == op_id: + self.commands.remove(c) + # Removing the REST API prefix, if needed + if route.startswith(self.REST.endpoint): + route = route[len(self.REST.endpoint):] + # Sending results + self.REST.sendMessage(route, {'mac': self.interface.mac, 'ip': self.interface.ip, 'trace': op_id, + 'status': stat, 'output': out, 'error': err}) + # Show latest menu, if OGAgent runs on ogLive + if os_type == 'oglive': + self._launch_browser(menu_url) + def onActivation(self): """ Sends OGAgent activation notification to OpenGnsys server @@ -222,21 +261,11 @@ class OpenGnSysWorker(ServerWorker): logger.debug('Successful connection after {} tries'.format(t)) elif t == 100: raise Exception('Initialization error: Cannot connect to remote server') - # Delete marking files - for f in ['ogboot.me', 'ogboot.firstboot', 'ogboot.secondboot']: - try: - os.remove(os.sep + f) - except OSError: - pass - # Copy file "HostsFile.FirstOctetOfIPAddress" to "HostsFile", if it exists - # (used in "exam mode" from the University of Seville) - hosts_file = os.path.join(operations.get_etc_path(), 'hosts') - new_hosts_file = hosts_file + '.' + self.interface.ip.split('.')[0] - if os.path.isfile(new_hosts_file): - shutil.copyfile(new_hosts_file, hosts_file) - ### Separate in a different function to launch browser while catching disk configuration - # Create HTML file (TEMPORARY) - message = """ + # Completing OGAgent initialization process + os_type = operations.os_type.lower() + if os_type == 'oglive': + ### Following code may be separated in a different function to launch browser while catching disk configuration + message = """

OpenGnsys 3 -
+