1 ##############################################################################
3 # Copyright (c) 2010 Vifib SARL and Contributors. All Rights Reserved.
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
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.
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.
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.
26 ##############################################################################
28 from slapos
.recipe
.librecipe
import wrap
, JSON_SERIALISED_MAGIC_KEY
30 from slapos
import slap
as slapmodule
31 import slapos
.recipe
.librecipe
.generic
as librecipe
34 DEFAULT_SOFTWARE_TYPE
= 'RootSoftwareInstance'
36 def getListOption(option_dict
, key
, default
=()):
37 result
= option_dict
.get(key
, default
)
38 if isinstance(result
, basestring
):
39 result
= result
.split()
44 Request a partition to a slap master.
45 Can provide parameters to that partition and fetches its connection
52 Used to contact slap master.
56 Current partition's identifiers.
57 Must match key's credentials if given.
59 name (optional, defaults to section name)
60 Name (reference) of requested partition.
63 URL of a software definition to request an instance of.
66 Software type of requested instance, among those provided by the
67 definition from software-url.
69 slave (optional, defaults to false)
70 Set to "true" when requesting a slave instance, ie just setting a set of
71 parameters in an existing instance.
74 Whitespace-separated list of Service Level Agreement names.
75 Each name must correspond to a "sla-<name>" option.
76 Used to specify what a suitable partition would be.
77 Possible names depend on master's capabilities.
80 Whitespace-separated list of partition parameter names.
81 Each name must correspond to a "config-<name>" option.
82 Possible names depend on requested partition's software type.
85 Whitespace-separated list of expected partition-published value names.
86 Options will be created from them, in the form of "connection-<name>"
87 As long as requested partition doesn't publish all those values,
88 installation of request section will fail.
89 Possible names depend on requested partition's software type.
92 Requested state, default value is the state of the requester.
95 See "return" input key.
97 The current state of the instance.
99 The requested state of the instance.
103 def __init__(self
, buildout
, name
, options
):
104 self
.logger
= logging
.getLogger(name
)
105 software_url
= options
['software-url']
106 name
= options
['name']
107 return_parameters
= getListOption(options
, 'return')
108 if not return_parameters
:
109 self
.logger
.debug("No parameter to return to main instance."
110 "Be careful about that...")
111 software_type
= options
.get('software-type', DEFAULT_SOFTWARE_TYPE
)
113 (x
, options
['sla-' + x
]) for x
in getListOption(options
, 'sla')
114 if options
['sla-' + x
]
116 partition_parameter_kw
= self
._filterForStorage(dict(
117 (x
, options
['config-' + x
])
118 for x
in getListOption(options
, 'config')
120 slave
= options
.get('slave', 'false').lower() in \
121 librecipe
.GenericBaseRecipe
.TRUE_VALUES
122 # By default XXXX Way of doing it is ugly and dangerous
123 requested_state
= options
.get('state', buildout
['slap-connection'].get('requested','started'))
124 slap
= slapmodule
.slap()
125 slap
.initializeConnection(
126 options
['server-url'],
127 options
.get('key-file'),
128 options
.get('cert-file'),
130 request
= slap
.registerComputerPartition(
131 options
['computer-id'],
132 options
['partition-id'],
134 self
._raise_request_exception
= None
135 self
._raise_request_exception_formatted
= None
137 # Try to do the request and fetch parameter dict...
139 self
.instance
= request(software_url
, software_type
,
140 name
, partition_parameter_kw
=partition_parameter_kw
,
141 filter_kw
=filter_kw
, shared
=slave
, state
=requested_state
)
142 return_parameter_dict
= self
._getReturnParameterDict(self
.instance
,
146 options
['instance-guid'] = self
.instance
.getInstanceGuid()
147 # XXX: deprecated, to be removed
148 options
['instance_guid'] = self
.instance
.getInstanceGuid()
149 except (slapmodule
.ResourceNotReady
, AttributeError):
150 # Backward compatibility. Old SlapOS master and core don't know this.
151 self
.logger
.warning("Impossible to fetch instance GUID.")
152 except (slapmodule
.NotFoundError
, slapmodule
.ServerError
, slapmodule
.ResourceNotReady
) as exc
:
153 self
._raise_request_exception
= exc
154 self
._raise_request_exception_formatted
= traceback
.format_exc()
155 return_parameter_dict
= {}
157 # Then try to get all the parameters. In case of problem, put empty string.
158 for param
in return_parameters
:
159 options
['connection-%s' % param
] = ''
161 options
['connection-%s' % param
] = return_parameter_dict
[param
]
163 if self
.failed
is None:
165 options
['requested-state'] = requested_state
167 options
['instance-state'] = self
.instance
.getState()
168 except slapmodule
.ResourceNotReady
:
169 # Odd case: SlapOS Master doesn't send the state of a slave partition.
170 # XXX Should be fixed in the SlapOS Master, we should not care here.
173 def _filterForStorage(self
, partition_parameter_kw
):
174 return partition_parameter_kw
176 def _getReturnParameterDict(self
, instance
, return_parameter_list
):
178 for param
in return_parameter_list
:
180 result
[param
] = str(instance
.getConnectionParameter(param
))
181 except slapmodule
.NotFoundError
:
186 if self
._raise_request_exception
:
187 raise self
._raise_request_exception
189 if self
.failed
is not None:
190 # Check instance status to know if instance has been deployed
192 if self
.instance
._computer_id
is not None:
193 status
= self
.instance
.getState()
195 status
= 'not ready yet'
196 except (slapmodule
.NotFoundError
, slapmodule
.ServerError
, slapmodule
.ResourceNotReady
):
197 status
= 'not ready yet'
198 except AttributeError:
200 error_message
= 'Connection parameter %s not found. '\
201 'Status of requested instance is: %s. If this error persists, '\
202 'check status of this instance.' %
(self
.failed
, status
)
203 self
.logger
.error(error_message
)
204 raise KeyError(error_message
)
210 class RequestOptional(Recipe
):
212 Request a SlapOS instance. Won't fail if request failed or is not ready.
213 Same as slapos.cookbook:request, but won't raise in case of problem.
216 if self
._raise_request_exception_formatted
:
217 self
.logger
.warning('Optional request failed.')
218 if not isinstance(self
._raise_request_exception
, slapmodule
.NotFoundError
):
219 # full traceback for optional 'not found' is too verbose and confusing
220 self
.logger
.debug(self
._raise_request_exception_formatted
)
221 elif self
.failed
is not None:
222 # Check instance status to know if instance has been deployed
224 if self
.instance
._computer_id
is not None:
225 status
= self
.instance
.getState()
227 status
= 'not ready yet'
228 except (slapmodule
.NotFoundError
, slapmodule
.ServerError
, slapmodule
.ResourceNotReady
):
229 status
= 'not ready yet'
230 except AttributeError:
232 error_message
= 'Connection parameter %s not found. '\
233 'Requested instance is currently %s. If this error persists, '\
234 'check status of this instance.' %
(self
.failed
, status
)
235 self
.logger
.warning(error_message
)
240 class Serialised(Recipe
):
241 def _filterForStorage(self
, partition_parameter_kw
):
242 return wrap(partition_parameter_kw
)
244 def _getReturnParameterDict(self
, instance
, return_parameter_list
):
246 return json
.loads(instance
.getConnectionParameter(JSON_SERIALISED_MAGIC_KEY
))
247 except slapmodule
.NotFoundError
:
253 CONNECTION_PARAMETER_STRING
= 'connection-'
255 class RequestEdge(Recipe
):
257 For each country in country-list, do a request.
259 def __init__(self
, buildout
, name
, options
):
260 self
.logger
= logging
.getLogger(name
)
261 self
.options
= options
262 self
.request_dict
= {}
263 # Keep a copy of original options dict
264 original_options
= options
.copy()
265 for country
in options
['country-list'].split(','):
266 # Request will have its own copy of options dict
267 local_options
= original_options
.copy()
268 local_options
['name'] = '%s-%s' %
(country
, name
)
269 local_options
['sla'] = "region"
270 local_options
['sla-region'] = country
272 self
.request_dict
[country
] = Recipe(buildout
, name
, local_options
)
273 # "Bubble" all connection parameters
274 for option
, value
in local_options
.iteritems():
275 if option
.startswith(CONNECTION_PARAMETER_STRING
):
276 self
.options
['%s-%s' %
(option
, country
)] = value
279 for country
, request
in self
.request_dict
.iteritems():