Merge branch 'resiliency_annotated' into lapp
[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
29 from slapos import slap as slapmodule
30 import slapos.recipe.librecipe.generic as librecipe
31
32 DEFAULT_SOFTWARE_TYPE = 'RootSoftwareInstance'
33
34 class Recipe(object):
35 """
36 Request a partition to a slap master.
37 Can provide parameters to that partition and fetches its connection
38 parameters.
39
40 Input:
41 server-url
42 key-file (optional)
43 cert-file (optional)
44 Used to contact slap master.
45
46 computer-id
47 partition-id
48 Current partition's identifiers.
49 Must match key's credentials if given.
50
51 name (optional, defaults to section name)
52 Name (reference) of requested partition.
53
54 software-url
55 URL of a software definition to request an instance of.
56
57 software-type
58 Software type of requested instance, among those provided by the
59 definition from software-url.
60
61 slave (optional, defaults to false)
62 Set to "true" when requesting a slave instance, ie just setting a set of
63 parameters in an existing instance.
64
65 sla (optional)
66 Whitespace-separated list of Service Level Agreement names.
67 Each name must correspond to a "sla-<name>" option.
68 Used to specify what a suitable partition would be.
69 Possible names depend on master's capabilities.
70
71 config (optional)
72 Whitespace-separated list of partition parameter names.
73 Each name must correspond to a "config-<name>" option.
74 Possible names depend on requested partition's software type.
75
76 return (optional)
77 Whitespace-separated list of expected partition-published value names.
78 Options will be created from them, in the form of "connection-<name>"
79 As long as requested partition doesn't publish all those values,
80 installation of request section will fail.
81 Possible names depend on requested partition's software type.
82
83 Output:
84 See "return" input key.
85 """
86 failed = None
87
88 def __init__(self, buildout, name, options):
89 self.logger = logging.getLogger(name)
90
91 slap = slapmodule.slap()
92
93 software_url = options['software-url']
94 name = options['name']
95
96 slap.initializeConnection(options['server-url'],
97 options.get('key-file'),
98 options.get('cert-file'),
99 )
100 request = slap.registerComputerPartition(
101 options['computer-id'], options['partition-id']).request
102
103 return_parameters = []
104 if 'return' in options:
105 return_parameters = [str(parameter).strip()
106 for parameter in options['return'].split()]
107 else:
108 self.logger.debug("No parameter to return to main instance."
109 "Be careful about that...")
110
111 software_type = options.get('software-type', DEFAULT_SOFTWARE_TYPE)
112
113 filter_kw = {}
114 if 'sla' in options:
115 for sla_parameter in options['sla'].split():
116 filter_kw[sla_parameter] = options['sla-%s' % sla_parameter]
117
118 partition_parameter_kw = {}
119 if 'config' in options:
120 for config_parameter in options['config'].split():
121 partition_parameter_kw[config_parameter] = \
122 options['config-%s' % config_parameter]
123
124 isSlave = options.get('slave', '').lower() in \
125 librecipe.GenericBaseRecipe.TRUE_VALUES
126 self.instance = request(software_url, software_type,
127 name, partition_parameter_kw=partition_parameter_kw,
128 filter_kw=filter_kw, shared=isSlave)
129
130 self._raise_resource_not_ready = None
131 try:
132 # XXX what is the right way to get a global id?
133 options['instance_guid'] = self.instance.getId()
134 except slapmodule.ResourceNotReady as exc:
135 self._raise_resource_not_ready = exc
136
137 for param in return_parameters:
138 try:
139 options['connection-%s' % param] = str(
140 self.instance.getConnectionParameter(param))
141 except (slapmodule.NotFoundError, slapmodule.ServerError, slapmodule.ResourceNotReady):
142 options['connection-%s' % param] = ''
143 if self.failed is None:
144 self.failed = param
145
146 def install(self):
147 if self._raise_resource_not_ready:
148 raise slapmodule.ResourceNotReady(self._resource_not_ready)
149
150 if self.failed is not None:
151 # Check instance status to know if instance has been deployed
152 try:
153 if self.instance.getComputerId() is not None:
154 status = self.instance.getState()
155 else:
156 status = 'not ready yet'
157 except (slapmodule.NotFoundError, slapmodule.ServerError, slapmodule.ResourceNotReady):
158 status = 'not ready yet'
159 except AttributeError:
160 status = 'unknown'
161 error_message = 'Connection parameter %s not found. '\
162 'Status of requested instance is: %s. If this error persists, '\
163 'check status of this instance.' % (self.failed, status)
164 self.logger.error(error_message)
165 raise KeyError(error_message)
166 return []
167
168 update = install
169
170 class RequestOptional(Recipe):
171 """
172 Request a SlapOS instance. Won't fail if instance is not ready.
173 Same as slapos.cookbook:request, but won't raise in case of problem.
174 """
175 def install(self):
176 if self.failed is not None:
177 # Check instance status to know if instance has been deployed
178 try:
179 if self.instance.getComputerId() is not None:
180 status = self.instance.getState()
181 else:
182 status = 'not ready yet'
183 except (slapmodule.NotFoundError, slapmodule.ServerError):
184 status = 'not ready yet'
185 except AttributeError:
186 status = 'unknown'
187 error_message = 'Connection parameter %s not found. '\
188 'Requested instance is currently %s. If this error persists, '\
189 'check status of this instance.' % (self.failed, status)
190 self.logger.warning(error_message)
191 return []
192
193 update = install