Left: | ||
Right: |
OLD | NEW |
---|---|
(Empty) | |
1 from twisted.internet.defer import inlineCallbacks | |
2 from twisted.internet.threads import deferToThread | |
3 | |
4 from juju.errors import ServiceError | |
5 | |
6 import os | |
7 import sys | |
8 import subprocess | |
9 | |
10 | |
11 def _check_call(args, env=None, output_path=None): | |
12 if not output_path: | |
13 output_path = os.devnull | |
14 | |
15 with open(output_path, "w") as f: | |
16 return subprocess.check_call( | |
17 args, | |
18 stdout=sys.stdout, stderr=subprocess.STDOUT, | |
19 #stdout=f.fileno(), stderr=f.fileno(), | |
20 env=env) | |
21 | |
22 | |
23 def _cat(filename, use_sudo=False): | |
24 args = ("cat", filename) | |
25 if use_sudo: | |
26 args = ("sudo",) + args | |
27 | |
28 p = subprocess.Popen( | |
29 args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) | |
30 stdout_data, _ = p.communicate() | |
31 r = p.returncode | |
32 return (r, stdout_data) | |
33 | |
34 | |
35 class ServiceManager(object): | |
36 """Manage the starting and stopping of an Agent. | |
37 | |
38 This manager tracks the agent via its --pidfile. The pidfile argument | |
39 specifies the location of the pid file that is used to track this service. | |
40 | |
41 """ | |
42 def __init__(self, name, pidfile, use_sudo=False): | |
43 self._name = name | |
44 self._use_sudo = use_sudo | |
45 self._description = None | |
46 self._environ = None | |
47 self._command = None | |
48 self._output_path = None | |
49 | |
50 self._pid_path = pidfile | |
51 self._pid = None | |
52 | |
53 @property | |
54 def output_path(self): | |
55 if self._output_path is not None: | |
56 return self._output_path | |
hazmat
2012/07/26 13:03:13
feels like this should be required, if pid file is
| |
57 return "/tmp/%s.output" % self._name | |
58 | |
59 @output_path.setter | |
60 def set_output_path(self, path): | |
61 self._output_path = path | |
62 | |
63 def set_description(self, description): | |
64 self._description = description | |
65 | |
66 def set_environ(self, environ): | |
67 for k, v in environ.items(): | |
68 environ[k] = str(v) | |
69 self._environ = environ | |
70 | |
71 def set_command(self, command): | |
72 if "--pidfile" not in command: | |
hazmat
2012/07/26 13:03:13
class should probably get renamed twisted daemon s
| |
73 command += ["--pidfile", self._pid_path] | |
74 self._command = command | |
75 | |
76 @inlineCallbacks | |
77 def _trash_output(self): | |
78 if os.path.exists(self.output_path): | |
79 # Just using os.unlink will fail when we're running TEST_SUDO | |
80 # tests which hit this code path (because root will own | |
81 # self.output_path) | |
82 yield self._call("rm", "-f", self.output_path) | |
83 | |
84 if os.path.exists(self._pid_path): | |
85 yield self._call("rm", "-f", self._pid_path) | |
86 | |
87 def _call(self, *args, **kwargs): | |
88 if self._use_sudo: | |
89 args = ("sudo", "-E") + args | |
90 | |
91 return deferToThread(_check_call, args, env=self._environ, | |
92 output_path=self.output_path) | |
93 | |
94 def install(self): | |
95 if self._command is None: | |
96 raise ServiceError("Cannot manage agent: %s no command set" % ( | |
97 self._name)) | |
98 | |
99 @inlineCallbacks | |
100 def start(self): | |
101 if (yield self.is_running()): | |
102 raise ServiceError( | |
103 "%s already running: pid (%s)" % ( | |
104 self._name, self.get_pid())) | |
105 | |
106 if not self.is_installed(): | |
107 yield self.install() | |
108 | |
109 yield self._trash_output() | |
110 | |
111 yield self._call(*self._command, output_path=self.output_path) | |
112 | |
113 @inlineCallbacks | |
114 def destroy(self): | |
115 if (yield self.is_running()): | |
116 yield self._call("kill", self.get_pid()) | |
117 yield self._trash_output() | |
118 | |
119 def get_pid(self): | |
120 if self._pid != None: | |
121 return self._pid | |
122 | |
123 if not os.path.exists(self._pid_path): | |
124 return None | |
125 r, data = _cat(self._pid_path, use_sudo=self._use_sudo) | |
126 if r != 0: | |
127 return None | |
128 | |
129 # verify that pid is a number but leave | |
130 # it as a string suitable for passing to kill | |
131 if not data.strip().isdigit(): | |
132 return None | |
133 pid = data.strip() | |
134 self._pid = pid | |
135 return self._pid | |
136 | |
137 def is_running(self): | |
138 pid = self.get_pid() | |
139 if not pid: | |
140 return False | |
141 proc_file = "/proc/%s" % pid | |
142 if not os.path.exists(proc_file): | |
143 return False | |
144 return True | |
145 | |
146 def is_installed(self): | |
147 return False | |
OLD | NEW |