Revision a8083063
b/COPYING | ||
---|---|---|
1 |
GNU GENERAL PUBLIC LICENSE |
|
2 |
Version 2, June 1991 |
|
3 |
|
|
4 |
Copyright (C) 1989, 1991 Free Software Foundation, Inc. |
|
5 |
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
6 |
Everyone is permitted to copy and distribute verbatim copies |
|
7 |
of this license document, but changing it is not allowed. |
|
8 |
|
|
9 |
Preamble |
|
10 |
|
|
11 |
The licenses for most software are designed to take away your |
|
12 |
freedom to share and change it. By contrast, the GNU General Public |
|
13 |
License is intended to guarantee your freedom to share and change free |
|
14 |
software--to make sure the software is free for all its users. This |
|
15 |
General Public License applies to most of the Free Software |
|
16 |
Foundation's software and to any other program whose authors commit to |
|
17 |
using it. (Some other Free Software Foundation software is covered by |
|
18 |
the GNU Library General Public License instead.) You can apply it to |
|
19 |
your programs, too. |
|
20 |
|
|
21 |
When we speak of free software, we are referring to freedom, not |
|
22 |
price. Our General Public Licenses are designed to make sure that you |
|
23 |
have the freedom to distribute copies of free software (and charge for |
|
24 |
this service if you wish), that you receive source code or can get it |
|
25 |
if you want it, that you can change the software or use pieces of it |
|
26 |
in new free programs; and that you know you can do these things. |
|
27 |
|
|
28 |
To protect your rights, we need to make restrictions that forbid |
|
29 |
anyone to deny you these rights or to ask you to surrender the rights. |
|
30 |
These restrictions translate to certain responsibilities for you if you |
|
31 |
distribute copies of the software, or if you modify it. |
|
32 |
|
|
33 |
For example, if you distribute copies of such a program, whether |
|
34 |
gratis or for a fee, you must give the recipients all the rights that |
|
35 |
you have. You must make sure that they, too, receive or can get the |
|
36 |
source code. And you must show them these terms so they know their |
|
37 |
rights. |
|
38 |
|
|
39 |
We protect your rights with two steps: (1) copyright the software, and |
|
40 |
(2) offer you this license which gives you legal permission to copy, |
|
41 |
distribute and/or modify the software. |
|
42 |
|
|
43 |
Also, for each author's protection and ours, we want to make certain |
|
44 |
that everyone understands that there is no warranty for this free |
|
45 |
software. If the software is modified by someone else and passed on, we |
|
46 |
want its recipients to know that what they have is not the original, so |
|
47 |
that any problems introduced by others will not reflect on the original |
|
48 |
authors' reputations. |
|
49 |
|
|
50 |
Finally, any free program is threatened constantly by software |
|
51 |
patents. We wish to avoid the danger that redistributors of a free |
|
52 |
program will individually obtain patent licenses, in effect making the |
|
53 |
program proprietary. To prevent this, we have made it clear that any |
|
54 |
patent must be licensed for everyone's free use or not licensed at all. |
|
55 |
|
|
56 |
The precise terms and conditions for copying, distribution and |
|
57 |
modification follow. |
|
58 |
|
|
59 |
GNU GENERAL PUBLIC LICENSE |
|
60 |
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION |
|
61 |
|
|
62 |
0. This License applies to any program or other work which contains |
|
63 |
a notice placed by the copyright holder saying it may be distributed |
|
64 |
under the terms of this General Public License. The "Program", below, |
|
65 |
refers to any such program or work, and a "work based on the Program" |
|
66 |
means either the Program or any derivative work under copyright law: |
|
67 |
that is to say, a work containing the Program or a portion of it, |
|
68 |
either verbatim or with modifications and/or translated into another |
|
69 |
language. (Hereinafter, translation is included without limitation in |
|
70 |
the term "modification".) Each licensee is addressed as "you". |
|
71 |
|
|
72 |
Activities other than copying, distribution and modification are not |
|
73 |
covered by this License; they are outside its scope. The act of |
|
74 |
running the Program is not restricted, and the output from the Program |
|
75 |
is covered only if its contents constitute a work based on the |
|
76 |
Program (independent of having been made by running the Program). |
|
77 |
Whether that is true depends on what the Program does. |
|
78 |
|
|
79 |
1. You may copy and distribute verbatim copies of the Program's |
|
80 |
source code as you receive it, in any medium, provided that you |
|
81 |
conspicuously and appropriately publish on each copy an appropriate |
|
82 |
copyright notice and disclaimer of warranty; keep intact all the |
|
83 |
notices that refer to this License and to the absence of any warranty; |
|
84 |
and give any other recipients of the Program a copy of this License |
|
85 |
along with the Program. |
|
86 |
|
|
87 |
You may charge a fee for the physical act of transferring a copy, and |
|
88 |
you may at your option offer warranty protection in exchange for a fee. |
|
89 |
|
|
90 |
2. You may modify your copy or copies of the Program or any portion |
|
91 |
of it, thus forming a work based on the Program, and copy and |
|
92 |
distribute such modifications or work under the terms of Section 1 |
|
93 |
above, provided that you also meet all of these conditions: |
|
94 |
|
|
95 |
a) You must cause the modified files to carry prominent notices |
|
96 |
stating that you changed the files and the date of any change. |
|
97 |
|
|
98 |
b) You must cause any work that you distribute or publish, that in |
|
99 |
whole or in part contains or is derived from the Program or any |
|
100 |
part thereof, to be licensed as a whole at no charge to all third |
|
101 |
parties under the terms of this License. |
|
102 |
|
|
103 |
c) If the modified program normally reads commands interactively |
|
104 |
when run, you must cause it, when started running for such |
|
105 |
interactive use in the most ordinary way, to print or display an |
|
106 |
announcement including an appropriate copyright notice and a |
|
107 |
notice that there is no warranty (or else, saying that you provide |
|
108 |
a warranty) and that users may redistribute the program under |
|
109 |
these conditions, and telling the user how to view a copy of this |
|
110 |
License. (Exception: if the Program itself is interactive but |
|
111 |
does not normally print such an announcement, your work based on |
|
112 |
the Program is not required to print an announcement.) |
|
113 |
|
|
114 |
These requirements apply to the modified work as a whole. If |
|
115 |
identifiable sections of that work are not derived from the Program, |
|
116 |
and can be reasonably considered independent and separate works in |
|
117 |
themselves, then this License, and its terms, do not apply to those |
|
118 |
sections when you distribute them as separate works. But when you |
|
119 |
distribute the same sections as part of a whole which is a work based |
|
120 |
on the Program, the distribution of the whole must be on the terms of |
|
121 |
this License, whose permissions for other licensees extend to the |
|
122 |
entire whole, and thus to each and every part regardless of who wrote it. |
|
123 |
|
|
124 |
Thus, it is not the intent of this section to claim rights or contest |
|
125 |
your rights to work written entirely by you; rather, the intent is to |
|
126 |
exercise the right to control the distribution of derivative or |
|
127 |
collective works based on the Program. |
|
128 |
|
|
129 |
In addition, mere aggregation of another work not based on the Program |
|
130 |
with the Program (or with a work based on the Program) on a volume of |
|
131 |
a storage or distribution medium does not bring the other work under |
|
132 |
the scope of this License. |
|
133 |
|
|
134 |
3. You may copy and distribute the Program (or a work based on it, |
|
135 |
under Section 2) in object code or executable form under the terms of |
|
136 |
Sections 1 and 2 above provided that you also do one of the following: |
|
137 |
|
|
138 |
a) Accompany it with the complete corresponding machine-readable |
|
139 |
source code, which must be distributed under the terms of Sections |
|
140 |
1 and 2 above on a medium customarily used for software interchange; or, |
|
141 |
|
|
142 |
b) Accompany it with a written offer, valid for at least three |
|
143 |
years, to give any third party, for a charge no more than your |
|
144 |
cost of physically performing source distribution, a complete |
|
145 |
machine-readable copy of the corresponding source code, to be |
|
146 |
distributed under the terms of Sections 1 and 2 above on a medium |
|
147 |
customarily used for software interchange; or, |
|
148 |
|
|
149 |
c) Accompany it with the information you received as to the offer |
|
150 |
to distribute corresponding source code. (This alternative is |
|
151 |
allowed only for noncommercial distribution and only if you |
|
152 |
received the program in object code or executable form with such |
|
153 |
an offer, in accord with Subsection b above.) |
|
154 |
|
|
155 |
The source code for a work means the preferred form of the work for |
|
156 |
making modifications to it. For an executable work, complete source |
|
157 |
code means all the source code for all modules it contains, plus any |
|
158 |
associated interface definition files, plus the scripts used to |
|
159 |
control compilation and installation of the executable. However, as a |
|
160 |
special exception, the source code distributed need not include |
|
161 |
anything that is normally distributed (in either source or binary |
|
162 |
form) with the major components (compiler, kernel, and so on) of the |
|
163 |
operating system on which the executable runs, unless that component |
|
164 |
itself accompanies the executable. |
|
165 |
|
|
166 |
If distribution of executable or object code is made by offering |
|
167 |
access to copy from a designated place, then offering equivalent |
|
168 |
access to copy the source code from the same place counts as |
|
169 |
distribution of the source code, even though third parties are not |
|
170 |
compelled to copy the source along with the object code. |
|
171 |
|
|
172 |
4. You may not copy, modify, sublicense, or distribute the Program |
|
173 |
except as expressly provided under this License. Any attempt |
|
174 |
otherwise to copy, modify, sublicense or distribute the Program is |
|
175 |
void, and will automatically terminate your rights under this License. |
|
176 |
However, parties who have received copies, or rights, from you under |
|
177 |
this License will not have their licenses terminated so long as such |
|
178 |
parties remain in full compliance. |
|
179 |
|
|
180 |
5. You are not required to accept this License, since you have not |
|
181 |
signed it. However, nothing else grants you permission to modify or |
|
182 |
distribute the Program or its derivative works. These actions are |
|
183 |
prohibited by law if you do not accept this License. Therefore, by |
|
184 |
modifying or distributing the Program (or any work based on the |
|
185 |
Program), you indicate your acceptance of this License to do so, and |
|
186 |
all its terms and conditions for copying, distributing or modifying |
|
187 |
the Program or works based on it. |
|
188 |
|
|
189 |
6. Each time you redistribute the Program (or any work based on the |
|
190 |
Program), the recipient automatically receives a license from the |
|
191 |
original licensor to copy, distribute or modify the Program subject to |
|
192 |
these terms and conditions. You may not impose any further |
|
193 |
restrictions on the recipients' exercise of the rights granted herein. |
|
194 |
You are not responsible for enforcing compliance by third parties to |
|
195 |
this License. |
|
196 |
|
|
197 |
7. If, as a consequence of a court judgment or allegation of patent |
|
198 |
infringement or for any other reason (not limited to patent issues), |
|
199 |
conditions are imposed on you (whether by court order, agreement or |
|
200 |
otherwise) that contradict the conditions of this License, they do not |
|
201 |
excuse you from the conditions of this License. If you cannot |
|
202 |
distribute so as to satisfy simultaneously your obligations under this |
|
203 |
License and any other pertinent obligations, then as a consequence you |
|
204 |
may not distribute the Program at all. For example, if a patent |
|
205 |
license would not permit royalty-free redistribution of the Program by |
|
206 |
all those who receive copies directly or indirectly through you, then |
|
207 |
the only way you could satisfy both it and this License would be to |
|
208 |
refrain entirely from distribution of the Program. |
|
209 |
|
|
210 |
If any portion of this section is held invalid or unenforceable under |
|
211 |
any particular circumstance, the balance of the section is intended to |
|
212 |
apply and the section as a whole is intended to apply in other |
|
213 |
circumstances. |
|
214 |
|
|
215 |
It is not the purpose of this section to induce you to infringe any |
|
216 |
patents or other property right claims or to contest validity of any |
|
217 |
such claims; this section has the sole purpose of protecting the |
|
218 |
integrity of the free software distribution system, which is |
|
219 |
implemented by public license practices. Many people have made |
|
220 |
generous contributions to the wide range of software distributed |
|
221 |
through that system in reliance on consistent application of that |
|
222 |
system; it is up to the author/donor to decide if he or she is willing |
|
223 |
to distribute software through any other system and a licensee cannot |
|
224 |
impose that choice. |
|
225 |
|
|
226 |
This section is intended to make thoroughly clear what is believed to |
|
227 |
be a consequence of the rest of this License. |
|
228 |
|
|
229 |
8. If the distribution and/or use of the Program is restricted in |
|
230 |
certain countries either by patents or by copyrighted interfaces, the |
|
231 |
original copyright holder who places the Program under this License |
|
232 |
may add an explicit geographical distribution limitation excluding |
|
233 |
those countries, so that distribution is permitted only in or among |
|
234 |
countries not thus excluded. In such case, this License incorporates |
|
235 |
the limitation as if written in the body of this License. |
|
236 |
|
|
237 |
9. The Free Software Foundation may publish revised and/or new versions |
|
238 |
of the General Public License from time to time. Such new versions will |
|
239 |
be similar in spirit to the present version, but may differ in detail to |
|
240 |
address new problems or concerns. |
|
241 |
|
|
242 |
Each version is given a distinguishing version number. If the Program |
|
243 |
specifies a version number of this License which applies to it and "any |
|
244 |
later version", you have the option of following the terms and conditions |
|
245 |
either of that version or of any later version published by the Free |
|
246 |
Software Foundation. If the Program does not specify a version number of |
|
247 |
this License, you may choose any version ever published by the Free Software |
|
248 |
Foundation. |
|
249 |
|
|
250 |
10. If you wish to incorporate parts of the Program into other free |
|
251 |
programs whose distribution conditions are different, write to the author |
|
252 |
to ask for permission. For software which is copyrighted by the Free |
|
253 |
Software Foundation, write to the Free Software Foundation; we sometimes |
|
254 |
make exceptions for this. Our decision will be guided by the two goals |
|
255 |
of preserving the free status of all derivatives of our free software and |
|
256 |
of promoting the sharing and reuse of software generally. |
|
257 |
|
|
258 |
NO WARRANTY |
|
259 |
|
|
260 |
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY |
|
261 |
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN |
|
262 |
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES |
|
263 |
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED |
|
264 |
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
|
265 |
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS |
|
266 |
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE |
|
267 |
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, |
|
268 |
REPAIR OR CORRECTION. |
|
269 |
|
|
270 |
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
|
271 |
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR |
|
272 |
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, |
|
273 |
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING |
|
274 |
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED |
|
275 |
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY |
|
276 |
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER |
|
277 |
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE |
|
278 |
POSSIBILITY OF SUCH DAMAGES. |
|
279 |
|
|
280 |
END OF TERMS AND CONDITIONS |
|
281 |
|
|
282 |
How to Apply These Terms to Your New Programs |
|
283 |
|
|
284 |
If you develop a new program, and you want it to be of the greatest |
|
285 |
possible use to the public, the best way to achieve this is to make it |
|
286 |
free software which everyone can redistribute and change under these terms. |
|
287 |
|
|
288 |
To do so, attach the following notices to the program. It is safest |
|
289 |
to attach them to the start of each source file to most effectively |
|
290 |
convey the exclusion of warranty; and each file should have at least |
|
291 |
the "copyright" line and a pointer to where the full notice is found. |
|
292 |
|
|
293 |
<one line to give the program's name and a brief idea of what it does.> |
|
294 |
Copyright (C) <year> <name of author> |
|
295 |
|
|
296 |
This program is free software; you can redistribute it and/or modify |
|
297 |
it under the terms of the GNU General Public License as published by |
|
298 |
the Free Software Foundation; either version 2 of the License, or |
|
299 |
(at your option) any later version. |
|
300 |
|
|
301 |
This program is distributed in the hope that it will be useful, |
|
302 |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
303 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
304 |
GNU General Public License for more details. |
|
305 |
|
|
306 |
You should have received a copy of the GNU General Public License |
|
307 |
along with this program; if not, write to the Free Software |
|
308 |
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
309 |
|
|
310 |
|
|
311 |
Also add information on how to contact you by electronic and paper mail. |
|
312 |
|
|
313 |
If the program is interactive, make it output a short notice like this |
|
314 |
when it starts in an interactive mode: |
|
315 |
|
|
316 |
Gnomovision version 69, Copyright (C) year name of author |
|
317 |
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. |
|
318 |
This is free software, and you are welcome to redistribute it |
|
319 |
under certain conditions; type `show c' for details. |
|
320 |
|
|
321 |
The hypothetical commands `show w' and `show c' should show the appropriate |
|
322 |
parts of the General Public License. Of course, the commands you use may |
|
323 |
be called something other than `show w' and `show c'; they could even be |
|
324 |
mouse-clicks or menu items--whatever suits your program. |
|
325 |
|
|
326 |
You should also get your employer (if you work as a programmer) or your |
|
327 |
school, if any, to sign a "copyright disclaimer" for the program, if |
|
328 |
necessary. Here is a sample; alter the names: |
|
329 |
|
|
330 |
Yoyodyne, Inc., hereby disclaims all copyright interest in the program |
|
331 |
`Gnomovision' (which makes passes at compilers) written by James Hacker. |
|
332 |
|
|
333 |
<signature of Ty Coon>, 1 April 1989 |
|
334 |
Ty Coon, President of Vice |
|
335 |
|
|
336 |
This General Public License does not permit incorporating your program into |
|
337 |
proprietary programs. If your program is a subroutine library, you may |
|
338 |
consider it more useful to permit linking proprietary applications with the |
|
339 |
library. If this is what you want to do, use the GNU Library General |
|
340 |
Public License instead of this License. |
b/INSTALL | ||
---|---|---|
1 |
Installation of the software |
|
2 |
============================ |
|
3 |
|
|
4 |
Before installing, please verify that you have the following programs: |
|
5 |
- lvm 2 |
|
6 |
- ssh |
|
7 |
- fping |
|
8 |
- python twisted library (the core is enough) |
|
9 |
- python openssl bindings |
|
10 |
|
|
11 |
To install, simply do ./configure && make && make install |
|
12 |
|
|
13 |
This will install the software under /usr/local. You then need to copy |
|
14 |
ganeti.init to /etc/init.d and integrate it into your boot sequence |
|
15 |
(``chkconfig``, ``update-rc.d``, etc.). |
|
16 |
|
|
17 |
Cluster initialisation |
|
18 |
====================== |
|
19 |
|
|
20 |
Before initialising the cluster, on each node you need to create the following |
|
21 |
directories: |
|
22 |
|
|
23 |
- /etc/ganeti |
|
24 |
- /var/log/ganeti |
|
25 |
- /var/lib/ganeti |
|
26 |
- /srv/ganeti and /srv/ganeti/os |
|
27 |
|
|
28 |
After this, use ``gnt-cluster init``. |
b/Makefile.am | ||
---|---|---|
1 |
# standard automake rules |
|
2 |
|
|
3 |
SUBDIRS = man lib scripts daemons docs testing tools |
|
4 |
EXTRA_DIST = ganeti.initd |
|
5 |
|
|
6 |
# custom rules |
|
7 |
depgraph: depgraph.png |
|
8 |
|
|
9 |
depgraph.png: depgraph.dot |
|
10 |
dot -Tpng -o $@ $< |
|
11 |
|
|
12 |
depgraph.ps: depgraph.dot |
|
13 |
dot -Tps -o $@ $< |
|
14 |
|
|
15 |
depgraph.dot: ganeti/*.py |
|
16 |
pylint.python2.4 --indent-string ' ' --rcfile=/dev/null --reports y --int-import-graph $@ --persistent n ganeti >/dev/null |
b/README | ||
---|---|---|
1 |
Ganeti 1.2 |
|
2 |
========== |
|
3 |
|
|
4 |
For installation instructions, read the INSTALL file. |
|
5 |
|
|
6 |
For a brief introduction, read the ganeti(7) manpage and the other pages |
|
7 |
it suggests. |
b/configure.ac | ||
---|---|---|
1 |
# -*- Autoconf -*- |
|
2 |
# Process this file with autoconf to produce a configure script. |
|
3 |
|
|
4 |
AC_PREREQ(2.59) |
|
5 |
AC_INIT(ganeti, 1.2a, ganeti@googlegroups.com) |
|
6 |
AM_INIT_AUTOMAKE(foreign) |
|
7 |
|
|
8 |
# Checks for programs. |
|
9 |
AC_PROG_INSTALL |
|
10 |
|
|
11 |
# Checks for python |
|
12 |
AM_PATH_PYTHON(2.4) |
|
13 |
|
|
14 |
# Checks for libraries. |
|
15 |
|
|
16 |
# Checks for header files. |
|
17 |
|
|
18 |
# Checks for typedefs, structures, and compiler characteristics. |
|
19 |
|
|
20 |
# Checks for library functions. |
|
21 |
|
|
22 |
AC_CONFIG_FILES([Makefile man/Makefile docs/Makefile |
|
23 |
testing/Makefile tools/Makefile |
|
24 |
lib/Makefile scripts/Makefile daemons/Makefile]) |
|
25 |
AC_OUTPUT |
b/daemons/Makefile.am | ||
---|---|---|
1 |
dist_sbin_SCRIPTS = ganeti-noded ganeti-watcher |
b/daemons/ganeti-noded | ||
---|---|---|
1 |
#!/usr/bin/python |
|
2 |
# |
|
3 |
|
|
4 |
# Copyright (C) 2006, 2007 Google Inc. |
|
5 |
# |
|
6 |
# This program is free software; you can redistribute it and/or modify |
|
7 |
# it under the terms of the GNU General Public License as published by |
|
8 |
# the Free Software Foundation; either version 2 of the License, or |
|
9 |
# (at your option) any later version. |
|
10 |
# |
|
11 |
# This program is distributed in the hope that it will be useful, but |
|
12 |
# WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
14 |
# General Public License for more details. |
|
15 |
# |
|
16 |
# You should have received a copy of the GNU General Public License |
|
17 |
# along with this program; if not, write to the Free Software |
|
18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
19 |
# 02110-1301, USA. |
|
20 |
|
|
21 |
|
|
22 |
"""Ganeti node daemon""" |
|
23 |
|
|
24 |
import os |
|
25 |
import sys |
|
26 |
import resource |
|
27 |
import traceback |
|
28 |
|
|
29 |
from optparse import OptionParser |
|
30 |
|
|
31 |
|
|
32 |
from ganeti import backend |
|
33 |
from ganeti import logger |
|
34 |
from ganeti import constants |
|
35 |
from ganeti import objects |
|
36 |
from ganeti import errors |
|
37 |
from ganeti import ssconf |
|
38 |
|
|
39 |
from twisted.spread import pb |
|
40 |
from twisted.internet import reactor |
|
41 |
from twisted.cred import checkers, portal |
|
42 |
from OpenSSL import SSL |
|
43 |
|
|
44 |
|
|
45 |
class ServerContextFactory: |
|
46 |
def getContext(self): |
|
47 |
ctx = SSL.Context(SSL.TLSv1_METHOD) |
|
48 |
ctx.use_certificate_file(constants.SSL_CERT_FILE) |
|
49 |
ctx.use_privatekey_file(constants.SSL_CERT_FILE) |
|
50 |
return ctx |
|
51 |
|
|
52 |
class ServerObject(pb.Avatar): |
|
53 |
def __init__(self, name): |
|
54 |
self.name = name |
|
55 |
|
|
56 |
def perspectiveMessageReceived(self, broker, message, args, kw): |
|
57 |
"""This method is called when a network message is received. |
|
58 |
|
|
59 |
I will call:: |
|
60 |
|
|
61 |
| self.perspective_%(message)s(*broker.unserialize(args), |
|
62 |
| **broker.unserialize(kw)) |
|
63 |
|
|
64 |
to handle the method; subclasses of Avatar are expected to |
|
65 |
implement methods of this naming convention. |
|
66 |
""" |
|
67 |
|
|
68 |
args = broker.unserialize(args, self) |
|
69 |
kw = broker.unserialize(kw, self) |
|
70 |
method = getattr(self, "perspective_%s" % message) |
|
71 |
tb = None |
|
72 |
state = None |
|
73 |
try: |
|
74 |
state = method(*args, **kw) |
|
75 |
except: |
|
76 |
tb = traceback.format_exc() |
|
77 |
|
|
78 |
return broker.serialize((tb, state), self, method, args, kw) |
|
79 |
|
|
80 |
# the new block devices -------------------------- |
|
81 |
|
|
82 |
def perspective_blockdev_create(self,params): |
|
83 |
bdev_s, size, on_primary = params |
|
84 |
bdev = objects.ConfigObject.Loads(bdev_s) |
|
85 |
if bdev is None: |
|
86 |
raise ValueError("can't unserialize data!") |
|
87 |
return backend.CreateBlockDevice(bdev, size, on_primary) |
|
88 |
|
|
89 |
|
|
90 |
def perspective_blockdev_remove(self,params): |
|
91 |
bdev_s = params[0] |
|
92 |
bdev = objects.ConfigObject.Loads(bdev_s) |
|
93 |
return backend.RemoveBlockDevice(bdev) |
|
94 |
|
|
95 |
|
|
96 |
def perspective_blockdev_assemble(self,params): |
|
97 |
bdev_s, on_primary = params |
|
98 |
bdev = objects.ConfigObject.Loads(bdev_s) |
|
99 |
if bdev is None: |
|
100 |
raise ValueError("can't unserialize data!") |
|
101 |
return backend.AssembleBlockDevice(bdev, on_primary) |
|
102 |
|
|
103 |
|
|
104 |
def perspective_blockdev_shutdown(self,params): |
|
105 |
bdev_s = params[0] |
|
106 |
bdev = objects.ConfigObject.Loads(bdev_s) |
|
107 |
if bdev is None: |
|
108 |
raise ValueError("can't unserialize data!") |
|
109 |
return backend.ShutdownBlockDevice(bdev) |
|
110 |
|
|
111 |
|
|
112 |
def perspective_blockdev_addchild(self,params): |
|
113 |
bdev_s, ndev_s = params |
|
114 |
bdev = objects.ConfigObject.Loads(bdev_s) |
|
115 |
ndev = objects.ConfigObject.Loads(ndev_s) |
|
116 |
if bdev is None or ndev is None: |
|
117 |
raise ValueError("can't unserialize data!") |
|
118 |
return backend.MirrorAddChild(bdev, ndev) |
|
119 |
|
|
120 |
|
|
121 |
def perspective_blockdev_removechild(self,params): |
|
122 |
bdev_s, ndev_s = params |
|
123 |
bdev = objects.ConfigObject.Loads(bdev_s) |
|
124 |
ndev = objects.ConfigObject.Loads(ndev_s) |
|
125 |
if bdev is None or ndev is None: |
|
126 |
raise ValueError("can't unserialize data!") |
|
127 |
return backend.MirrorRemoveChild(bdev, ndev) |
|
128 |
|
|
129 |
def perspective_blockdev_getmirrorstatus(self, params): |
|
130 |
disks = [objects.ConfigObject.Loads(dsk_s) |
|
131 |
for dsk_s in params] |
|
132 |
return backend.GetMirrorStatus(disks) |
|
133 |
|
|
134 |
def perspective_blockdev_find(self, params): |
|
135 |
disk = objects.ConfigObject.Loads(params[0]) |
|
136 |
return backend.FindBlockDevice(disk) |
|
137 |
|
|
138 |
def perspective_blockdev_snapshot(self,params): |
|
139 |
cfbd = objects.ConfigObject.Loads(params[0]) |
|
140 |
return backend.SnapshotBlockDevice(cfbd) |
|
141 |
|
|
142 |
# export/import -------------------------- |
|
143 |
|
|
144 |
def perspective_snapshot_export(self,params): |
|
145 |
disk = objects.ConfigObject.Loads(params[0]) |
|
146 |
dest_node = params[1] |
|
147 |
instance = objects.ConfigObject.Loads(params[2]) |
|
148 |
return backend.ExportSnapshot(disk,dest_node,instance) |
|
149 |
|
|
150 |
def perspective_finalize_export(self,params): |
|
151 |
instance = objects.ConfigObject.Loads(params[0]) |
|
152 |
snap_disks = [objects.ConfigObject.Loads(str_data) |
|
153 |
for str_data in params[1]] |
|
154 |
return backend.FinalizeExport(instance, snap_disks) |
|
155 |
|
|
156 |
def perspective_export_info(self,params): |
|
157 |
dir = params[0] |
|
158 |
einfo = backend.ExportInfo(dir) |
|
159 |
if einfo is None: |
|
160 |
return einfo |
|
161 |
return einfo.Dumps() |
|
162 |
|
|
163 |
def perspective_export_list(self, params): |
|
164 |
return backend.ListExports() |
|
165 |
|
|
166 |
def perspective_export_remove(self, params): |
|
167 |
export = params[0] |
|
168 |
return backend.RemoveExport(export) |
|
169 |
|
|
170 |
# volume -------------------------- |
|
171 |
|
|
172 |
def perspective_volume_list(self,params): |
|
173 |
vgname = params[0] |
|
174 |
return backend.GetVolumeList(vgname) |
|
175 |
|
|
176 |
def perspective_vg_list(self,params): |
|
177 |
return backend.ListVolumeGroups() |
|
178 |
|
|
179 |
# bridge -------------------------- |
|
180 |
|
|
181 |
def perspective_bridges_exist(self,params): |
|
182 |
bridges_list = params[0] |
|
183 |
return backend.BridgesExist(bridges_list) |
|
184 |
|
|
185 |
# instance -------------------------- |
|
186 |
|
|
187 |
def perspective_instance_os_add(self,params): |
|
188 |
inst_s, os_disk, swap_disk = params |
|
189 |
inst = objects.ConfigObject.Loads(inst_s) |
|
190 |
return backend.AddOSToInstance(inst, os_disk, swap_disk) |
|
191 |
|
|
192 |
def perspective_instance_os_import(self, params): |
|
193 |
inst_s, os_disk, swap_disk, src_node, src_image = params |
|
194 |
inst = objects.ConfigObject.Loads(inst_s) |
|
195 |
return backend.ImportOSIntoInstance(inst, os_disk, swap_disk, |
|
196 |
src_node, src_image) |
|
197 |
|
|
198 |
def perspective_instance_shutdown(self,params): |
|
199 |
instance = objects.ConfigObject.Loads(params[0]) |
|
200 |
return backend.ShutdownInstance(instance) |
|
201 |
|
|
202 |
def perspective_instance_start(self,params): |
|
203 |
instance = objects.ConfigObject.Loads(params[0]) |
|
204 |
extra_args = params[1] |
|
205 |
return backend.StartInstance(instance, extra_args) |
|
206 |
|
|
207 |
def perspective_instance_info(self,params): |
|
208 |
return backend.GetInstanceInfo(params[0]) |
|
209 |
|
|
210 |
def perspective_all_instances_info(self,params): |
|
211 |
return backend.GetAllInstancesInfo() |
|
212 |
|
|
213 |
def perspective_instance_list(self,params): |
|
214 |
return backend.GetInstanceList() |
|
215 |
|
|
216 |
# node -------------------------- |
|
217 |
|
|
218 |
def perspective_node_info(self,params): |
|
219 |
vgname = params[0] |
|
220 |
return backend.GetNodeInfo(vgname) |
|
221 |
|
|
222 |
def perspective_node_add(self,params): |
|
223 |
return backend.AddNode(params[0], params[1], params[2], |
|
224 |
params[3], params[4], params[5]) |
|
225 |
|
|
226 |
def perspective_node_verify(self,params): |
|
227 |
return backend.VerifyNode(params[0]) |
|
228 |
|
|
229 |
def perspective_node_start_master(self, params): |
|
230 |
return backend.StartMaster() |
|
231 |
|
|
232 |
def perspective_node_stop_master(self, params): |
|
233 |
return backend.StopMaster() |
|
234 |
|
|
235 |
def perspective_node_leave_cluster(self, params): |
|
236 |
return backend.LeaveCluster() |
|
237 |
|
|
238 |
# cluster -------------------------- |
|
239 |
|
|
240 |
def perspective_version(self,params): |
|
241 |
return constants.PROTOCOL_VERSION |
|
242 |
|
|
243 |
def perspective_configfile_list(self,params): |
|
244 |
return backend.ListConfigFiles() |
|
245 |
|
|
246 |
def perspective_upload_file(self,params): |
|
247 |
return backend.UploadFile(*params) |
|
248 |
|
|
249 |
|
|
250 |
# os ----------------------- |
|
251 |
|
|
252 |
def perspective_os_diagnose(self, params): |
|
253 |
os_list = backend.DiagnoseOS() |
|
254 |
if not os_list: |
|
255 |
# this catches also return values of 'False', |
|
256 |
# for which we can't iterate over |
|
257 |
return os_list |
|
258 |
result = [] |
|
259 |
for data in os_list: |
|
260 |
if isinstance(data, objects.OS): |
|
261 |
result.append(data.Dumps()) |
|
262 |
elif isinstance(data, errors.InvalidOS): |
|
263 |
result.append(data.args) |
|
264 |
else: |
|
265 |
raise errors.ProgrammerError, ("Invalid result from backend.DiagnoseOS" |
|
266 |
" (class %s, %s)" % |
|
267 |
(str(data.__class__), data)) |
|
268 |
|
|
269 |
return result |
|
270 |
|
|
271 |
def perspective_os_get(self, params): |
|
272 |
name = params[0] |
|
273 |
try: |
|
274 |
os = backend.OSFromDisk(name).Dumps() |
|
275 |
except errors.InvalidOS, err: |
|
276 |
os = err.args |
|
277 |
return os |
|
278 |
|
|
279 |
# hooks ----------------------- |
|
280 |
|
|
281 |
def perspective_hooks_runner(self, params): |
|
282 |
hpath, phase, env = params |
|
283 |
hr = backend.HooksRunner() |
|
284 |
return hr.RunHooks(hpath, phase, env) |
|
285 |
|
|
286 |
|
|
287 |
class MyRealm: |
|
288 |
__implements__ = portal.IRealm |
|
289 |
def requestAvatar(self, avatarId, mind, *interfaces): |
|
290 |
if pb.IPerspective not in interfaces: |
|
291 |
raise NotImplementedError |
|
292 |
return pb.IPerspective, ServerObject(avatarId), lambda:None |
|
293 |
|
|
294 |
|
|
295 |
def ParseOptions(): |
|
296 |
"""Parse the command line options. |
|
297 |
|
|
298 |
Returns: |
|
299 |
(options, args) as from OptionParser.parse_args() |
|
300 |
|
|
301 |
""" |
|
302 |
parser = OptionParser(description="Ganeti node daemon", |
|
303 |
usage="%prog [-f] [-d]", |
|
304 |
version="%%prog (ganeti) %s" % |
|
305 |
constants.RELEASE_VERSION) |
|
306 |
|
|
307 |
parser.add_option("-f", "--foreground", dest="fork", |
|
308 |
help="Don't detach from the current terminal", |
|
309 |
default=True, action="store_false") |
|
310 |
parser.add_option("-d", "--debug", dest="debug", |
|
311 |
help="Enable some debug messages", |
|
312 |
default=False, action="store_true") |
|
313 |
options, args = parser.parse_args() |
|
314 |
return options, args |
|
315 |
|
|
316 |
|
|
317 |
def main(): |
|
318 |
options, args = ParseOptions() |
|
319 |
for fname in (constants.SSL_CERT_FILE,): |
|
320 |
if not os.path.isfile(fname): |
|
321 |
print "config %s not there, will not run." % fname |
|
322 |
sys.exit(5) |
|
323 |
|
|
324 |
try: |
|
325 |
ss = ssconf.SimpleStore() |
|
326 |
port = ss.GetNodeDaemonPort() |
|
327 |
pwdata = ss.GetNodeDaemonPassword() |
|
328 |
except errors.ConfigurationError, err: |
|
329 |
print "Cluster configuration incomplete: '%s'" % str(err) |
|
330 |
sys.exit(5) |
|
331 |
|
|
332 |
# become a daemon |
|
333 |
if options.fork: |
|
334 |
createDaemon() |
|
335 |
|
|
336 |
logger.SetupLogging(twisted_workaround=True, debug=options.debug, |
|
337 |
program="ganeti-noded") |
|
338 |
|
|
339 |
p = portal.Portal(MyRealm()) |
|
340 |
p.registerChecker( |
|
341 |
checkers.InMemoryUsernamePasswordDatabaseDontUse(master_node=pwdata)) |
|
342 |
reactor.listenSSL(port, pb.PBServerFactory(p), ServerContextFactory()) |
|
343 |
reactor.run() |
|
344 |
|
|
345 |
|
|
346 |
def createDaemon(): |
|
347 |
"""Detach a process from the controlling terminal and run it in the |
|
348 |
background as a daemon. |
|
349 |
""" |
|
350 |
UMASK = 077 |
|
351 |
WORKDIR = "/" |
|
352 |
# Default maximum for the number of available file descriptors. |
|
353 |
if 'SC_OPEN_MAX' in os.sysconf_names: |
|
354 |
try: |
|
355 |
MAXFD = os.sysconf('SC_OPEN_MAX') |
|
356 |
if MAXFD < 0: |
|
357 |
MAXFD = 1024 |
|
358 |
except OSError: |
|
359 |
MAXFD = 1024 |
|
360 |
else: |
|
361 |
MAXFD = 1024 |
|
362 |
# The standard I/O file descriptors are redirected to /dev/null by default. |
|
363 |
#REDIRECT_TO = getattr(os, "devnull", "/dev/null") |
|
364 |
REDIRECT_TO = constants.LOG_NODESERVER |
|
365 |
try: |
|
366 |
pid = os.fork() |
|
367 |
except OSError, e: |
|
368 |
raise Exception, "%s [%d]" % (e.strerror, e.errno) |
|
369 |
if (pid == 0): # The first child. |
|
370 |
os.setsid() |
|
371 |
try: |
|
372 |
pid = os.fork() # Fork a second child. |
|
373 |
except OSError, e: |
|
374 |
raise Exception, "%s [%d]" % (e.strerror, e.errno) |
|
375 |
if (pid == 0): # The second child. |
|
376 |
os.chdir(WORKDIR) |
|
377 |
os.umask(UMASK) |
|
378 |
else: |
|
379 |
# exit() or _exit()? See below. |
|
380 |
os._exit(0) # Exit parent (the first child) of the second child. |
|
381 |
else: |
|
382 |
os._exit(0) # Exit parent of the first child. |
|
383 |
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] |
|
384 |
if (maxfd == resource.RLIM_INFINITY): |
|
385 |
maxfd = MAXFD |
|
386 |
|
|
387 |
# Iterate through and close all file descriptors. |
|
388 |
for fd in range(0, maxfd): |
|
389 |
try: |
|
390 |
os.close(fd) |
|
391 |
except OSError: # ERROR, fd wasn't open to begin with (ignored) |
|
392 |
pass |
|
393 |
os.open(REDIRECT_TO, os.O_RDWR|os.O_CREAT|os.O_APPEND) # standard input (0) |
|
394 |
# Duplicate standard input to standard output and standard error. |
|
395 |
os.dup2(0, 1) # standard output (1) |
|
396 |
os.dup2(0, 2) # standard error (2) |
|
397 |
return(0) |
|
398 |
|
|
399 |
|
|
400 |
if __name__=='__main__': |
|
401 |
main() |
b/daemons/ganeti-watcher | ||
---|---|---|
1 |
#!/usr/bin/python |
|
2 |
# |
|
3 |
|
|
4 |
# Copyright (C) 2006, 2007 Google Inc. |
|
5 |
# |
|
6 |
# This program is free software; you can redistribute it and/or modify |
|
7 |
# it under the terms of the GNU General Public License as published by |
|
8 |
# the Free Software Foundation; either version 2 of the License, or |
|
9 |
# (at your option) any later version. |
|
10 |
# |
|
11 |
# This program is distributed in the hope that it will be useful, but |
|
12 |
# WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
14 |
# General Public License for more details. |
|
15 |
# |
|
16 |
# You should have received a copy of the GNU General Public License |
|
17 |
# along with this program; if not, write to the Free Software |
|
18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
19 |
# 02110-1301, USA. |
|
20 |
|
|
21 |
|
|
22 |
"""Tool to restart erronously downed virtual machines. |
|
23 |
|
|
24 |
This program and set of classes implement a watchdog to restart |
|
25 |
virtual machines in a Ganeti cluster that have crashed or been killed |
|
26 |
by a node reboot. Run from cron or similar. |
|
27 |
""" |
|
28 |
|
|
29 |
|
|
30 |
LOGFILE = '/var/log/ganeti/watcher.log' |
|
31 |
MAXTRIES = 5 |
|
32 |
BAD_STATES = ['stopped'] |
|
33 |
HELPLESS_STATES = ['(node down)'] |
|
34 |
NOTICE = 'NOTICE' |
|
35 |
ERROR = 'ERROR' |
|
36 |
|
|
37 |
import os |
|
38 |
import sys |
|
39 |
import time |
|
40 |
import fcntl |
|
41 |
import errno |
|
42 |
from optparse import OptionParser |
|
43 |
|
|
44 |
|
|
45 |
from ganeti import utils |
|
46 |
from ganeti import constants |
|
47 |
|
|
48 |
|
|
49 |
class Error(Exception): |
|
50 |
"""Generic custom error class.""" |
|
51 |
pass |
|
52 |
|
|
53 |
|
|
54 |
def Indent(s, prefix='| '): |
|
55 |
"""Indent a piece of text with a given prefix before each line. |
|
56 |
|
|
57 |
Args: |
|
58 |
s: The string to indent |
|
59 |
prefix: The string to prepend each line. |
|
60 |
""" |
|
61 |
return "%s%s\n" % (prefix, ('\n' + prefix).join(s.splitlines())) |
|
62 |
|
|
63 |
|
|
64 |
def DoCmd(cmd): |
|
65 |
"""Run a shell command. |
|
66 |
|
|
67 |
Args: |
|
68 |
cmd: the command to run. |
|
69 |
|
|
70 |
Raises CommandError with verbose commentary on error. |
|
71 |
""" |
|
72 |
res = utils.RunCmd(cmd) |
|
73 |
|
|
74 |
if res.failed: |
|
75 |
raise Error("Command %s failed:\n%s\nstdout:\n%sstderr:\n%s" % |
|
76 |
(repr(cmd), |
|
77 |
Indent(res.fail_reason), |
|
78 |
Indent(res.stdout), |
|
79 |
Indent(res.stderr))) |
|
80 |
|
|
81 |
return res |
|
82 |
|
|
83 |
|
|
84 |
class RestarterState(object): |
|
85 |
"""Interface to a state file recording restart attempts. |
|
86 |
|
|
87 |
Methods: |
|
88 |
Open(): open, lock, read and parse the file. |
|
89 |
Raises StandardError on lock contention. |
|
90 |
|
|
91 |
NumberOfAttempts(name): returns the number of times in succession |
|
92 |
a restart has been attempted of the named instance. |
|
93 |
|
|
94 |
RecordAttempt(name, when): records one restart attempt of name at |
|
95 |
time in when. |
|
96 |
|
|
97 |
Remove(name): remove record given by name, if exists. |
|
98 |
|
|
99 |
Save(name): saves all records to file, releases lock and closes file. |
|
100 |
""" |
|
101 |
def __init__(self): |
|
102 |
# The two-step dance below is necessary to allow both opening existing |
|
103 |
# file read/write and creating if not existing. Vanilla open will truncate |
|
104 |
# an existing file -or- allow creating if not existing. |
|
105 |
f = os.open(constants.WATCHER_STATEFILE, os.O_RDWR | os.O_CREAT) |
|
106 |
f = os.fdopen(f, 'w+') |
|
107 |
|
|
108 |
try: |
|
109 |
fcntl.flock(f.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB) |
|
110 |
except IOError, x: |
|
111 |
if x.errno == errno.EAGAIN: |
|
112 |
raise StandardError('State file already locked') |
|
113 |
raise |
|
114 |
|
|
115 |
self.statefile = f |
|
116 |
self.inst_map = {} |
|
117 |
|
|
118 |
for line in f: |
|
119 |
name, when, count = line.rstrip().split(':') |
|
120 |
|
|
121 |
when = int(when) |
|
122 |
count = int(count) |
|
123 |
|
|
124 |
self.inst_map[name] = (when, count) |
|
125 |
|
|
126 |
def NumberOfAttempts(self, instance): |
|
127 |
"""Returns number of previous restart attempts. |
|
128 |
|
|
129 |
Args: |
|
130 |
instance - the instance to look up. |
|
131 |
""" |
|
132 |
assert self.statefile |
|
133 |
|
|
134 |
if instance.name in self.inst_map: |
|
135 |
return self.inst_map[instance.name][1] |
|
136 |
|
|
137 |
return 0 |
|
138 |
|
|
139 |
def RecordAttempt(self, instance): |
|
140 |
"""Record a restart attempt. |
|
141 |
|
|
142 |
Args: |
|
143 |
instance - the instance being restarted |
|
144 |
""" |
|
145 |
assert self.statefile |
|
146 |
|
|
147 |
when = time.time() |
|
148 |
|
|
149 |
self.inst_map[instance.name] = (when, 1 + self.NumberOfAttempts(instance)) |
|
150 |
|
|
151 |
def Remove(self, instance): |
|
152 |
"""Update state to reflect that a machine is running, i.e. remove record |
|
153 |
|
|
154 |
Args: |
|
155 |
instance - the instance to remove from books |
|
156 |
|
|
157 |
This method removes the record for a named instance |
|
158 |
""" |
|
159 |
assert self.statefile |
|
160 |
|
|
161 |
if instance.name in self.inst_map: |
|
162 |
del self.inst_map[instance.name] |
|
163 |
|
|
164 |
def Save(self): |
|
165 |
"""Save records to file, then unlock and close file. |
|
166 |
""" |
|
167 |
assert self.statefile |
|
168 |
|
|
169 |
self.statefile.seek(0) |
|
170 |
self.statefile.truncate() |
|
171 |
|
|
172 |
for name in self.inst_map: |
|
173 |
print >> self.statefile, "%s:%d:%d" % ((name,) + self.inst_map[name]) |
|
174 |
|
|
175 |
fcntl.flock(self.statefile.fileno(), fcntl.LOCK_UN) |
|
176 |
|
|
177 |
self.statefile.close() |
|
178 |
self.statefile = None |
|
179 |
|
|
180 |
|
|
181 |
class Instance(object): |
|
182 |
"""Abstraction for a Virtual Machine instance. |
|
183 |
|
|
184 |
Methods: |
|
185 |
Restart(): issue a command to restart the represented machine. |
|
186 |
""" |
|
187 |
def __init__(self, name, state): |
|
188 |
self.name = name |
|
189 |
self.state = state |
|
190 |
|
|
191 |
def Restart(self): |
|
192 |
DoCmd(['gnt-instance', 'startup', '--lock-retries=15', self.name]) |
|
193 |
|
|
194 |
|
|
195 |
class InstanceList(object): |
|
196 |
"""The set of Virtual Machine instances on a cluster. |
|
197 |
""" |
|
198 |
cmd = ['gnt-instance', 'list', '--lock-retries=15', |
|
199 |
'-o', 'name,admin_state,oper_state', '--no-headers', '--separator=:'] |
|
200 |
|
|
201 |
def __init__(self): |
|
202 |
res = DoCmd(self.cmd) |
|
203 |
|
|
204 |
lines = res.stdout.splitlines() |
|
205 |
|
|
206 |
self.instances = [] |
|
207 |
for line in lines: |
|
208 |
fields = [fld.strip() for fld in line.split(':')] |
|
209 |
|
|
210 |
if len(fields) != 3: |
|
211 |
continue |
|
212 |
if fields[1] == "no": #no autostart, we don't care about this instance |
|
213 |
continue |
|
214 |
name, status = fields[0], fields[2] |
|
215 |
|
|
216 |
self.instances.append(Instance(name, status)) |
|
217 |
|
|
218 |
def __iter__(self): |
|
219 |
return self.instances.__iter__() |
|
220 |
|
|
221 |
|
|
222 |
class Message(object): |
|
223 |
"""Encapsulation of a notice or error message. |
|
224 |
""" |
|
225 |
def __init__(self, level, msg): |
|
226 |
self.level = level |
|
227 |
self.msg = msg |
|
228 |
self.when = time.time() |
|
229 |
|
|
230 |
def __str__(self): |
|
231 |
return self.level + ' ' + time.ctime(self.when) + '\n' + Indent(self.msg) |
|
232 |
|
|
233 |
|
|
234 |
class Restarter(object): |
|
235 |
"""Encapsulate the logic for restarting erronously halted virtual machines. |
|
236 |
|
|
237 |
The calling program should periodically instantiate me and call Run(). |
|
238 |
This will traverse the list of instances, and make up to MAXTRIES attempts |
|
239 |
to restart machines that are down. |
|
240 |
""" |
|
241 |
def __init__(self): |
|
242 |
self.instances = InstanceList() |
|
243 |
self.messages = [] |
|
244 |
|
|
245 |
def Run(self): |
|
246 |
"""Make a pass over the list of instances, restarting downed ones. |
|
247 |
""" |
|
248 |
notepad = RestarterState() |
|
249 |
|
|
250 |
for instance in self.instances: |
|
251 |
if instance.state in BAD_STATES: |
|
252 |
n = notepad.NumberOfAttempts(instance) |
|
253 |
|
|
254 |
if n > MAXTRIES: |
|
255 |
# stay quiet. |
|
256 |
continue |
|
257 |
elif n < MAXTRIES: |
|
258 |
last = " (Attempt #%d)" % (n + 1) |
|
259 |
else: |
|
260 |
notepad.RecordAttempt(instance) |
|
261 |
self.messages.append(Message(ERROR, "Could not restart %s for %d" |
|
262 |
" times, giving up..." % |
|
263 |
(instance.name, MAXTRIES))) |
|
264 |
continue |
|
265 |
try: |
|
266 |
self.messages.append(Message(NOTICE, |
|
267 |
"Restarting %s%s." % |
|
268 |
(instance.name, last))) |
|
269 |
instance.Restart() |
|
270 |
except Error, x: |
|
271 |
self.messages.append(Message(ERROR, str(x))) |
|
272 |
|
|
273 |
notepad.RecordAttempt(instance) |
|
274 |
elif instance.state in HELPLESS_STATES: |
|
275 |
if notepad.NumberOfAttempts(instance): |
|
276 |
notepad.Remove(instance) |
|
277 |
else: |
|
278 |
if notepad.NumberOfAttempts(instance): |
|
279 |
notepad.Remove(instance) |
|
280 |
msg = Message(NOTICE, |
|
281 |
"Restart of %s succeeded." % instance.name) |
|
282 |
self.messages.append(msg) |
|
283 |
|
|
284 |
notepad.Save() |
|
285 |
|
|
286 |
def WriteReport(self, logfile): |
|
287 |
""" |
|
288 |
Log all messages to file. |
|
289 |
|
|
290 |
Args: |
|
291 |
logfile: file object open for writing (the log file) |
|
292 |
""" |
|
293 |
for msg in self.messages: |
|
294 |
print >> logfile, str(msg) |
|
295 |
|
|
296 |
|
|
297 |
def ParseOptions(): |
|
298 |
"""Parse the command line options. |
|
299 |
|
|
300 |
Returns: |
|
301 |
(options, args) as from OptionParser.parse_args() |
|
302 |
|
|
303 |
""" |
|
304 |
parser = OptionParser(description="Ganeti cluster watcher", |
|
305 |
usage="%prog [-d]", |
|
306 |
version="%%prog (ganeti) %s" % |
|
307 |
constants.RELEASE_VERSION) |
|
308 |
|
|
309 |
parser.add_option("-d", "--debug", dest="debug", |
|
310 |
help="Don't redirect messages to the log file", |
|
311 |
default=False, action="store_true") |
|
312 |
options, args = parser.parse_args() |
|
313 |
return options, args |
|
314 |
|
|
315 |
|
|
316 |
def main(): |
|
317 |
"""Main function. |
|
318 |
|
|
319 |
""" |
|
320 |
options, args = ParseOptions() |
|
321 |
|
|
322 |
if not options.debug: |
|
323 |
sys.stderr = sys.stdout = open(LOGFILE, 'a') |
|
324 |
|
|
325 |
try: |
|
326 |
restarter = Restarter() |
|
327 |
restarter.Run() |
|
328 |
restarter.WriteReport(sys.stdout) |
|
329 |
except Error, err: |
|
330 |
print err |
|
331 |
|
|
332 |
if __name__ == '__main__': |
|
333 |
main() |
b/docs/Makefile.am | ||
---|---|---|
1 |
docdir = $(datadir)/doc/$(PACKAGE) |
|
2 |
|
|
3 |
dist_doc_DATA = hooks.html hooks.pdf |
|
4 |
EXTRA_DIST = hooks.sgml |
|
5 |
|
|
6 |
%.html: %.sgml |
|
7 |
docbook2html --nochunks $< |
|
8 |
|
|
9 |
%.pdf: %.sgml |
|
10 |
docbook2pdf $< |
b/docs/hooks.sgml | ||
---|---|---|
1 |
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook V4.2//EN" [ |
|
2 |
]> |
|
3 |
<article class="specification"> |
|
4 |
<articleinfo> |
|
5 |
<title>Ganeti customisation using hooks</title> |
|
6 |
</articleinfo> |
|
7 |
<para>Documents ganeti version 1.2</para> |
|
8 |
<section> |
|
9 |
<title>Introduction</title> |
|
10 |
|
|
11 |
<para> |
|
12 |
In order to allow customisation of operations, ganeti will run |
|
13 |
scripts under <filename |
|
14 |
class="directory">/etc/ganeti/hooks</filename> based on certain |
|
15 |
rules. |
|
16 |
</para> |
|
17 |
|
|
18 |
<para>This is similar to the <filename |
|
19 |
class="directory">/etc/network/</filename> structure present in |
|
20 |
Debian for network interface handling.</para> |
|
21 |
|
|
22 |
</section> |
|
23 |
|
|
24 |
|
|
25 |
<section> |
|
26 |
<title>Organisation</title> |
|
27 |
|
|
28 |
<para>For every operation, two sets of scripts are run: |
|
29 |
|
|
30 |
<itemizedlist> |
|
31 |
<listitem> |
|
32 |
<simpara>pre phase (for authorization/checking)</simpara> |
|
33 |
</listitem> |
|
34 |
<listitem> |
|
35 |
<simpara>post phase (for logging)</simpara> |
|
36 |
</listitem> |
|
37 |
</itemizedlist> |
|
38 |
</para> |
|
39 |
|
|
40 |
<para>Also, for each operation, the scripts are run on one or |
|
41 |
more nodes, depending on the operation type.</para> |
|
42 |
|
|
43 |
<para>Note that, even though we call them scripts, we are |
|
44 |
actually talking about any executable.</para> |
|
45 |
|
|
46 |
<section> |
|
47 |
<title><emphasis>pre</emphasis> scripts</title> |
|
48 |
|
|
49 |
<para>The <emphasis>pre</emphasis> scripts have a definite |
|
50 |
target: to check that the operation is allowed given the |
|
51 |
site-specific constraints. You could have, for example, a rule |
|
52 |
that says every new instance is required to exists in a |
|
53 |
database; to implement this, you could write a script that |
|
54 |
checks the new instance parameters against your |
|
55 |
database.</para> |
|
56 |
|
|
57 |
<para>The objective of these scripts should be their return |
|
58 |
code (zero or non-zero for success and failure). However, if |
|
59 |
they modify the environment in any way, they should be |
|
60 |
idempotent, as failed executions could be restarted and thus |
|
61 |
the script(s) run again with exactly the same |
|
62 |
parameters.</para> |
|
63 |
|
|
64 |
</section> |
|
65 |
|
|
66 |
<section> |
|
67 |
<title><emphasis>post</emphasis> scripts</title> |
|
68 |
|
|
69 |
<para>These scripts should do whatever you need as a reaction |
|
70 |
to the completion of an operation. Their return code is not |
|
71 |
checked (but logged), and they should not depend on the fact |
|
72 |
that the <emphasis>pre</emphasis> scripts have been |
|
73 |
run.</para> |
|
74 |
|
|
75 |
</section> |
|
76 |
|
|
77 |
<section> |
|
78 |
<title>Naming</title> |
|
79 |
|
|
80 |
<para>The allowed names for the scripts consist of (similar to |
|
81 |
<citerefentry> <refentrytitle>run-parts</refentrytitle> |
|
82 |
<manvolnum>8</manvolnum> </citerefentry>) upper and lower |
|
83 |
case, digits, underscores and hyphens. In other words, the |
|
84 |
regexp |
|
85 |
<computeroutput>^[a-zA-Z0-9_-]+$</computeroutput>. Also, |
|
86 |
non-executable scripts will be ignored. |
|
87 |
</para> |
|
88 |
</section> |
|
89 |
|
|
90 |
<section> |
|
91 |
<title>Order of execution</title> |
|
92 |
|
|
93 |
<para>On a single node, the scripts in a directory are run in |
|
94 |
lexicographic order (more exactly, the python string |
|
95 |
comparison order). It is advisable to implement the usual |
|
96 |
<emphasis>NN-name</emphasis> convention where |
|
97 |
<emphasis>NN</emphasis> is a two digit number.</para> |
|
98 |
|
|
99 |
<para>For an operation whose hooks are run on multiple nodes, |
|
100 |
there is no specific ordering of nodes with regard to hooks |
|
101 |
execution; you should assume that the scripts are run in |
|
102 |
parallel on the target nodes (keeping on each node the above |
|
103 |
specified ordering). If you need any kind of inter-node |
|
104 |
synchronisation, you have to implement it yourself in the |
|
105 |
scripts.</para> |
|
106 |
|
|
107 |
</section> |
|
108 |
|
|
109 |
<section> |
|
110 |
<title>Execution environment</title> |
|
111 |
|
|
112 |
<para>The scripts will be run as follows: |
|
113 |
<itemizedlist> |
|
114 |
<listitem> |
|
115 |
<simpara>no command line arguments</simpara> |
|
116 |
</listitem> |
|
117 |
<listitem> |
|
118 |
<simpara>no controlling <acronym>tty</acronym></simpara> |
|
119 |
</listitem> |
|
120 |
<listitem> |
|
121 |
<simpara><varname>stdin</varname> is |
|
122 |
actually <filename>/dev/null</filename></simpara> |
|
123 |
</listitem> |
|
124 |
<listitem> |
|
125 |
<simpara><varname>stdout</varname> and |
|
126 |
<varname>stderr</varname> are directed to |
|
127 |
files</simpara> |
|
128 |
</listitem> |
|
129 |
<listitem> |
|
130 |
<simpara>the <varname>PATH</varname> is reset to |
|
131 |
<literal>/sbin:/bin:/usr/sbin:/usr/bin</literal></simpara> |
|
132 |
</listitem> |
|
133 |
<listitem> |
|
134 |
<simpara>the environment is cleared, and only |
|
135 |
ganeti-specific variables will be left</simpara> |
|
136 |
</listitem> |
|
137 |
</itemizedlist> |
|
138 |
|
|
139 |
</para> |
|
140 |
|
|
141 |
<para>All informations about the cluster is passed using |
|
142 |
environment variables. Different operations will have sligthly |
|
143 |
different environments, but most of the variables are |
|
144 |
common.</para> |
|
145 |
|
|
146 |
</section> |
|
147 |
|
|
148 |
|
|
149 |
<section> |
|
150 |
<title>Operation list</title> |
|
151 |
<table> |
|
152 |
<title>Operation list</title> |
|
153 |
<tgroup cols="7"> |
|
154 |
<colspec> |
|
155 |
<colspec> |
|
156 |
<colspec> |
|
157 |
<colspec> |
|
158 |
<colspec> |
|
159 |
<colspec colname="prehooks"> |
|
160 |
<colspec colname="posthooks"> |
|
161 |
<spanspec namest="prehooks" nameend="posthooks" |
|
162 |
spanname="bothhooks"> |
|
163 |
<thead> |
|
164 |
<row> |
|
165 |
<entry>Operation ID</entry> |
|
166 |
<entry>Directory prefix</entry> |
|
167 |
<entry>Description</entry> |
|
168 |
<entry>Command</entry> |
|
169 |
<entry>Supported env. variables</entry> |
|
170 |
<entry><emphasis>pre</emphasis> hooks</entry> |
|
171 |
<entry><emphasis>post</emphasis> hooks</entry> |
|
172 |
</row> |
|
173 |
</thead> |
|
174 |
<tbody> |
|
175 |
<row> |
|
176 |
<entry>OP_INIT_CLUSTER</entry> |
|
177 |
<entry><filename class="directory">cluster-init</filename></entry> |
|
178 |
<entry>Initialises the cluster</entry> |
|
179 |
<entry><computeroutput>gnt-cluster init</computeroutput></entry> |
|
180 |
<entry><constant>CLUSTER</constant>, <constant>MASTER</constant></entry> |
|
181 |
<entry spanname="bothhooks">master node, cluster name</entry> |
|
182 |
</row> |
|
183 |
<row> |
|
184 |
<entry>OP_MASTER_FAILOVER</entry> |
|
185 |
<entry><filename class="directory">master-failover</filename></entry> |
|
186 |
<entry>Changes the master</entry> |
|
187 |
<entry><computeroutput>gnt-cluster master-failover</computeroutput></entry> |
|
188 |
<entry><constant>OLD_MASTER</constant>, <constant>NEW_MASTER</constant></entry> |
|
189 |
<entry>the new master</entry> |
|
190 |
<entry>all nodes</entry> |
|
191 |
</row> |
|
192 |
<row> |
|
193 |
<entry>OP_ADD_NODE</entry> |
|
194 |
<entry><filename class="directory">node-add</filename></entry> |
|
195 |
<entry>Adds a new node to the cluster</entry> |
|
196 |
<entry><computeroutput>gnt-node add</computeroutput></entry> |
|
197 |
<entry><constant>NODE_NAME</constant>, <constant>NODE_PIP</constant>, <constant>NODE_SIP</constant></entry> |
|
198 |
<entry>all existing nodes</entry> |
|
199 |
<entry>all existing nodes plus the new node</entry> |
|
200 |
</row> |
|
201 |
<row> |
|
202 |
<entry>OP_REMOVE_NODE</entry> |
|
203 |
<entry><filename class="directory">node-remove</filename></entry> |
|
204 |
<entry>Removes a node from the cluster</entry> |
|
205 |
<entry><computeroutput>gnt-node remove</computeroutput></entry> |
|
206 |
<entry><constant>NODE_NAME</constant></entry> |
|
207 |
<entry spanname="bothhooks">all existing nodes except the removed node</entry> |
|
208 |
</row> |
|
209 |
<row> |
|
210 |
<entry>OP_INSTANCE_ADD</entry> |
|
211 |
<entry><filename class="directory">instance-add</filename></entry> |
|
212 |
<entry>Creates a new instance</entry> |
|
213 |
<entry><computeroutput>gnt-instance add</computeroutput></entry> |
|
214 |
<entry><constant>INSTANCE_NAME</constant>, <constant>INSTANCE_PRIMARY</constant>, <constant>INSTANCE_SECONDARIES</constant>, <constant>DISK_TEMPLATE</constant>, <constant>MEM_SIZE</constant>, <constant>DISK_SIZE</constant>, <constant>SWAP_SIZE</constant>, <constant>VCPUS</constant>, <constant>INSTANCE_IP</constant>, <constant>INSTANCE_ADD_MODE</constant>, <constant>SRC_NODE</constant>, <constant>SRC_PATH</constant>, <constant>SRC_IMAGE</constant></entry> |
|
215 |
<entry spanname="bothhooks" morerows="4">master node, primary and |
|
216 |
secondary nodes</entry> |
|
217 |
</row> |
|
218 |
<row> |
|
219 |
<entry>OP_BACKUP_EXPORT</entry> |
|
220 |
<entry><filename class="directory">instance-export</filename></entry> |
|
221 |
<entry>Export the instance</entry> |
|
222 |
<entry><computeroutput>gnt-backup export</computeroutput></entry> |
|
223 |
<entry><constant>INSTANCE_NAME</constant>, <constant>EXPORT_NODE</constant>, <constant>EXPORT_DO_SHUTDOWN</constant></entry> |
|
224 |
</row> |
|
225 |
<row> |
|
226 |
<entry>OP_INSTANCE_START</entry> |
|
227 |
<entry><filename class="directory">instance-start</filename></entry> |
|
228 |
<entry>Starts an instance</entry> |
|
229 |
<entry><computeroutput>gnt-instance start</computeroutput></entry> |
|
230 |
<entry><constant>INSTANCE_NAME</constant>, <constant>INSTANCE_PRIMARY</constant>, <constant>INSTANCE_SECONDARIES</constant>, <constant>FORCE</constant></entry> |
|
231 |
</row> |
|
232 |
<row> |
|
233 |
<entry>OP_INSTANCE_SHUTDOWN</entry> |
|
234 |
<entry><filename class="directory">instance-shutdown</filename></entry> |
|
235 |
<entry>Stops an instance</entry> |
|
236 |
<entry><computeroutput>gnt-instance shutdown</computeroutput></entry> |
|
237 |
<entry><constant>INSTANCE_NAME</constant>, <constant>INSTANCE_PRIMARY</constant>, <constant>INSTANCE_SECONDARIES</constant></entry> |
|
238 |
</row> |
|
239 |
<row> |
|
240 |
<entry>OP_INSTANCE_MODIFY</entry> |
|
241 |
<entry><filename class="directory">instance-modify</filename></entry> |
|
242 |
<entry>Modifies the instance parameters.</entry> |
|
243 |
<entry><computeroutput>gnt-instance modify</computeroutput></entry> |
|
244 |
<entry><constant>INSTANCE_NAME</constant>, <constant>MEM_SIZE</constant>, <constant>VCPUS</constant>, <constant>INSTANCE_IP</constant></entry> |
|
245 |
</row> |
|
246 |
<row> |
|
247 |
<entry>OP_INSTANCE_FAILOVER</entry> |
|
248 |
<entry><filename class="directory">instance-failover</filename></entry> |
|
249 |
<entry>Failover an instance</entry> |
|
250 |
<entry><computeroutput>gnt-instance start</computeroutput></entry> |
|
251 |
<entry><constant>INSTANCE_NAME</constant>, <constant>INSTANCE_PRIMARY</constant>, <constant>INSTANCE_SECONDARIES</constant>, <constant>IGNORE_CONSISTENCY</constant></entry> |
|
252 |
</row> |
|
253 |
<row> |
|
254 |
<entry>OP_INSTANCE_REMOVE</entry> |
|
255 |
<entry><filename class="directory">instance-remove</filename></entry> |
|
256 |
<entry>Remove an instance</entry> |
|
257 |
<entry><computeroutput>gnt-instance remove</computeroutput></entry> |
|
258 |
<entry><constant>INSTANCE_NAME</constant>, <constant>INSTANCE_PRIMARY</constant>, <constant>INSTANCE_SECONDARIES</constant></entry> |
|
259 |
</row> |
|
260 |
<row> |
|
261 |
<entry>OP_INSTANCE_ADD_MDDRBD</entry> |
|
262 |
<entry><filename class="directory">mirror-add</filename></entry> |
|
263 |
<entry>Adds a mirror component</entry> |
|
264 |
<entry><computeroutput>gnt-instance add-mirror</computeroutput></entry> |
|
265 |
<entry><constant>INSTANCE_NAME</constant>, <constant>NEW_SECONDARY</constant>, <constant>DISK_NAME</constant></entry> |
|
266 |
</row> |
|
267 |
<row> |
|
268 |
<entry>OP_INSTANCE_REMOVE_MDDRBD</entry> |
|
269 |
<entry><filename class="directory">mirror-remove</filename></entry> |
|
270 |
<entry>Removes a mirror component</entry> |
|
271 |
<entry><computeroutput>gnt-instance remove-mirror</computeroutput></entry> |
|
272 |
<entry><constant>INSTANCE_NAME</constant>, <constant>OLD_SECONDARY</constant>, <constant>DISK_NAME</constant>, <constant>DISK_ID</constant></entry> |
|
273 |
</row> |
|
274 |
<row> |
Also available in: Unified diff