Statistics
| Branch: | Tag: | Revision:

root / lib / confd / querylib.py @ f299ca21

History | View | Annotate | Download (8.6 kB)

1
#
2
#
3

    
4
# Copyright (C) 2009 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 configuration daemon queries library.
23

24
"""
25

    
26
import logging
27

    
28
from ganeti import constants
29

    
30

    
31
# constants for some common errors to return from a query
32
QUERY_UNKNOWN_ENTRY_ERROR = (constants.CONFD_REPL_STATUS_ERROR,
33
                             constants.CONFD_ERROR_UNKNOWN_ENTRY)
34
QUERY_INTERNAL_ERROR = (constants.CONFD_REPL_STATUS_ERROR,
35
                        constants.CONFD_ERROR_INTERNAL)
36
QUERY_ARGUMENT_ERROR = (constants.CONFD_REPL_STATUS_ERROR,
37
                        constants.CONFD_ERROR_ARGUMENT)
38

    
39

    
40
class ConfdQuery(object):
41
  """Confd Query base class.
42

43
  """
44
  def __init__(self, reader):
45
    """Constructor for Confd Query
46

47
    @type reader: L{ssconf.SimpleConfigReader}
48
    @param reader: ConfigReader to use to access the config
49

50
    """
51
    self.reader = reader
52

    
53
  def Exec(self, query): # pylint: disable-msg=R0201,W0613
54
    """Process a single UDP request from a client.
55

56
    Different queries should override this function, which by defaults returns
57
    a "non-implemented" answer.
58

59
    @type query: (undefined)
60
    @param query: ConfdRequest 'query' field
61
    @rtype: (integer, undefined)
62
    @return: status and answer to give to the client
63

64
    """
65
    status = constants.CONFD_REPL_STATUS_NOTIMPLEMENTED
66
    answer = 'not implemented'
67
    return status, answer
68

    
69

    
70
class PingQuery(ConfdQuery):
71
  """An empty confd query.
72

73
  It will return success on an empty argument, and an error on any other
74
  argument.
75

76
  """
77
  def Exec(self, query):
78
    """PingQuery main execution.
79

80
    """
81
    if query is None:
82
      status = constants.CONFD_REPL_STATUS_OK
83
      answer = 'ok'
84
    else:
85
      status = constants.CONFD_REPL_STATUS_ERROR
86
      answer = 'non-empty ping query'
87

    
88
    return status, answer
89

    
90

    
91
class ClusterMasterQuery(ConfdQuery):
92
  """Cluster master query.
93

94
  It accepts no arguments, and returns the current cluster master.
95

96
  """
97
  def _GetMasterNode(self):
98
    return self.reader.GetMasterNode()
99

    
100
  def Exec(self, query):
101
    """ClusterMasterQuery main execution
102

103
    """
104
    if isinstance(query, dict):
105
      if constants.CONFD_REQQ_FIELDS in query:
106
        status = constants.CONFD_REPL_STATUS_OK
107
        req_fields = query[constants.CONFD_REQQ_FIELDS]
108
        if not isinstance(req_fields, (list, tuple)):
109
          logging.debug("FIELDS request should be a list")
110
          return QUERY_ARGUMENT_ERROR
111

    
112
        answer = []
113
        for field in req_fields:
114
          if field == constants.CONFD_REQFIELD_NAME:
115
            answer.append(self._GetMasterNode())
116
          elif field == constants.CONFD_REQFIELD_IP:
117
            answer.append(self.reader.GetMasterIP())
118
          elif field == constants.CONFD_REQFIELD_MNODE_PIP:
119
            answer.append(self.reader.GetNodePrimaryIp(self._GetMasterNode()))
120
      else:
121
        logging.debug("missing FIELDS in query dict")
122
        return QUERY_ARGUMENT_ERROR
123
    elif not query:
124
      status = constants.CONFD_REPL_STATUS_OK
125
      answer = self.reader.GetMasterNode()
126
    else:
127
      logging.debug("Invalid master query argument: not dict or empty")
128
      return QUERY_ARGUMENT_ERROR
129

    
130
    return status, answer
131

    
132

    
133
class NodeRoleQuery(ConfdQuery):
134
  """A query for the role of a node.
135

136
  It will return one of CONFD_NODE_ROLE_*, or an error for non-existing nodes.
137

138
  """
139
  def Exec(self, query):
140
    """EmptyQuery main execution
141

142
    """
143
    node = query
144
    if self.reader.GetMasterNode() == node:
145
      status = constants.CONFD_REPL_STATUS_OK
146
      answer = constants.CONFD_NODE_ROLE_MASTER
147
      return status, answer
148
    flags = self.reader.GetNodeStatusFlags(node)
149
    if flags is None:
150
      return QUERY_UNKNOWN_ENTRY_ERROR
151

    
152
    master_candidate, drained, offline = flags
153
    if master_candidate:
154
      answer = constants.CONFD_NODE_ROLE_CANDIDATE
155
    elif drained:
156
      answer = constants.CONFD_NODE_ROLE_DRAINED
157
    elif offline:
158
      answer = constants.CONFD_NODE_ROLE_OFFLINE
159
    else:
160
      answer = constants.CONFD_NODE_ROLE_REGULAR
161

    
162
    return constants.CONFD_REPL_STATUS_OK, answer
163

    
164

    
165
class InstanceIpToNodePrimaryIpQuery(ConfdQuery):
166
  """A query for the location of one or more instance's ips.
167

168
  """
169
  def Exec(self, query):
170
    """InstanceIpToNodePrimaryIpQuery main execution.
171

172
    @type query: string or dict
173
    @param query: instance ip or dict containing:
174
                  constants.CONFD_REQQ_LINK: nic link (optional)
175
                  constants.CONFD_REQQ_IPLIST: list of ips
176
                  constants.CONFD_REQQ_IP: single ip
177
                  (one IP type request is mandatory)
178
    @rtype: (integer, ...)
179
    @return: ((status, answer) or (success, [(status, answer)...])
180

181
    """
182
    if isinstance(query, dict):
183
      if constants.CONFD_REQQ_IP in query:
184
        instances_list = [query[constants.CONFD_REQQ_IP]]
185
        mode = constants.CONFD_REQQ_IP
186
      elif constants.CONFD_REQQ_IPLIST in query:
187
        instances_list = query[constants.CONFD_REQQ_IPLIST]
188
        mode = constants.CONFD_REQQ_IPLIST
189
      else:
190
        logging.debug("missing IP or IPLIST in query dict")
191
        return QUERY_ARGUMENT_ERROR
192

    
193
      if constants.CONFD_REQQ_LINK in query:
194
        network_link = query[constants.CONFD_REQQ_LINK]
195
      else:
196
        network_link = None # default will be used
197
    else:
198
      logging.debug("Invalid query argument type for: %s", query)
199
      return QUERY_ARGUMENT_ERROR
200

    
201
    pnodes_list = []
202

    
203
    for instance_ip in instances_list:
204
      if not isinstance(instance_ip, basestring):
205
        logging.debug("Invalid IP type for: %s", instance_ip)
206
        return QUERY_ARGUMENT_ERROR
207

    
208
      instance = self.reader.GetInstanceByLinkIp(instance_ip, network_link)
209
      if not instance:
210
        logging.debug("Unknown instance IP: %s", instance_ip)
211
        pnodes_list.append(QUERY_UNKNOWN_ENTRY_ERROR)
212
        continue
213

    
214
      pnode = self.reader.GetInstancePrimaryNode(instance)
215
      if not pnode:
216
        logging.error("Instance '%s' doesn't have an associated primary"
217
                      " node", instance)
218
        pnodes_list.append(QUERY_INTERNAL_ERROR)
219
        continue
220

    
221
      pnode_primary_ip = self.reader.GetNodePrimaryIp(pnode)
222
      if not pnode_primary_ip:
223
        logging.error("Primary node '%s' doesn't have an associated"
224
                      " primary IP", pnode)
225
        pnodes_list.append(QUERY_INTERNAL_ERROR)
226
        continue
227

    
228
      pnodes_list.append((constants.CONFD_REPL_STATUS_OK, pnode_primary_ip))
229

    
230
    # If a single ip was requested, return a single answer, otherwise
231
    # the whole list, with a success status (since each entry has its
232
    # own success/failure)
233
    if mode == constants.CONFD_REQQ_IP:
234
      return pnodes_list[0]
235

    
236
    return constants.CONFD_REPL_STATUS_OK, pnodes_list
237

    
238

    
239
class NodesPipsQuery(ConfdQuery):
240
  """A query for nodes primary IPs.
241

242
  It returns the list of nodes primary IPs.
243

244
  """
245
  def Exec(self, query):
246
    """NodesPipsQuery main execution.
247

248
    """
249
    if query is None:
250
      status = constants.CONFD_REPL_STATUS_OK
251
      answer = self.reader.GetNodesPrimaryIps()
252
    else:
253
      status = constants.CONFD_REPL_STATUS_ERROR
254
      answer = "non-empty node primary IPs query"
255

    
256
    return status, answer
257

    
258

    
259
class MasterCandidatesPipsQuery(ConfdQuery):
260
  """A query for master candidates primary IPs.
261

262
  It returns the list of master candidates primary IPs.
263

264
  """
265
  def Exec(self, query):
266
    """MasterCandidatesPipsQuery main execution.
267

268
    """
269
    if query is None:
270
      status = constants.CONFD_REPL_STATUS_OK
271
      answer = self.reader.GetMasterCandidatesPrimaryIps()
272
    else:
273
      status = constants.CONFD_REPL_STATUS_ERROR
274
      answer = "non-empty master candidates primary IPs query"
275

    
276
    return status, answer
277

    
278

    
279
class InstancesIpsQuery(ConfdQuery):
280
  """A query for instances IPs.
281

282
  It returns the list of IPs of NICs connected to the requested link or all the
283
  instances IPs if no link is submitted.
284

285
  """
286
  def Exec(self, query):
287
    """InstancesIpsQuery main execution.
288

289
    """
290
    link = query
291
    status = constants.CONFD_REPL_STATUS_OK
292
    answer = self.reader.GetInstancesIps(link)
293

    
294
    return status, answer