File: service.py

package info (click to toggle)
subuser 0.6.2-3
  • links: PTS
  • area: main
  • in suites: bookworm, bullseye, buster, forky, sid, trixie
  • size: 4,208 kB
  • sloc: python: 5,201; sh: 380; makefile: 73
file content (142 lines) | stat: -rwxr-xr-x 4,258 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# -*- coding: utf-8 -*-

"""
This is an abstract class providing common methods shared by all service types.

The semantics of the lock file mechanism may be found `here <https://github.com/subuser-security/subuser/issues/31>`_ .
"""

#external imports
import abc
import json
import os
import errno
import fcntl
import shutil
import sys
from collections import OrderedDict
#internal imports
from subuserlib.classes.userOwnedObject import UserOwnedObject

class Service(UserOwnedObject):
  __metaclass__ = abc.ABCMeta

  def __init__(self,user,subuser):
    self.__subuser = subuser
    UserOwnedObject.__init__(self,user)

  @abc.abstractmethod
  def start(self,serviceStatus):
    """
    Start the service. Block untill the service has started. Returns a modified service status dictionary with any service specific properties set.
    """
    pass

  @abc.abstractmethod
  def stop(self,serviceStatus):
    """
    Stop the service. Block untill the service has stopped.
    """
    pass

  @abc.abstractmethod
  def cleanUp(self):
    pass

  @abc.abstractmethod
  def isRunning(self,serviceStatus):
    """
    Returns True if the services is running.
    """
    pass

  def getLockfileDir(self):
    return os.path.join(self.user.config["lock-dir"],"services",self.__subuser.name)

  def getLockfilePath(self):
    return os.path.join(self.getLockfileDir(),self.name+".json")

  def removeLockFile(self):
    os.remove(self.getLockfilePath())
    try:
      os.rmdir(self.getLockfileDir())
    except OSError:
      pass

  def getLock(self):
    try:
      self.user.endUser.makedirs(self.getLockfileDir())
    except OSError as exception:
      if exception.errno != errno.EEXIST:
        raise
    while True:
      try:
        lockFd = open(self.getLockfilePath(),mode="r+")
        break
      except IOError:
        self.user.endUser.create_file(self.getLockfilePath())
    fcntl.flock(lockFd,fcntl.LOCK_EX)
    return lockFd

  def addClient(self):
    """
    Increase the services client counter, starting the service if necessary. Blocks untill the service is ready to accept the new client.
    """
    with self.getLock() as lockFile:
      try:
        serviceStatus = json.load(lockFile, object_pairs_hook=OrderedDict)
      except ValueError:
        serviceStatus = {}
        serviceStatus["client-counter"] = 0
      if serviceStatus["client-counter"] == 0 or not self.isRunning(serviceStatus):
        serviceStatus["client-counter"] = 0
        serviceStatus = self.start(serviceStatus)
      serviceStatus["client-counter"] = serviceStatus["client-counter"] + 1
      lockFile.seek(0)
      lockFile.truncate()
      json.dump(serviceStatus,lockFile)
      fcntl.flock(lockFile,fcntl.LOCK_UN)

  def removeClient(self):
    """
    Decrease the services client counter, stopping the service if no longer necessary.
    """
    noMoreClients = False
    with self.getLock() as lockFile:
      try:
        lock_info = lockFile.read()
        serviceStatus = json.loads(lock_info, object_pairs_hook=OrderedDict)
      except ValueError as e:
        sys.exit("Error in lock file. Failed to release lock:\n"+lock_info+"\n"+str(e))
      serviceStatus["client-counter"] = serviceStatus["client-counter"] - 1
      if serviceStatus["client-counter"] < 0:
        raise RemoveClientException("The client-counter is already zero. Client cannot be removed! \n" + str(serviceStatus))
      if serviceStatus["client-counter"] == 0:
        self.stop(serviceStatus)
        noMoreClients = True
      lockFile.seek(0)
      lockFile.truncate()
      json.dump(serviceStatus,lockFile)
      if noMoreClients:
        self.removeLockFile()
      fcntl.flock(lockFile,fcntl.LOCK_UN)

  def cleanUpIfNotRunning(self):
    """
    Check if running.
    If not running, run cleanUp method.
    """
    with self.getLock() as lockFile:
      try:
        serviceStatus = json.load(lockFile, object_pairs_hook=OrderedDict)
      except ValueError:
        serviceStatus = {}
        serviceStatus["client-counter"] = 0
      if serviceStatus["client-counter"] == 0 or not self.isRunning(serviceStatus):
        self.cleanUp()
        self.removeLockFile()
      fcntl.flock(lockFile,fcntl.LOCK_UN)


class RemoveClientException(Exception):
  pass