querylib.py 8.78 KB
Newer Older
Michael Hanselmann's avatar
Michael Hanselmann committed
1
#
Guido Trotter's avatar
Guido Trotter committed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#

# Copyright (C) 2009, Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


"""Ganeti configuration daemon queries library.

"""

Guido Trotter's avatar
Guido Trotter committed
26
27
import logging

Guido Trotter's avatar
Guido Trotter committed
28
29
from ganeti import constants

Michael Hanselmann's avatar
Michael Hanselmann committed
30

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

Michael Hanselmann's avatar
Michael Hanselmann committed
39

Guido Trotter's avatar
Guido Trotter committed
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class ConfdQuery(object):
  """Confd Query base class.

  """
  def __init__(self, reader):
    """Constructor for Confd Query

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

    """
    self.reader = reader

  def Exec(self, query):
    """Process a single UDP request from a client.

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

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

    """
    status = constants.CONFD_REPL_STATUS_NOTIMPLEMENTED
    answer = 'not implemented'
    return status, answer


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

73
74
  It will return success on an empty argument, and an error on any other
  argument.
Guido Trotter's avatar
Guido Trotter committed
75
76
77

  """
  def Exec(self, query):
Guido Trotter's avatar
Guido Trotter committed
78
    """PingQuery main execution.
Guido Trotter's avatar
Guido Trotter committed
79
80
81
82
83
84
85
86
87
88
89

    """
    if query is None:
      status = constants.CONFD_REPL_STATUS_OK
      answer = 'ok'
    else:
      status = constants.CONFD_REPL_STATUS_ERROR
      answer = 'non-empty ping query'

    return status, answer

Michael Hanselmann's avatar
Michael Hanselmann committed
90

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

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

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

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

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

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

    return status, answer

Guido Trotter's avatar
Guido Trotter committed
132
133

class NodeRoleQuery(ConfdQuery):
Guido Trotter's avatar
Guido Trotter committed
134
  """A query for the role of a node.
Guido Trotter's avatar
Guido Trotter committed
135

Guido Trotter's avatar
Guido Trotter committed
136
  It will return one of CONFD_NODE_ROLE_*, or an error for non-existing nodes.
Guido Trotter's avatar
Guido Trotter committed
137
138
139
140
141
142
143
144
145
146
147
148
149

  """
  def Exec(self, query):
    """EmptyQuery main execution

    """
    node = query
    if self.reader.GetMasterNode() == node:
      status = constants.CONFD_REPL_STATUS_OK
      answer = constants.CONFD_NODE_ROLE_MASTER
      return status, answer
    flags = self.reader.GetNodeStatusFlags(node)
    if flags is None:
150
      return QUERY_UNKNOWN_ENTRY_ERROR
Guido Trotter's avatar
Guido Trotter committed
151
152
153
154
155
156
157
158
159
160
161
162
163

    master_candidate, drained, offline = flags
    if master_candidate:
      answer = constants.CONFD_NODE_ROLE_CANDIDATE
    elif drained:
      answer = constants.CONFD_NODE_ROLE_DRAINED
    elif offline:
      answer = constants.CONFD_NODE_ROLE_OFFLINE
    else:
      answer = constants.CONFD_NODE_ROLE_REGULAR

    return constants.CONFD_REPL_STATUS_OK, answer

Guido Trotter's avatar
Guido Trotter committed
164
165

class InstanceIpToNodePrimaryIpQuery(ConfdQuery):
166
  """A query for the location of one or more instance's ips.
Guido Trotter's avatar
Guido Trotter committed
167
168
169

  """
  def Exec(self, query):
Guido Trotter's avatar
Guido Trotter committed
170
    """InstanceIpToNodePrimaryIpQuery main execution.
Guido Trotter's avatar
Guido Trotter committed
171

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

Guido Trotter's avatar
Guido Trotter committed
181
    """
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
    if isinstance(query, dict):
      if constants.CONFD_REQQ_IP in query:
        instances_list = [query[constants.CONFD_REQQ_IP]]
        mode = constants.CONFD_REQQ_IP
      elif constants.CONFD_REQQ_IPLIST in query:
        instances_list = query[constants.CONFD_REQQ_IPLIST]
        mode = constants.CONFD_REQQ_IPLIST
      else:
        status = constants.CONFD_REPL_STATUS_ERROR
        logging.debug("missing IP or IPLIST in query dict")
        return QUERY_ARGUMENT_ERROR

      if constants.CONFD_REQQ_LINK in query:
        network_link = query[constants.CONFD_REQQ_LINK]
      else:
        network_link = None # default will be used
    elif isinstance(query, basestring):
      # 2.1 beta1 and beta2 mode, to be deprecated for 2.2
200
      instances_list = [query]
201
202
203
      network_link = None
      mode = constants.CONFD_REQQ_IP
    else:
Iustin Pop's avatar
Iustin Pop committed
204
      logging.debug("Invalid query argument type for: %s", query)
205
206
      return QUERY_ARGUMENT_ERROR

207
208
209
    pnodes_list = []

    for instance_ip in instances_list:
210
      if not isinstance(instance_ip, basestring):
Iustin Pop's avatar
Iustin Pop committed
211
        logging.debug("Invalid IP type for: %s", instance_ip)
212
213
214
        return QUERY_ARGUMENT_ERROR

      instance = self.reader.GetInstanceByLinkIp(instance_ip, network_link)
215
      if not instance:
Iustin Pop's avatar
Iustin Pop committed
216
        logging.debug("Unknown instance IP: %s", instance_ip)
217
218
        pnodes_list.append(QUERY_UNKNOWN_ENTRY_ERROR)
        continue
Guido Trotter's avatar
Guido Trotter committed
219

220
221
222
      pnode = self.reader.GetInstancePrimaryNode(instance)
      if not pnode:
        logging.error("Instance '%s' doesn't have an associated primary"
Iustin Pop's avatar
Iustin Pop committed
223
                      " node", instance)
224
225
        pnodes_list.append(QUERY_INTERNAL_ERROR)
        continue
Guido Trotter's avatar
Guido Trotter committed
226

227
228
229
      pnode_primary_ip = self.reader.GetNodePrimaryIp(pnode)
      if not pnode_primary_ip:
        logging.error("Primary node '%s' doesn't have an associated"
Iustin Pop's avatar
Iustin Pop committed
230
                      " primary IP", pnode)
231
232
        pnodes_list.append(QUERY_INTERNAL_ERROR)
        continue
Guido Trotter's avatar
Guido Trotter committed
233

234
235
      pnodes_list.append((constants.CONFD_REPL_STATUS_OK, pnode_primary_ip))

Iustin Pop's avatar
Iustin Pop committed
236
237
238
    # If a single ip was requested, return a single answer, otherwise
    # the whole list, with a success status (since each entry has its
    # own success/failure)
239
    if mode == constants.CONFD_REQQ_IP:
240
241
242
      return pnodes_list[0]

    return constants.CONFD_REPL_STATUS_OK, pnodes_list
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283


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

  It returns the list of nodes primary IPs.

  """
  def Exec(self, query):
    """NodesPipsQuery main execution.

    """
    if query is None:
      status = constants.CONFD_REPL_STATUS_OK
      answer = self.reader.GetNodesPrimaryIps()
    else:
      status = constants.CONFD_REPL_STATUS_ERROR
      answer = "non-empty node primary IPs query"

    return status, answer


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

  It returns the list of master candidates primary IPs.

  """
  def Exec(self, query):
    """MasterCandidatesPipsQuery main execution.

    """
    if query is None:
      status = constants.CONFD_REPL_STATUS_OK
      answer = self.reader.GetMasterCandidatesPrimaryIps()
    else:
      status = constants.CONFD_REPL_STATUS_ERROR
      answer = "non-empty master candidates primary IPs query"

    return status, answer

284
285
286
287

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

288
289
  It returns the list of IPs of NICs connected to the requested link or all the
  instances IPs if no link is submitted.
290
291
292
293
294
295

  """
  def Exec(self, query):
    """InstancesIpsQuery main execution.

    """
296
297
298
    link = query
    status = constants.CONFD_REPL_STATUS_OK
    answer = self.reader.GetInstancesIps(link)
299
300

    return status, answer