Change the meaning of call_node_start_master
[ganeti-local] / lib / netutils.py
1 #
2 #
3
4 # Copyright (C) 2010 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 network utility module.
23
24 This module holds functions that can be used in both daemons (all) and
25 the command line scripts.
26
27 """
28
29
30 import errno
31 import re
32 import socket
33 import struct
34 import IN
35
36 from ganeti import constants
37 from ganeti import errors
38
39 # Structure definition for getsockopt(SOL_SOCKET, SO_PEERCRED, ...):
40 # struct ucred { pid_t pid; uid_t uid; gid_t gid; };
41 #
42 # The GNU C Library defines gid_t and uid_t to be "unsigned int" and
43 # pid_t to "int".
44 #
45 # IEEE Std 1003.1-2008:
46 # "nlink_t, uid_t, gid_t, and id_t shall be integer types"
47 # "blksize_t, pid_t, and ssize_t shall be signed integer types"
48 _STRUCT_UCRED = "iII"
49 _STRUCT_UCRED_SIZE = struct.calcsize(_STRUCT_UCRED)
50
51
52 def GetSocketCredentials(sock):
53   """Returns the credentials of the foreign process connected to a socket.
54
55   @param sock: Unix socket
56   @rtype: tuple; (number, number, number)
57   @return: The PID, UID and GID of the connected foreign process.
58
59   """
60   peercred = sock.getsockopt(socket.SOL_SOCKET, IN.SO_PEERCRED,
61                              _STRUCT_UCRED_SIZE)
62   return struct.unpack(_STRUCT_UCRED, peercred)
63
64
65 def GetHostInfo(name=None):
66   """Lookup host name and raise an OpPrereqError for failures"""
67
68   try:
69     return HostInfo(name)
70   except errors.ResolverError, err:
71     raise errors.OpPrereqError("The given name (%s) does not resolve: %s" %
72                                (err[0], err[2]), errors.ECODE_RESOLVER)
73
74
75 class HostInfo:
76   """Class implementing resolver and hostname functionality
77
78   """
79   _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$")
80
81   def __init__(self, name=None):
82     """Initialize the host name object.
83
84     If the name argument is not passed, it will use this system's
85     name.
86
87     """
88     if name is None:
89       name = self.SysName()
90
91     self.query = name
92     self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
93     self.ip = self.ipaddrs[0]
94
95   def ShortName(self):
96     """Returns the hostname without domain.
97
98     """
99     return self.name.split('.')[0]
100
101   @staticmethod
102   def SysName():
103     """Return the current system's name.
104
105     This is simply a wrapper over C{socket.gethostname()}.
106
107     """
108     return socket.gethostname()
109
110   @staticmethod
111   def LookupHostname(hostname):
112     """Look up hostname
113
114     @type hostname: str
115     @param hostname: hostname to look up
116
117     @rtype: tuple
118     @return: a tuple (name, aliases, ipaddrs) as returned by
119         C{socket.gethostbyname_ex}
120     @raise errors.ResolverError: in case of errors in resolving
121
122     """
123     try:
124       result = socket.gethostbyname_ex(hostname)
125     except (socket.gaierror, socket.herror, socket.error), err:
126       # hostname not found in DNS, or other socket exception in the
127       # (code, description format)
128       raise errors.ResolverError(hostname, err.args[0], err.args[1])
129
130     return result
131
132   @classmethod
133   def NormalizeName(cls, hostname):
134     """Validate and normalize the given hostname.
135
136     @attention: the validation is a bit more relaxed than the standards
137         require; most importantly, we allow underscores in names
138     @raise errors.OpPrereqError: when the name is not valid
139
140     """
141     hostname = hostname.lower()
142     if (not cls._VALID_NAME_RE.match(hostname) or
143         # double-dots, meaning empty label
144         ".." in hostname or
145         # empty initial label
146         hostname.startswith(".")):
147       raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
148                                  errors.ECODE_INVAL)
149     if hostname.endswith("."):
150       hostname = hostname.rstrip(".")
151     return hostname
152
153
154 def _GenericIsValidIP(family, ip):
155   """Generic internal version of ip validation.
156
157   @type family: int
158   @param family: socket.AF_INET | socket.AF_INET6
159   @type ip: str
160   @param ip: the address to be checked
161   @rtype: boolean
162   @return: True if ip is valid, False otherwise
163
164   """
165   try:
166     socket.inet_pton(family, ip)
167     return True
168   except socket.error:
169     return False
170
171
172 def IsValidIP4(ip):
173   """Verifies an IPv4 address.
174
175   This function checks if the given address is a valid IPv4 address.
176
177   @type ip: str
178   @param ip: the address to be checked
179   @rtype: boolean
180   @return: True if ip is valid, False otherwise
181
182   """
183   return _GenericIsValidIP(socket.AF_INET, ip)
184
185
186 def IsValidIP6(ip):
187   """Verifies an IPv6 address.
188
189   This function checks if the given address is a valid IPv6 address.
190
191   @type ip: str
192   @param ip: the address to be checked
193   @rtype: boolean
194   @return: True if ip is valid, False otherwise
195
196   """
197   return _GenericIsValidIP(socket.AF_INET6, ip)
198
199
200 def IsValidIP(ip):
201   """Verifies an IP address.
202
203   This function checks if the given IP address (both IPv4 and IPv6) is valid.
204
205   @type ip: str
206   @param ip: the address to be checked
207   @rtype: boolean
208   @return: True if ip is valid, False otherwise
209
210   """
211   return IsValidIP4(ip) or IsValidIP6(ip)
212
213
214 def GetAddressFamily(ip):
215   """Get the address family of the given address.
216
217   @type ip: str
218   @param ip: ip address whose family will be returned
219   @rtype: int
220   @return: socket.AF_INET or socket.AF_INET6
221   @raise errors.GenericError: for invalid addresses
222
223   """
224   if IsValidIP6(ip):
225     return socket.AF_INET6
226   elif IsValidIP4(ip):
227     return socket.AF_INET
228   else:
229     raise errors.GenericError("Address %s not valid" % ip)
230
231
232 def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
233   """Simple ping implementation using TCP connect(2).
234
235   Check if the given IP is reachable by doing attempting a TCP connect
236   to it.
237
238   @type target: str
239   @param target: the IP or hostname to ping
240   @type port: int
241   @param port: the port to connect to
242   @type timeout: int
243   @param timeout: the timeout on the connection attempt
244   @type live_port_needed: boolean
245   @param live_port_needed: whether a closed port will cause the
246       function to return failure, as if there was a timeout
247   @type source: str or None
248   @param source: if specified, will cause the connect to be made
249       from this specific source address; failures to bind other
250       than C{EADDRNOTAVAIL} will be ignored
251
252   """
253   try:
254     family = GetAddressFamily(target)
255   except errors.GenericError:
256     return False
257
258   sock = socket.socket(family, socket.SOCK_STREAM)
259   success = False
260
261   if source is not None:
262     try:
263       sock.bind((source, 0))
264     except socket.error, (errcode, _):
265       if errcode == errno.EADDRNOTAVAIL:
266         success = False
267
268   sock.settimeout(timeout)
269
270   try:
271     sock.connect((target, port))
272     sock.close()
273     success = True
274   except socket.timeout:
275     success = False
276   except socket.error, (errcode, _):
277     success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
278
279   return success
280
281
282 def OwnIpAddress(address):
283   """Check if the current host has the the given IP address.
284
285   This is done by trying to bind the given address. We return True if we
286   succeed or false if a socket.error is raised.
287
288   @type address: string
289   @param address: the address to check
290   @rtype: bool
291   @return: True if we own the address
292
293   """
294   family = GetAddressFamily(address)
295   s = socket.socket(family, socket.SOCK_DGRAM)
296   success = False
297   try:
298     try:
299       s.bind((address, 0))
300       success = True
301     except socket.error:
302       success = False
303   finally:
304     s.close()
305   return success
306
307
308 def GetDaemonPort(daemon_name):
309   """Get the daemon port for this cluster.
310
311   Note that this routine does not read a ganeti-specific file, but
312   instead uses C{socket.getservbyname} to allow pre-customization of
313   this parameter outside of Ganeti.
314
315   @type daemon_name: string
316   @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
317   @rtype: int
318
319   """
320   if daemon_name not in constants.DAEMONS_PORTS:
321     raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
322
323   (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
324   try:
325     port = socket.getservbyname(daemon_name, proto)
326   except socket.error:
327     port = default_port
328
329   return port