Follow conventions.
[slapos.git] / slapos / recipe / request.py
1 ##############################################################################
2 #
3 # Copyright (c) 2010 Vifib SARL and Contributors. All Rights Reserved.
4 #
5 # WARNING: This program as such is intended to be used by professional
6 # programmers who take the whole responsibility of assessing all potential
7 # consequences resulting from its eventual inadequacies and bugs
8 # End users who are looking for a ready-to-use solution with commercial
9 # guarantees and support are strongly adviced to contract a Free Software
10 # Service Company
11 #
12 # This program is Free Software; you can redistribute it and/or
13 # modify it under the terms of the GNU General Public License
14 # as published by the Free Software Foundation; either version 3
15 # of the License, or (at your option) any later version.
16 #
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 #
26 ##############################################################################
27 import logging
28 from slapos.recipe.librecipe import wrap, JSON_SERIALISED_MAGIC_KEY
29 import json
30 from slapos import slap as slapmodule
31 import slapos.recipe.librecipe.generic as librecipe
32 import traceback
33
34 DEFAULT_SOFTWARE_TYPE = 'RootSoftwareInstance'
35
36 class Recipe(object):
37 """
38 Request a partition to a slap master.
39 Can provide parameters to that partition and fetches its connection
40 parameters.
41
42 Input:
43 server-url
44 key-file (optional)
45 cert-file (optional)
46 Used to contact slap master.
47
48 computer-id
49 partition-id
50 Current partition's identifiers.
51 Must match key's credentials if given.
52
53 name (optional, defaults to section name)
54 Name (reference) of requested partition.
55
56 software-url
57 URL of a software definition to request an instance of.
58
59 software-type
60 Software type of requested instance, among those provided by the
61 definition from software-url.
62
63 slave (optional, defaults to false)
64 Set to "true" when requesting a slave instance, ie just setting a set of
65 parameters in an existing instance.
66
67 sla (optional)
68 Whitespace-separated list of Service Level Agreement names.
69 Each name must correspond to a "sla-<name>" option.
70 Used to specify what a suitable partition would be.
71 Possible names depend on master's capabilities.
72
73 config (optional)
74 Whitespace-separated list of partition parameter names.
75 Each name must correspond to a "config-<name>" option.
76 Possible names depend on requested partition's software type.
77
78 return (optional)
79 Whitespace-separated list of expected partition-published value names.
80 Options will be created from them, in the form of "connection-<name>"
81 As long as requested partition doesn't publish all those values,
82 installation of request section will fail.
83 Possible names depend on requested partition's software type.
84
85 Output:
86 See "return" input key.
87 """
88 failed = None
89
90 def __init__(self, buildout, name, options):
91 self.logger = logging.getLogger(name)
92 software_url = options['software-url']
93 name = options['name']
94 return_parameters = options.get('return', '').split()
95 if not return_parameters:
96 self.logger.debug("No parameter to return to main instance."
97 "Be careful about that...")
98 software_type = options.get('software-type', DEFAULT_SOFTWARE_TYPE)
99 filter_kw = dict(
100 (x, options['sla-' + x]) for x in options.get('sla', '').split()
101 if options['sla-' + x]
102 )
103 partition_parameter_kw = self._filterForStorage(dict(
104 (x, options['config-' + x])
105 for x in options.get('config', '').split()
106 ))
107 slave = options.get('slave', 'false').lower() in \
108 librecipe.GenericBaseRecipe.TRUE_VALUES
109 slap = slapmodule.slap()
110 slap.initializeConnection(
111 options['server-url'],
112 options.get('key-file'),
113 options.get('cert-file'),
114 )
115 request = slap.registerComputerPartition(
116 options['computer-id'],
117 options['partition-id'],
118 ).request
119 self._raise_request_exception = None
120 self._raise_request_exception_formatted = None
121 self.instance = None
122 # Try to do the request and fetch parameter dict...
123 try:
124 self.instance = request(software_url, software_type,
125 name, partition_parameter_kw=partition_parameter_kw,
126 filter_kw=filter_kw, shared=slave)
127 return_parameter_dict = self._getReturnParameterDict(self.instance,
128 return_parameters)
129 if not slave:
130 options['instance-guid'] = self.instance.getInstanceGuid()
131 # XXX: deprecated, to be removed
132 options['instance_guid'] = self.instance.getInstanceGuid()
133 except (slapmodule.NotFoundError, slapmodule.ServerError, slapmodule.ResourceNotReady) as exc:
134 self._raise_request_exception = exc
135 self._raise_request_exception_formatted = traceback.format_exc()
136 return_parameter_dict = {}
137
138 # Then try to get all the parameters. In case of problem, put empty string.
139 for param in return_parameters:
140 options['connection-%s' % param] = ''
141 try:
142 options['connection-%s' % param] = return_parameter_dict[param]
143 except KeyError:
144 if self.failed is None:
145 self.failed = param
146
147 def _filterForStorage(self, partition_parameter_kw):
148 return partition_parameter_kw
149
150 def _getReturnParameterDict(self, instance, return_parameter_list):
151 result = {}
152 for param in return_parameter_list:
153 try:
154 result[param] = str(instance.getConnectionParameter(param))
155 except slapmodule.NotFoundError:
156 pass
157 return result
158
159 def install(self):
160 if self._raise_request_exception:
161 raise self._raise_request_exception
162
163 if self.failed is not None:
164 # Check instance status to know if instance has been deployed
165 try:
166 if self.instance._computer_id is not None:
167 status = self.instance.getState()
168 else:
169 status = 'not ready yet'
170 except (slapmodule.NotFoundError, slapmodule.ServerError, slapmodule.ResourceNotReady):
171 status = 'not ready yet'
172 except AttributeError:
173 status = 'unknown'
174 error_message = 'Connection parameter %s not found. '\
175 'Status of requested instance is: %s. If this error persists, '\
176 'check status of this instance.' % (self.failed, status)
177 self.logger.error(error_message)
178 raise KeyError(error_message)
179 return []
180
181 update = install
182
183
184 class RequestOptional(Recipe):
185 """
186 Request a SlapOS instance. Won't fail if request failed or is not ready.
187 Same as slapos.cookbook:request, but won't raise in case of problem.
188 """
189 def install(self):
190 if self._raise_request_exception_formatted:
191 self.logger.warning('Optional request failed.')
192 if not isinstance(self._raise_request_exception, slapmodule.NotFoundError):
193 # full traceback for optional 'not found' is too verbose and confusing
194 self.logger.warning(self._raise_request_exception_formatted)
195 elif self.failed is not None:
196 # Check instance status to know if instance has been deployed
197 try:
198 if self.instance._computer_id is not None:
199 status = self.instance.getState()
200 else:
201 status = 'not ready yet'
202 except (slapmodule.NotFoundError, slapmodule.ServerError):
203 status = 'not ready yet'
204 except AttributeError:
205 status = 'unknown'
206 error_message = 'Connection parameter %s not found. '\
207 'Requested instance is currently %s. If this error persists, '\
208 'check status of this instance.' % (self.failed, status)
209 self.logger.warning(error_message)
210 return []
211
212 update = install
213
214
215 class Serialised(Recipe):
216 def _filterForStorage(self, partition_parameter_kw):
217 return wrap(partition_parameter_kw)
218
219 def _getReturnParameterDict(self, instance, return_parameter_list):
220 try:
221 return json.loads(instance.getConnectionParameter(JSON_SERIALISED_MAGIC_KEY))
222 except slapmodule.NotFoundError:
223 return {}