blob: 989bf11a58e56c94f7cb4aef6f63658d1dfcf225 [file] [log] [blame]
Joshua Hesketh39a0fee2013-07-31 12:00:53 +10001# Copyright 2013 Rackspace Australia
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
Joshua Hesketh0ddd6382013-07-26 10:33:36 +100015
16import git
17import logging
18import os
19import select
20import subprocess
21import time
22
23
24class GitRepository(object):
25
26 """ Manage a git repository for our uses """
Joshua Hesketh363d0042013-07-26 11:44:07 +100027 log = logging.getLogger("lib.utils.GitRepository")
Joshua Hesketh0ddd6382013-07-26 10:33:36 +100028
29 def __init__(self, remote_url, local_path):
30 self.remote_url = remote_url
31 self.local_path = local_path
32 self._ensure_cloned()
33
34 self.repo = git.Repo(self.local_path)
35
36 def fetch(self, ref):
37 # The git.remote.fetch method may read in git progress info and
38 # interpret it improperly causing an AssertionError. Because the
39 # data was fetched properly subsequent fetches don't seem to fail.
40 # So try again if an AssertionError is caught.
41 origin = self.repo.remotes.origin
42 self.log.debug("Fetching %s from %s" % (ref, origin))
43
44 try:
45 origin.fetch(ref)
46 except AssertionError:
47 origin.fetch(ref)
48
49 def checkout(self, ref):
50 self.log.debug("Checking out %s" % ref)
51 return self.repo.git.checkout(ref)
52
53 def _ensure_cloned(self):
54 if not os.path.exists(self.local_path):
55 self.log.debug("Cloning from %s to %s" % (self.remote_url,
56 self.local_path))
57 git.Repo.clone_from(self.remote_url, self.local_path)
58
Joshua Hesketh0ddd6382013-07-26 10:33:36 +100059
60def execute_to_log(cmd, logfile, timeout=-1,
61 watch_logs=[
62 ('[syslog]', '/var/log/syslog'),
63 ('[sqlslo]', '/var/log/mysql/slow-queries.log'),
64 ('[sqlerr]', '/var/log/mysql/error.log')
65 ],
66 heartbeat=True
67 ):
68 """ Executes a command and logs the STDOUT/STDERR and output of any
69 supplied watch_logs from logs into a new logfile
70
71 watch_logs is a list of tuples with (name,file) """
72
73 if not os.path.isdir(os.path.dirname(logfile)):
74 os.makedirs(os.path.dirname(logfile))
75
76 logger = logging.getLogger('execute_to_log')
77 log_hanlder = logging.FileHandler(logfile)
78 log_formatter = logging.Formatter('%(asctime)s %(message)s')
79 log_hanlder.setFormatter(log_formatter)
80 logger.addHandler(log_hanlder)
81
82 descriptors = {}
83
84 for watch_file in watch_logs:
85 fd = os.open(watch_file[1], os.O_RDONLY)
86 os.lseek(fd, 0, os.SEEK_END)
87 descriptors[fd] = dict(
88 name=watch_file[0],
Joshua Hesketh1ab465f2013-07-26 13:57:28 +100089 poll=select.POLLIN,
90 lines=''
Joshua Hesketh0ddd6382013-07-26 10:33:36 +100091 )
92
93 cmd += ' 2>&1'
94 start_time = time.time()
95 p = subprocess.Popen(
96 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
97
98 descriptors[p.stdout.fileno()] = dict(
Joshua Hesketh1ab465f2013-07-26 13:57:28 +100099 name='[output]',
Joshua Hesketh09b2f7f2013-07-29 09:05:58 +1000100 poll=(select.POLLIN | select.POLLHUP),
101 lines=''
Joshua Hesketh0ddd6382013-07-26 10:33:36 +1000102 )
103
104 poll_obj = select.poll()
105 for fd, descriptor in descriptors.items():
106 poll_obj.register(fd, descriptor['poll'])
107
108 last_heartbeat = time.time()
109
Joshua Hesketh1ab465f2013-07-26 13:57:28 +1000110 def process(fd):
111 """ Write the fd to log """
112 descriptors[fd]['lines'] += os.read(fd, 1024 * 1024)
113 # Avoid partial lines by only processing input with breaks
Joshua Hesketh09b2f7f2013-07-29 09:05:58 +1000114 if descriptors[fd]['lines'].find('\n') != -1:
Joshua Hesketh1ab465f2013-07-26 13:57:28 +1000115 elems = descriptors[fd]['lines'].split('\n')
116 # Take all but the partial line
117 for l in elems[:-1]:
118 if len(l) > 0:
119 l = '%s %s' % (descriptors[fd]['name'], l)
120 logger.info(l)
121 last_heartbeat = time.time()
122 # Place the partial line back into lines to be processed
123 descriptors[fd]['lines'] = elems[-1]
124
Joshua Hesketh0ddd6382013-07-26 10:33:36 +1000125 while p.poll() is None:
126 if timeout > 0 and time.time() - start_time > timeout:
127 # Append to logfile
128 logger.info("[timeout]")
129 os.kill(p.pid, 9)
130
131 for fd, flag in poll_obj.poll(0):
Joshua Hesketh1ab465f2013-07-26 13:57:28 +1000132 process(fd)
Joshua Hesketh0ddd6382013-07-26 10:33:36 +1000133
134 if time.time() - last_heartbeat > 30:
135 # Append to logfile
136 logger.info("[heartbeat]")
137 last_heartbeat = time.time()
138
Joshua Hesketh1ab465f2013-07-26 13:57:28 +1000139 # Do one last write to get the remaining lines
140 for fd, flag in poll_obj.poll(0):
141 process(fd)
142
Joshua Hesketh363d0042013-07-26 11:44:07 +1000143 logger.info('[script exit code = %d]' % p.returncode)
Joshua Hesketh926502f2013-07-31 11:56:40 +1000144
145def push_file(local_file):
146 """ Push a log file to a server. Returns the public URL """
147 pass