RIFT OSM R1 Initial Submission
[osm/SO.git] / rwcal / rift / cal / server / operations.py
1 """
2 #
3 # Copyright 2016 RIFT.IO Inc
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17
18 @file operations.py
19 @author Varun Prasad(varun.prasad@riftio.com)
20 @date 2016-06-14
21 """
22
23 import daemon
24 import daemon.pidfile
25 import os
26 import signal
27 import subprocess
28 import sys
29 import time
30
31 import gi
32 gi.require_version('RwcalYang', '1.0')
33 gi.require_version('RwCal', '1.0')
34 gi.require_version('RwLog', '1.0')
35
36 from . import server as cal_server
37 import rift.cal.utils as cal_util
38 import rift.rwcal.cloudsim.shell as shell
39
40
41
42 class CloudsimServerOperations(cal_util.CloudSimCalMixin):
43 """Convenience class to provide start, stop and cleanup operations
44
45 Attributes:
46 log (logging): Log instance
47 PID_FILE (str): Location to generate the PID file.
48 """
49 PID_FILE = "/var/log/rift/cloudsim_server.pid"
50
51 def __init__(self, log):
52 super().__init__()
53 self.log = log
54
55 @property
56 def pid(self):
57 pid = None
58 try:
59 with open(self.PID_FILE) as fh:
60 pid = fh.readlines()[0]
61 pid = int(pid.strip())
62 except IndexError:
63 self.log.error("Looks like the pid file does not contain a valid ID")
64 except OSError:
65 self.log.debug("No PID file found.")
66
67 return pid
68
69 def is_pid_exists(self, pid):
70 try:
71 os.kill(pid, 0)
72 except OSError:
73 return False
74
75 return True
76
77 def start_server(self, foreground=False):
78 """Start the tornado app """
79
80 # Before starting verify if all requirements are satisfied
81 cal_server.CalServer.verify_requirements(self.log)
82
83 # If the /var/log directory is not present, then create it first.
84 if not os.path.exists(os.path.dirname(self.PID_FILE)):
85 self.log.warning("Creating /var/log/rift directory for log storage")
86 os.makedirs(os.path.dirname(self.PID_FILE))
87
88 # Check if an exiting PID file is present, if so check if it has an
89 # associated proc, otherwise it's a zombie file so clean it.
90 # Otherwise the daemon fails silently.
91 if self.pid is not None and not self.is_pid_exists(self.pid):
92 self.log.warning("Removing stale PID file")
93 os.remove(self.PID_FILE)
94
95
96
97 def start(daemon_mode=False):
98
99 log = cal_util.Logger(daemon_mode=daemon_mode, log_name='')
100 log.logger.info("Starting the cloud server.")
101 server = cal_server.CalServer()
102 server.start()
103
104 if foreground:
105 # Write the PID file for consistency
106 with open(self.PID_FILE, mode='w') as fh:
107 fh.write(str(os.getpid()) + "\n")
108 start()
109 else:
110 context = daemon.DaemonContext(
111 pidfile=daemon.pidfile.PIDLockFile(self.PID_FILE))
112 with context:
113 start(daemon_mode=True)
114
115 def stop_server(self):
116 """Stop the daemon"""
117
118 def kill_pid(pid, sig):
119 self.log.info("Sending {} to PID: {}".format(str(sig), pid))
120 os.kill(pid, sig)
121
122
123 def search_and_kill():
124 """In case the PID file is not found, and the server is still
125 running, as a last resort we search thro' the process table
126 and stop the server."""
127 cmd = ["pgrep", "-u", "daemon,root", "python3"]
128
129 try:
130 pids = subprocess.check_output(cmd)
131 except subprocess.CalledProcessError:
132 self.log.error("No Cloudsim server process found. "
133 "Please ensure Cloudsim server is running")
134 return
135
136 pids = map(int, pids.split())
137
138 for pid in pids:
139 if pid != os.getpid():
140 kill_sequence(pid)
141
142 def wait_till_exit(pid, timeout=30, retry_interval=1):
143 start_time = time.time()
144
145 while True:
146 if not self.is_pid_exists(pid):
147 msg = "Killed {}".format(pid)
148 print (msg)
149 return True
150
151 time_elapsed = time.time() - start_time
152 time_remaining = timeout - time_elapsed
153
154 self.log.info("Process still exists, trying again in {} sec(s)"
155 .format(retry_interval))
156
157 if time_remaining <= 0:
158 msg = 'Process {} has not yet terminated within {} secs. Trying SIGKILL'
159 self.log.error(msg.format(pid, timeout))
160 return False
161
162 time.sleep(min(time_remaining, retry_interval))
163
164 def kill_sequence(pid):
165 kill_pid(pid, signal.SIGHUP)
166 wait_till_exit(pid, timeout=10, retry_interval=2)
167 kill_pid(pid, signal.SIGKILL)
168 status = wait_till_exit(pid)
169
170 if status:
171 # Remove the lock file.
172 shell.command("rm -f {}".format(self.PID_FILE))
173
174 pid = self.pid
175 if pid is not None:
176 self.log.warning("Server running with PID: {} found, "
177 "trying to stop it".format(pid))
178 kill_sequence(pid)
179 else:
180 self.log.warning("No PID file found. Searching the process "
181 "table to find PID")
182 search_and_kill()
183
184 def clean_server(self, images=False):
185 """Clean all resource using rest APIs. """
186
187 # Delete VDUs
188 _, vdus = self.cal.get_vdu_list(self.account)
189 for vdu in vdus.vdu_info_list:
190 self.cal.delete_vdu(self.account, vdu.vdu_id)
191
192 # Delete Vlinks
193 _, vlinks = self.cal.get_virtual_link_list(self.account)
194 for vlink in vlinks.virtual_link_info_list:
195 self.cal.delete_virtual_link(self.account, vlink.virtual_link_id)
196
197 if images:
198 _, images = self.cal.get_image_list(self.account)
199 for image in images.image_info_list:
200 self.cal.delete_image(self.account, image.id)