udsactor/linux/renamer/common.py

This commit is contained in:
Adolfo Gómez García 2019-11-28 11:04:08 +01:00
parent 2c8963ba5f
commit 4cdbe5abae
17 changed files with 148 additions and 253 deletions

3
actor/deps.txt Normal file
View File

@ -0,0 +1,3 @@
Linux:
python3-prctl (recommended, but not required in fact)

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# Copyright (c) 2014-2019 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -29,44 +29,45 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
import sys
import os
import stat
import subprocess
import signal
import typing
from udsactor import operations
from . import operations
from . import renamer
from . import daemon
from udsactor.service import CommonService
from udsactor.service import initCfg
from udsactor.service import IPC_PORT
from ..log import logger
from ..service import CommonService
from udsactor import ipc
from udsactor import store
from udsactor.log import logger
from udsactor.linux.daemon import Daemon
from udsactor.linux import renamer
POST_CMD = '/etc/udsactor/post'
try:
from prctl import set_proctitle # @UnresolvedImport
except Exception: # Platform may not include prctl, so in case it's not available, we let the "name" as is
except ImportError: # Platform may not include prctl, so in case it's not available, we let the "name" as is
def set_proctitle(_):
pass
class UDSActorSvc(Daemon, CommonService):
rebootMachineAfterOp = False
def __init__(self, args=None):
Daemon.__init__(self, '/var/run/udsa.pid')
class UDSActorSvc(daemon.Daemon, CommonService):
def __init__(self, args=None) -> None:
daemon.Daemon.__init__(self, '/var/run/udsactor.pid')
CommonService.__init__(self)
def rename(self, name, user=None, oldPassword=None, newPassword=None):
signal.signal(signal.SIGINT, self.markForExit)
signal.signal(signal.SIGTERM, self.markForExit)
def markForExit(self, signum, frame):
self._isAlive = False
def rename( # pylint: disable=unused-argument
self,
name: str,
userName: typing.Optional[str] = None,
oldPassword: typing.Optional[str] = None,
newPassword: typing.Optional[str] = None
) -> None:
'''
Renames the computer, and optionally sets a password for an user
before this
@ -75,103 +76,40 @@ class UDSActorSvc(Daemon, CommonService):
if hostName.lower() == name.lower():
logger.info('Computer name is already {}'.format(hostName))
self.setReady()
return
# Check for password change request for an user
if user is not None:
logger.info('Setting password for user {}'.format(user))
if userName and oldPassword and newPassword:
logger.info('Setting password for user {}'.format(userName))
try:
operations.changeUserPassword(user, oldPassword, newPassword)
operations.changeUserPassword(userName, oldPassword, newPassword)
except Exception as e:
# We stop here without even renaming computer, because the
# process has failed
raise Exception(
'Could not change password for user {} (maybe invalid current password is configured at broker): {} '.format(user, unicode(e)))
raise Exception('Could not change password for user {} (maybe invalid current password is configured at broker): {} '.format(userName, e))
renamer.rename(name)
if self.rebootMachineAfterOp is False:
self.setReady()
else:
logger.info('Rebooting computer to activate new name {}'.format(name))
self.reboot()
def joinDomain(self, name, domain, ou, account, password):
def joinDomain( # pylint: disable=unused-argument, too-many-arguments
self,
name: str,
domain: str,
ou: str,
account: str,
password: str
) -> None:
logger.fatal('Join domain is not supported on linux platforms right now')
def preConnect(self, user, protocol):
'''
Invoked when received a PRE Connection request via REST
'''
# Execute script in /etc/udsactor/post after interacting with broker, if no reboot is requested ofc
# This will be executed only when machine gets "ready"
try:
pre_cmd = store.preApplication()
if os.path.isfile(pre_cmd):
if (os.stat(pre_cmd).st_mode & stat.S_IXUSR) != 0:
subprocess.call([pre_cmd, user, protocol])
else:
logger.info('PRECONNECT file exists but it it is not executable (needs execution permission by root)')
else:
logger.info('PRECONNECT file not found & not executed')
except Exception:
# Ignore output of execution command
logger.error('Executing preconnect command give')
return 'ok'
def run(self):
cfg = initCfg() # Gets a local copy of config to get "reboot"
logger.debug('CFG: {}'.format(cfg))
if cfg is not None:
self.rebootMachineAfterOp = cfg.get('reboot', True)
else:
self.rebootMachineAfterOp = False
logger.info('Reboot after is {}'.format(self.rebootMachineAfterOp))
logger.debug('Running Daemon')
set_proctitle('UDSActorDaemon')
# Linux daemon will continue running unless something is requested to
while True:
brokerConnected = self.interactWithBroker()
if brokerConnected is False:
logger.debug('Interact with broker returned false, stopping service after a while')
return
elif brokerConnected is True:
break
if not self.initialize():
return # Stop daemon if initializes told to do so
# If brokerConnected returns None, repeat the cycle
self.doWait(16000) # Wait for a looong while
if self.isAlive is False:
logger.debug('The service is not alive after broker interaction, stopping it')
return
if self.rebootRequested is True:
logger.debug('Reboot has been requested, stopping service')
return
# Execute script in /etc/udsactor/post after interacting with broker, if no reboot is requested ofc
# This will be executed only when machine gets "ready"
try:
if os.path.isfile(POST_CMD):
if (os.stat(POST_CMD).st_mode & stat.S_IXUSR) != 0:
subprocess.call([POST_CMD, ])
else:
logger.info('POST file exists but it it is not executable (needs execution permission by root)')
else:
logger.info('POST file not found & not executed')
except Exception as e:
# Ignore output of execution command
logger.error('Executing post command give')
self.initIPC()
# Initialization is done, set machine to ready for UDS, communicate urls, etc...
self.setReady()
# *********************
# * Main Service loop *
@ -186,9 +124,6 @@ class UDSActorSvc(Daemon, CommonService):
# In milliseconds, will break
self.doWait(1000)
self.endIPC()
self.endAPI()
self.notifyStop()
@ -202,31 +137,31 @@ if __name__ == '__main__':
if len(sys.argv) == 3 and sys.argv[1] in ('login', 'logout'):
logger.debug('Running client udsactor')
client = None
try:
client = ipc.ClientIPC(IPC_PORT)
if 'login' == sys.argv[1]:
client.sendLogin(sys.argv[2])
sys.exit(0)
elif 'logout' == sys.argv[1]:
client.sendLogout(sys.argv[2])
sys.exit(0)
else:
usage()
except Exception as e:
logger.error(e)
# client = None
# try:
# client = ipc.ClientIPC(IPC_PORT)
# if 'login' == sys.argv[1]:
# client.sendLogin(sys.argv[2])
# sys.exit(0)
# elif 'logout' == sys.argv[1]:
# client.sendLogout(sys.argv[2])
# sys.exit(0)
# else:
# usage()
# except Exception as e:
# logger.error(e)
elif len(sys.argv) != 2:
usage()
logger.debug('Executing actor')
daemon = UDSActorSvc()
daemonSvr = UDSActorSvc()
if len(sys.argv) == 2:
if 'start' == sys.argv[1]:
daemon.start()
elif 'stop' == sys.argv[1]:
daemon.stop()
elif 'restart' == sys.argv[1]:
daemon.restart()
if sys.argv[1] == 'start':
daemonSvr.start()
elif sys.argv[1] == 'stop':
daemonSvr.stop()
elif sys.argv[1] == 'restart':
daemonSvr.restart()
else:
usage()
sys.exit(0)

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# Copyright (c) 2014-2019 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -29,4 +29,3 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2018 Virtual Cable S.L.
# Copyright (c) 2014-2019 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -26,19 +26,16 @@
# 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/
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
import sys
import os
import time
import atexit
from udsactor.log import logger
from signal import SIGTERM
from udsactor.log import logger
class Daemon:
"""
@ -47,13 +44,13 @@ class Daemon:
Usage: subclass the Daemon class and override the run() method
"""
def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
def __init__(self, pidfile: str, stdin: str = '/dev/null', stdout: str = '/dev/null', stderr: str = '/dev/null'):
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
self.pidfile = pidfile
def daemonize(self):
def daemonize(self) -> None:
"""
do the UNIX double-fork magic, see Stevens' "Advanced
Programming in the UNIX Environment" for details (ISBN 0201563177)
@ -96,33 +93,26 @@ class Daemon:
os.dup2(se.fileno(), sys.stderr.fileno())
# write pidfile
atexit.register(self.delpid)
pid = str(os.getpid())
atexit.register(self.removePidFile)
pidStr = str(os.getpid())
with open(self.pidfile, 'w+') as f:
f.write("{}\n".format(pid))
f.write("{}\n".format(pidStr))
def delpid(self):
def removePidFile(self) -> None:
try:
os.remove(self.pidfile)
except Exception:
# Not found/not permissions or whatever...
# Not found/not permissions or whatever, ignore it
pass
def start(self):
def start(self) -> None:
"""
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)
if os.path.exists(self.pidfile):
message = "pidfile {} already exist. Daemon already running?\n".format(self.pidfile)
logger.error(message)
sys.stderr.write(message)
sys.exit(1)
@ -134,10 +124,9 @@ class Daemon:
except Exception as e:
logger.error('Exception running process: {}'.format(e))
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
self.removePidFile()
def stop(self):
def stop(self) -> None:
"""
Stop the daemon
"""
@ -147,9 +136,6 @@ class Daemon:
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)
@ -165,10 +151,10 @@ class Daemon:
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
else:
sys.stderr.write(err)
sys.stderr.write('Error: {}'.format(err))
sys.exit(1)
def restart(self):
def restart(self) -> None:
"""
Restart the daemon
"""
@ -176,8 +162,7 @@ class Daemon:
self.start()
# Overridables
def run(self):
def run(self) -> None:
"""
You should override this method when you subclass Daemon. It will be called after the process has been
daemonized by start() or restart().
override this to provide your own daemon
"""

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# Copyright (c) 2014-2019 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -29,20 +29,22 @@
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
# pylint: disable=invalid-name
import logging
import os
import tempfile
import six
import logging
import typing
# Valid logging levels, from UDS Broker (uds.core.utils.log)
OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in six.moves.xrange(6)) # @UndefinedVariable
OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in range(6)) # @UndefinedVariable
class LocalLogger: # pylint: disable=too-few-public-methods
linux = False
windows = True
def __init__(self):
logger: typing.Optional[logging.Logger]
def __init__(self) -> None:
# tempdir is different for "user application" and "service"
# service wil get c:\windows\temp, while user will get c:\users\XXX\temp
# Try to open logger at /var/log path
@ -71,4 +73,5 @@ class LocalLogger: # pylint: disable=too-few-public-methods
# our loglevels are 10000 (other), 20000 (debug), ....
# logging levels are 10 (debug), 20 (info)
# OTHER = logging.NOTSET
self.logger.log(int(level / 1000) - 10, message)
if self.logger:
self.logger.log(int(level / 1000) - 10, message)

View File

@ -29,8 +29,9 @@
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
# pylint: disable=invalid-name
import socket
import configparser
import platform
import socket
import fcntl
import os
import ctypes
@ -109,30 +110,30 @@ def _getIpAndMac(ifname: str) -> typing.Tuple[typing.Optional[str], typing.Optio
def checkPermissions() -> bool:
return os.getuid() == 0
def getComputerName() -> str:
'''
Returns computer name, with no domain
'''
return socket.gethostname().split('.')[0]
def getNetworkInfo() -> typing.Iterable[types.InterfaceInfoType]:
for ifname in _getInterfaces():
ip, mac = _getIpAndMac(ifname)
if mac != '00:00:00:00:00:00' and mac and ip and ip.startswith('169.254') is False: # Skips local interfaces & interfaces with no dhcp IPs
yield types.InterfaceInfoType(name=ifname, mac=mac, ip=ip)
def getDomainName() -> str:
return ''
def getLinuxVersion() -> str:
import distro
lv = distro.linux_distribution()
return lv[0] + ', ' + lv[1]
def getLinuxOs() -> str:
try:
with open('/etc/os-release', 'r') as f:
data = f.read()
cfg = configparser.ConfigParser()
cfg.read_string('[os]\n' + data)
return cfg['os'].get('id', 'unknown')
except Exception:
return 'unknown'
def reboot(flags: int = 0):
'''
@ -216,7 +217,7 @@ def getIdleDuration() -> float:
xss.XScreenSaverQueryInfo(display, xlib.XDefaultRootWindow(display), xssInfo)
# Centos seems to set state to 1?? (weird, but it's happening don't know why... will try this way)
if xssInfo.contents.state != 0 and 'centos' not in getLinuxVersion().lower().strip():
if xssInfo.contents.state != 0 and 'centos' not in getLinuxOs().lower().strip():
return 3600 * 100 * 1000 # If screen saver is active, return a high enough value
return xssInfo.contents.idle / 1000.0

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# Copyright (c) 2014-2019 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -29,33 +29,4 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
import platform
import os
import sys
import pkgutil
from udsactor.log import logger
renamers = {}
# Renamers now are for IPv4 only addresses
def rename(newName):
distribution = platform.linux_distribution()[0].lower().strip()
if distribution in renamers:
return renamers[distribution](newName)
# Try Debian renamer, simplest one
logger.info('Renamer for platform "{0}" not found, tryin debian renamer'.format(distribution))
return renamers['debian'](newName)
# Do load of packages
def _init():
pkgpath = os.path.dirname(sys.modules[__name__].__file__)
for _, name, _ in pkgutil.iter_modules([pkgpath]):
__import__(__name__ + '.' + name, globals(), locals())
_init()
from .common import rename

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# Copyright (c) 2014-2019 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -29,15 +29,13 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from udsactor.linux.renamer import renamers
from udsactor.log import logger
import os
from .common import renamers
from ...log import logger
def rename(newName):
def rename(newName: str) -> bool:
'''
Debian renamer
Expects new host name on newName

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# Copyright (c) 2014-2019 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -28,15 +28,13 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from udsactor.linux.renamer import renamers
from udsactor.log import logger
import os
from .common import renamers
from ...log import logger
def rename(newName):
def rename(newName: str) -> bool:
'''
RH, Centos, Fedora Renamer
Expects new host name on newName

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# Copyright (c) 2014-2019 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -28,15 +28,13 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from udsactor.linux.renamer import renamers
from udsactor.log import logger
import os
from .common import renamers
from ...log import logger
def rename(newName):
def rename(newName: str) -> bool:
'''
RH, Centos, Fedora Renamer
Expects new host name on newName

View File

@ -98,5 +98,5 @@ def writeConfig(config: types.ActorConfigurationType) -> None:
os.chmod(CONFIGFILE, 0o0600) # Ensure only readable by root
def useOldJoinSystem():
def useOldJoinSystem() -> bool:
return False

View File

@ -36,15 +36,15 @@ import sys
import six
if sys.platform == 'win32':
from udsactor.windows.log import LocalLogger # @UnusedImport
from .windows.log import LocalLogger # @UnusedImport
else:
from udsactor.linux.log import LocalLogger # @Reimport
from .linux.log import LocalLogger # @Reimport
# Valid logging levels, from UDS Broker (uds.core.utils.log)
OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in six.moves.xrange(6)) # @UndefinedVariable
class Logger(object):
class Logger:
def __init__(self):
self.logLevel = INFO

View File

@ -193,7 +193,7 @@ class REST:
os = r['os']
return types.InitializationResultType(
own_token=r['own_token'],
unique_id=r['unique_id'].lower(),
unique_id=r['unique_id'].lower() if r['unique_id'] else None,
max_idle=r['max_idle'],
os=types.ActorOsConfigurationType(
action=os['action'],
@ -203,11 +203,11 @@ class REST:
new_password=os.get('new_password'),
ad=os.get('ad'),
ou=os.get('ou')
)
) if r['os'] else None
)
except requests.ConnectionError as e:
raise RESTConnectionError(str(e))
except Exception:
except Exception as e:
pass
raise RESTError(result.content)

View File

@ -208,7 +208,7 @@ class CommonService:
def rename( # pylint: disable=unused-argument
self,
name: str,
user: typing.Optional[str] = None,
userName: typing.Optional[str] = None,
oldPassword: typing.Optional[str] = None,
newPassword: typing.Optional[str] = None
) -> None:
@ -216,7 +216,7 @@ class CommonService:
Invoked when broker requests a rename action
default does nothing
'''
logger.info('Base renamed invoked: {}'.format(name))
logger.info('Base renamed invoked: {}, {}'.format(name, userName))
def joinDomain( # pylint: disable=unused-argument, too-many-arguments
self,
@ -230,14 +230,11 @@ class CommonService:
Invoked when broker requests a "domain" action
default does nothing
'''
logger.info('Base join invode: {} on {}, {}'.format(name, domain, ou))
logger.info('Base join invoked: {} on {}, {}'.format(name, domain, ou))
# ****************************************
# Methods that CAN BE overriden by actors
# ****************************************
def notifyLocal(self) -> None:
self.setReady()
def doWait(self, miliseconds: int) -> None:
'''
Invoked to wait a bit
@ -254,8 +251,11 @@ class CommonService:
def preConnect(self, user: str, protocol: str) -> str: # pylint: disable=unused-argument
'''
Invoked when received a PRE Connection request via REST
Base preconnect executes the preconnect command
'''
logger.debug('Pre-connect does nothing')
if self._cfg.pre_command:
self.execute(self._cfg.pre_command, 'preConnect')
return 'ok'
def onLogout(self, user: str) -> None:

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# Copyright (c) 2014-2019 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -29,4 +29,3 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals

View File

@ -165,7 +165,7 @@ class ActorV2Initiialize(ActorV2Action):
* own_token: Optional[str] -> Personal uuid for the service (That, on service, will be used from now onwards). If None, there is no own_token
* unique_id: Optional[str] -> If not None, unique id for the service
* max_idle: Optional[int] -> If not None, max configured Idle for the vm
* os: Optional[dict] -> Data returned by os manager for setting up this service.
* os: Optional[dict] -> Data returned by os manager for setting up this service.
On error, will return Empty (None) result, and error field
Example:
{
@ -263,6 +263,9 @@ class ActorV2IpChange(ActorV2Action):
name = 'ipchange'
def post(self):
"""
Records the ip change, and also fix notifyComms url
"""
logger.debug('Args: %s, Params: %s', self._args, self._params)
return actorResult('ok')

View File

@ -489,8 +489,10 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods
return self.deployed_service.testServer(host, port, timeout)
def __str__(self):
return "User service {0}, cache_level {1}, user {2}, name {3}, state {4}:{5}".format(self.name, self.cache_level, self.user, self.friendly_name,
State.toString(self.state), State.toString(self.os_state))
return "User service {}, unique_id {}, cache_level {}, user {}, name {}, state {}:{}".format(
self.name, self.unique_id, self.cache_level, self.user, self.friendly_name,
State.toString(self.state), State.toString(self.os_state)
)
@staticmethod
def beforeDelete(sender, **kwargs):