tinny style change
[slapos.git] / slapos / recipe / librecipe / __init__.py
1 ##############################################################################
2 #
3 # Copyright (c) 2010 Vifib SARL and Contributors. All Rights Reserved.
4 #
5 # WARNING: This program as such is intended to be used by professional
6 # programmers who take the whole responsibility of assessing all potential
7 # consequences resulting from its eventual inadequacies and bugs
8 # End users who are looking for a ready-to-use solution with commercial
9 # guarantees and support are strongly adviced to contract a Free Software
10 # Service Company
11 #
12 # This program is Free Software; you can redistribute it and/or
13 # modify it under the terms of the GNU General Public License
14 # as published by the Free Software Foundation; either version 3
15 # of the License, or (at your option) any later version.
16 #
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 #
26 ##############################################################################
27 import logging
28 from slapos import slap
29 import os
30 import zc.buildout
31 import zc.recipe.egg
32 from hashlib import md5
33 import stat
34 import netaddr
35 import time
36
37 class BaseSlapRecipe:
38 """Base class for all slap.recipe.*"""
39 def __init__(self, buildout, name, options):
40 """Default initialisation"""
41 self.name = name
42 options['eggs'] = 'slapos.cookbook'
43 self.options = options
44 self.logger = logging.getLogger(self.name)
45 self.slap = slap.slap()
46 self.work_directory = os.path.abspath(buildout['buildout'][
47 'directory'])
48 self.bin_directory = os.path.join(buildout['buildout'][
49 'directory'], 'bin')
50 self.data_root_directory = os.path.join(self.work_directory, 'srv')
51 self.backup_directory = os.path.join(self.data_root_directory, 'backup')
52 self.var_directory = os.path.join(self.work_directory, 'var')
53 self.log_directory = os.path.join(self.var_directory, 'log')
54 self.run_directory = os.path.join(self.var_directory, 'run')
55 self.etc_directory = os.path.join(self.work_directory, 'etc')
56 self.tmp_directory = os.path.join(self.work_directory, 'tmp')
57 self.wrapper_directory = os.path.join(self.etc_directory, 'run')
58 self.wrapper_report_directory = os.path.join(self.etc_directory, 'report')
59 self.wrapper_xml_report_directory = os.path.join(self.var_directory,
60 'xml_report')
61 self.destroy_script_location = os.path.join(self, self.work_directory,
62 'sbin', 'destroy')
63
64 # default directory structure information
65 self.default_directory_list = [
66 self.bin_directory, # CP/bin - instance own binaries
67 os.path.join(self, self.work_directory, 'sbin'), # CP/sbin - system
68 # binaries, not exposed, only CP/sbin/destroy
69 self.data_root_directory, # CP/srv - data container
70 self.backup_directory, # CP/srv/backup - backup container
71 self.etc_directory, # CP/etc - configuration container
72 self.wrapper_directory, # CP/etc/run - for wrappers
73 self.wrapper_report_directory, # CP/etc/report - for report wrappers
74 self.var_directory, # CP/var - partition "internal" container for logs,
75 # and another metadata
76 self.wrapper_xml_report_directory, # CP/var/xml_report - for xml_report wrappers
77 self.log_directory, # CP/var/log - log container
78 self.run_directory, # CP/var/run - working container - pids, sockets
79 self.tmp_directory, # CP/tmp - temporary files
80 ]
81
82 # SLAP related information
83 slap_connection = buildout['slap_connection']
84 self.computer_id = slap_connection['computer_id']
85 self.computer_partition_id = slap_connection['partition_id']
86 self.server_url = slap_connection['server_url']
87 self.software_release_url = slap_connection['software_release_url']
88 self.key_file = slap_connection.get('key_file')
89 self.cert_file = slap_connection.get('cert_file')
90
91 # setup egg to give possibility to generate scripts
92 self.egg = zc.recipe.egg.Egg(buildout, options['recipe'], options)
93
94 # setup auto uninstall/install
95 self._setupAutoInstallUninstall()
96
97 def _setupAutoInstallUninstall(self):
98 """By default SlapOS recipes are reinstalled each time"""
99 # Note: It is possible to create in future subclass which will do no-op in
100 # this method
101 self.options['slapos_timestamp'] = str(time.time())
102
103 def _getIpAddress(self, test_method):
104 """Internal helper method to fetch ip address"""
105 if not 'ip_list' in self.parameter_dict:
106 raise AttributeError
107 for name, ip in self.parameter_dict['ip_list']:
108 if test_method(ip):
109 return ip
110 raise AttributeError
111
112 def getLocalIPv4Address(self):
113 """Returns local IPv4 address available on partition"""
114 # XXX: Lack checking for locality of address
115 return self._getIpAddress(netaddr.valid_ipv4)
116
117 def getGlobalIPv6Address(self):
118 """Returns global IPv6 address available on partition"""
119 # XXX: Lack checking for globality of address
120 return self._getIpAddress(netaddr.valid_ipv6)
121
122 def createConfigurationFile(self, name, content):
123 """Creates named configuration file and returns its path"""
124 file_path = os.path.join(self.etc_directory, name)
125 self._writeFile(file_path, content)
126 self.logger.debug('Created configuration file: %r' % file_path)
127 return file_path
128
129 def createRunningWrapper(self, wrapper_name, file_content):
130 """Creates named running wrapper and returns its path"""
131 wrapper_path = os.path.join(self.wrapper_directory, wrapper_name)
132 self._writeExecutable(wrapper_path, file_content)
133 return wrapper_path
134
135 def createReportRunningWrapper(self, file_content):
136 """Creates report runnig wrapper and returns its path"""
137 report_wrapper_path = os.path.join(self.wrapper_report_directory,
138 'slapreport')
139 self._writeExecutable(report_wrapper_path, file_content)
140 return report_wrapper_path
141
142 def substituteTemplate(self, template_location, mapping_dict):
143 """Returns template content after substitution"""
144 return open(template_location, 'r').read() % mapping_dict
145
146 def _writeExecutable(self, path, content, mode='0700'):
147 """Creates file in path with content and sets mode
148
149 If file was created or altered returns true
150 Otherwise returns false
151
152 To be used to create executables
153
154 Raises os related errors"""
155 return self._writeFile(path, content, mode)
156
157 def _writeFile(self, path, content, mode='0600'):
158 """Creates file in path with content and sets mode
159
160 If file was created or altered returns true
161 Otherwise returns false
162
163 Raises os related errors"""
164
165 file_altered = False
166 if not os.path.exists(path):
167 open(path, 'w').write(content)
168 file_altered = True
169 else:
170 new_sum = md5()
171 current_sum = md5()
172 new_sum.update(content)
173 current_sum.update(open(path, 'r').read())
174 if new_sum.digest() != current_sum.digest():
175 file_altered = True
176 open(path, 'w').write(content)
177
178 if oct(stat.S_IMODE(os.stat(path).st_mode)) != mode:
179 os.chmod(path, int(mode, 8))
180 file_altered = True
181
182 return file_altered
183
184 def createBackupDirectory(self, name, mode='0700'):
185 """Creates named directory in self.backup_directory and returns its path"""
186 path = os.path.join(self.backup_directory, name)
187 self._createDirectory(path, mode)
188 return path
189
190 def createDataDirectory(self, name, mode='0700'):
191 """Creates named directory in self.data_root_directory and returns its path"""
192 path = os.path.join(self.data_root_directory, name)
193 self._createDirectory(path, mode)
194 return path
195
196 def _createDirectory(self, path, mode='0700'):
197 """Creates path directory and sets mode
198
199 If directory was created or its mode was altered returns true
200 Otherwise returns false
201
202 Raises os related errors"""
203 directory_altered = False
204 if not os.path.exists(path):
205 os.mkdir(path, int(mode, 8))
206 directory_altered = True
207 if not os.path.isdir(path):
208 raise zc.buildout.UserError('Path %r exists, but it is not directory'
209 % path)
210 if oct(stat.S_IMODE(os.stat(path).st_mode)) != mode:
211 os.chmod(path, int(mode, 8))
212 directory_altered = True
213 if directory_altered:
214 self.logger.debug('Created directory %r with permission %r' % (path, mode))
215 return directory_altered
216
217 def _createDefaultDirectoryStructure(self):
218 for directory in self.default_directory_list:
219 self._createDirectory(directory)
220
221 def generatePassword(self, len=32):
222 """Generates password. Shall be secured, until then all are insecure"""
223 return 'insecure'
224
225 def install(self):
226 self.slap.initializeConnection(self.server_url, self.key_file,
227 self.cert_file)
228 self.computer_partition = self.slap.registerComputerPartition(
229 self.computer_id,
230 self.computer_partition_id)
231 self.request = self.computer_partition.request
232 self.setConnectionDict = self.computer_partition.setConnectionDict
233 self._createDefaultDirectoryStructure()
234 self.parameter_dict = self.computer_partition.getInstanceParameterDict()
235
236 # call children part of install
237 path_list = self._install()
238
239 return path_list
240
241 update = install
242
243 def _install(self):
244 """Hook which shall be implemented in children class"""
245 raise NotImplementedError('Shall be implemented by subclass')