Resilient recipe: remove hashing of urls/names.
[slapos.git] / slapos / recipe / pbs.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
28 import json
29 import os
30 import signal
31 import subprocess
32 import sys
33 import urlparse
34
35 from slapos.recipe.librecipe import GenericSlapRecipe
36 from slapos.recipe.dropbear import KnownHostsFile
37 from slapos.recipe.notifier import Notify
38 from slapos.recipe.notifier import Callback
39 from slapos import slap as slapmodule
40
41
42 def promise(args):
43
44 def failed_ssh():
45 sys.stderr.write("SSH Connection failed\n")
46 partition = slap.registerComputerPartition(args['computer_id'],
47 args['partition_id'])
48 partition.bang("SSH Connection failed. rdiff-backup is unusable.")
49
50 def sigterm_handler(signum, frame):
51 failed_ssh()
52
53 signal.signal(signal.SIGTERM, sigterm_handler)
54
55 slap = slapmodule.slap()
56 slap.initializeConnection(args['server_url'],
57 key_file=args.get('key_file'),
58 cert_file=args.get('cert_file'))
59
60 ssh = subprocess.Popen([args['ssh_client'], '%(user)s@%(host)s/%(port)s' % args],
61 stdin=subprocess.PIPE,
62 stdout=open(os.devnull, 'w'),
63 stderr=open(os.devnull, 'w'))
64
65 # Rdiff Backup protocol quit command
66 quitcommand = 'q' + chr(255) + chr(0) * 7
67
68 ssh.stdin.write(quitcommand)
69 ssh.stdin.flush()
70 ssh.stdin.close()
71 ssh.wait()
72
73 if ssh.poll() is None:
74 return 1
75 if ssh.returncode != 0:
76 failed_ssh()
77 return ssh.returncode
78
79
80
81 class Recipe(GenericSlapRecipe, Notify, Callback):
82
83 def add_slave(self, entry, known_hosts_file):
84 path_list = []
85
86 url = entry.get('url')
87 if not url:
88 raise ValueError('Missing URL parameter for PBS recipe')
89
90 slave_id = entry['notification-id']
91
92 promise_path = os.path.join(self.options['promises-directory'],
93 slave_id)
94 parsed_url = urlparse.urlparse(url)
95 promise_dict = self.promise_base_dict.copy()
96 promise_dict.update(user=parsed_url.username,
97 host=parsed_url.hostname,
98 port=parsed_url.port)
99 promise = self.createPythonScript(promise_path,
100 __name__ + '.promise',
101 promise_dict)
102 path_list.append(promise)
103
104 host = parsed_url.hostname
105 known_hosts_file[host] = entry['server-key']
106
107 # XXX use -y because the host might not yet be in the
108 # trusted hosts file until the next time slapgrid is run.
109 remote_schema = '%(ssh)s -y -p %%s %(user)s@%(host)s' % \
110 {
111 'ssh': self.options['sshclient-binary'],
112 'user': parsed_url.username,
113 'host': parsed_url.hostname,
114 }
115
116 parameters = ['--remote-schema', remote_schema]
117
118 remote_directory = '%(port)s::%(path)s' % {'port': parsed_url.port,
119 'path': parsed_url.path}
120
121 local_directory = self.createDirectory(self.options['directory'], entry['name'])
122
123 if entry['type'] == 'push':
124 parameters.extend(['--restore-as-of', 'now'])
125 parameters.append('--force')
126 parameters.extend([local_directory, remote_directory])
127 comments = ['','Push data to a PBS *-import instance.','']
128 else:
129 parameters.extend([remote_directory, local_directory])
130 comments = ['','Pull data from a PBS *-export instance.','']
131
132 wrapper_basepath = os.path.join(self.options['wrappers-directory'],
133 slave_id)
134
135 if 'notify' in entry:
136 wrapper_path = wrapper_basepath + '_raw'
137 else:
138 wrapper_path = wrapper_basepath
139
140 wrapper = self.createWrapper(name=wrapper_path,
141 command=self.options['rdiffbackup-binary'],
142 parameters=parameters,
143 comments = comments)
144 path_list.append(wrapper)
145
146 if 'notify' in entry:
147 feed_url = '%s/get/%s' % (self.options['notifier-url'],
148 entry['notification-id'])
149 wrapper = self.createNotifier(notifier_binary=self.options['notifier-binary'],
150 wrapper=wrapper_basepath,
151 executable=wrapper_path,
152 log=os.path.join(self.options['feeds'], entry['notification-id']),
153 title=entry.get('title', slave_id),
154 notification_url=entry['notify'],
155 feed_url=feed_url,
156 )
157 path_list.append(wrapper)
158
159 if 'on-notification' in entry:
160 path_list.append(self.createCallback(str(entry['on-notification']),
161 wrapper))
162 else:
163 cron_entry = os.path.join(self.options['cron-entries'], slave_id)
164 with open(cron_entry, 'w') as cron_entry_file:
165 cron_entry_file.write('%s %s' % (entry['frequency'], wrapper))
166 path_list.append(cron_entry)
167
168 return path_list
169
170
171 def _install(self):
172 path_list = []
173
174 if self.optionIsTrue('client', True):
175 self.logger.info("Client mode")
176
177 slap_connection = self.buildout['slap-connection']
178 self.promise_base_dict = {
179 'server_url': slap_connection['server-url'],
180 'computer_id': slap_connection['computer-id'],
181 'cert_file': slap_connection.get('cert-file'),
182 'key_file': slap_connection.get('key-file'),
183 'partition_id': slap_connection['partition-id'],
184 'ssh_client': self.options['sshclient-binary'],
185 }
186
187 slaves = json.loads(self.options['slave-instance-list'])
188 known_hosts = KnownHostsFile(self.options['known-hosts'])
189 with known_hosts:
190 for slave in slaves:
191 path_list.extend(self.add_slave(slave, known_hosts))
192 else:
193 self.logger.info("Server mode")
194
195 wrapper = self.createWrapper(name=self.options['wrapper'],
196 command=self.options['rdiffbackup-binary'],
197 parameters=[
198 '--restrict', self.options['path'],
199 '--server'
200 ])
201 path_list.append(wrapper)
202
203 return path_list
204