1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5#    http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10# See the License for the specific language governing permissions and
11# limitations under the License.
12
13# import types so that we can reference ListType in sphinx param declarations.
14# We can't just use list, because sphinx gets confused by
15# openstack.resource.Resource.list and openstack.resource2.Resource.list
16import types  # noqa
17
18from openstack.cloud import _normalize
19from openstack.cloud import _utils
20from openstack.cloud import exc
21from openstack import utils
22
23
24class ClusteringCloudMixin(_normalize.Normalizer):
25
26    @property
27    def _clustering_client(self):
28        if 'clustering' not in self._raw_clients:
29            clustering_client = self._get_versioned_client(
30                'clustering', min_version=1, max_version='1.latest')
31            self._raw_clients['clustering'] = clustering_client
32        return self._raw_clients['clustering']
33
34    def create_cluster(self, name, profile, config=None, desired_capacity=0,
35                       max_size=None, metadata=None, min_size=None,
36                       timeout=None):
37        profile = self.get_cluster_profile(profile)
38        profile_id = profile['id']
39        body = {
40            'desired_capacity': desired_capacity,
41            'name': name,
42            'profile_id': profile_id
43        }
44
45        if config is not None:
46            body['config'] = config
47
48        if max_size is not None:
49            body['max_size'] = max_size
50
51        if metadata is not None:
52            body['metadata'] = metadata
53
54        if min_size is not None:
55            body['min_size'] = min_size
56
57        if timeout is not None:
58            body['timeout'] = timeout
59
60        data = self._clustering_client.post(
61            '/clusters', json={'cluster': body},
62            error_message="Error creating cluster {name}".format(name=name))
63
64        return self._get_and_munchify(key=None, data=data)
65
66    def set_cluster_metadata(self, name_or_id, metadata):
67        cluster = self.get_cluster(name_or_id)
68        if not cluster:
69            raise exc.OpenStackCloudException(
70                'Invalid Cluster {cluster}'.format(cluster=name_or_id))
71
72        self._clustering_client.post(
73            '/clusters/{cluster_id}/metadata'.format(cluster_id=cluster['id']),
74            json={'metadata': metadata},
75            error_message='Error updating cluster metadata')
76
77    def get_cluster_by_id(self, cluster_id):
78        try:
79            data = self._clustering_client.get(
80                "/clusters/{cluster_id}".format(cluster_id=cluster_id),
81                error_message="Error fetching cluster {name}".format(
82                    name=cluster_id))
83            return self._get_and_munchify('cluster', data)
84        except Exception:
85            return None
86
87    def get_cluster(self, name_or_id, filters=None):
88        return _utils._get_entity(
89            cloud=self, resource='cluster',
90            name_or_id=name_or_id, filters=filters)
91
92    def update_cluster(self, name_or_id, new_name=None,
93                       profile_name_or_id=None, config=None, metadata=None,
94                       timeout=None, profile_only=False):
95        old_cluster = self.get_cluster(name_or_id)
96        if old_cluster is None:
97            raise exc.OpenStackCloudException(
98                'Invalid Cluster {cluster}'.format(cluster=name_or_id))
99        cluster = {
100            'profile_only': profile_only
101        }
102
103        if config is not None:
104            cluster['config'] = config
105
106        if metadata is not None:
107            cluster['metadata'] = metadata
108
109        if profile_name_or_id is not None:
110            profile = self.get_cluster_profile(profile_name_or_id)
111            if profile is None:
112                raise exc.OpenStackCloudException(
113                    'Invalid Cluster Profile {profile}'.format(
114                        profile=profile_name_or_id))
115            cluster['profile_id'] = profile.id
116
117        if timeout is not None:
118            cluster['timeout'] = timeout
119
120        if new_name is not None:
121            cluster['name'] = new_name
122
123        data = self._clustering_client.patch(
124            "/clusters/{cluster_id}".format(cluster_id=old_cluster['id']),
125            json={'cluster': cluster},
126            error_message="Error updating cluster "
127                          "{name}".format(name=name_or_id))
128
129        return self._get_and_munchify(key=None, data=data)
130
131    def delete_cluster(self, name_or_id):
132        cluster = self.get_cluster(name_or_id)
133        if cluster is None:
134            self.log.debug("Cluster %s not found for deleting", name_or_id)
135            return False
136
137        for policy in self.list_policies_on_cluster(name_or_id):
138            detach_policy = self.get_cluster_policy_by_id(
139                policy['policy_id'])
140            self.detach_policy_from_cluster(cluster, detach_policy)
141
142        for receiver in self.list_cluster_receivers():
143            if cluster["id"] == receiver["cluster_id"]:
144                self.delete_cluster_receiver(receiver["id"], wait=True)
145
146        self._clustering_client.delete(
147            "/clusters/{cluster_id}".format(cluster_id=name_or_id),
148            error_message="Error deleting cluster {name}".format(
149                name=name_or_id))
150
151        return True
152
153    def search_clusters(self, name_or_id=None, filters=None):
154        clusters = self.list_clusters()
155        return _utils._filter_list(clusters, name_or_id, filters)
156
157    def list_clusters(self):
158        try:
159            data = self._clustering_client.get(
160                '/clusters',
161                error_message="Error fetching clusters")
162            return self._get_and_munchify('clusters', data)
163        except exc.OpenStackCloudURINotFound as e:
164            self.log.debug(str(e), exc_info=True)
165            return []
166
167    def attach_policy_to_cluster(self, name_or_id, policy_name_or_id,
168                                 is_enabled):
169        cluster = self.get_cluster(name_or_id)
170        policy = self.get_cluster_policy(policy_name_or_id)
171        if cluster is None:
172            raise exc.OpenStackCloudException(
173                'Cluster {cluster} not found for attaching'.format(
174                    cluster=name_or_id))
175
176        if policy is None:
177            raise exc.OpenStackCloudException(
178                'Policy {policy} not found for attaching'.format(
179                    policy=policy_name_or_id))
180
181        body = {
182            'policy_id': policy['id'],
183            'enabled': is_enabled
184        }
185
186        self._clustering_client.post(
187            "/clusters/{cluster_id}/actions".format(cluster_id=cluster['id']),
188            error_message="Error attaching policy {policy} to cluster "
189                          "{cluster}".format(
190                policy=policy['id'],
191                cluster=cluster['id']),
192            json={'policy_attach': body})
193
194        return True
195
196    def detach_policy_from_cluster(
197            self, name_or_id, policy_name_or_id, wait=False, timeout=3600):
198        cluster = self.get_cluster(name_or_id)
199        policy = self.get_cluster_policy(policy_name_or_id)
200        if cluster is None:
201            raise exc.OpenStackCloudException(
202                'Cluster {cluster} not found for detaching'.format(
203                    cluster=name_or_id))
204
205        if policy is None:
206            raise exc.OpenStackCloudException(
207                'Policy {policy} not found for detaching'.format(
208                    policy=policy_name_or_id))
209
210        body = {'policy_id': policy['id']}
211        self._clustering_client.post(
212            "/clusters/{cluster_id}/actions".format(cluster_id=cluster['id']),
213            error_message="Error detaching policy {policy} from cluster "
214                          "{cluster}".format(
215                policy=policy['id'],
216                cluster=cluster['id']),
217            json={'policy_detach': body})
218
219        if not wait:
220            return True
221
222        value = []
223
224        for count in utils.iterate_timeout(
225                timeout, "Timeout waiting for cluster policy to detach"):
226
227            # TODO(bjjohnson) This logic will wait until there are no policies.
228            # Since we're detaching a specific policy, checking to make sure
229            # that policy is not in the list of policies would be better.
230            policy_status = self.get_cluster_by_id(cluster['id'])['policies']
231
232            if policy_status == value:
233                break
234        return True
235
236    def update_policy_on_cluster(self, name_or_id, policy_name_or_id,
237                                 is_enabled):
238        cluster = self.get_cluster(name_or_id)
239        policy = self.get_cluster_policy(policy_name_or_id)
240        if cluster is None:
241            raise exc.OpenStackCloudException(
242                'Cluster {cluster} not found for updating'.format(
243                    cluster=name_or_id))
244
245        if policy is None:
246            raise exc.OpenStackCloudException(
247                'Policy {policy} not found for updating'.format(
248                    policy=policy_name_or_id))
249
250        body = {
251            'policy_id': policy['id'],
252            'enabled': is_enabled
253        }
254        self._clustering_client.post(
255            "/clusters/{cluster_id}/actions".format(cluster_id=cluster['id']),
256            error_message="Error updating policy {policy} on cluster "
257                          "{cluster}".format(
258                policy=policy['id'],
259                cluster=cluster['id']),
260            json={'policy_update': body})
261
262        return True
263
264    def get_policy_on_cluster(self, name_or_id, policy_name_or_id):
265        try:
266            policy = self._clustering_client.get(
267                "/clusters/{cluster_id}/policies/{policy_id}".format(
268                    cluster_id=name_or_id, policy_id=policy_name_or_id),
269                error_message="Error fetching policy "
270                              "{name}".format(name=policy_name_or_id))
271            return self._get_and_munchify('cluster_policy', policy)
272        except Exception:
273            return False
274
275    def list_policies_on_cluster(self, name_or_id):
276        endpoint = "/clusters/{cluster_id}/policies".format(
277            cluster_id=name_or_id)
278        try:
279            data = self._clustering_client.get(
280                endpoint,
281                error_message="Error fetching cluster policies")
282        except exc.OpenStackCloudURINotFound as e:
283            self.log.debug(str(e), exc_info=True)
284            return []
285        return self._get_and_munchify('cluster_policies', data)
286
287    def create_cluster_profile(self, name, spec, metadata=None):
288        profile = {
289            'name': name,
290            'spec': spec
291        }
292
293        if metadata is not None:
294            profile['metadata'] = metadata
295
296        data = self._clustering_client.post(
297            '/profiles', json={'profile': profile},
298            error_message="Error creating profile {name}".format(name=name))
299
300        return self._get_and_munchify('profile', data)
301
302    def set_cluster_profile_metadata(self, name_or_id, metadata):
303        profile = self.get_cluster_profile(name_or_id)
304        if not profile:
305            raise exc.OpenStackCloudException(
306                'Invalid Profile {profile}'.format(profile=name_or_id))
307
308        self._clustering_client.post(
309            '/profiles/{profile_id}/metadata'.format(profile_id=profile['id']),
310            json={'metadata': metadata},
311            error_message='Error updating profile metadata')
312
313    def search_cluster_profiles(self, name_or_id=None, filters=None):
314        cluster_profiles = self.list_cluster_profiles()
315        return _utils._filter_list(cluster_profiles, name_or_id, filters)
316
317    def list_cluster_profiles(self):
318        try:
319            data = self._clustering_client.get(
320                '/profiles',
321                error_message="Error fetching profiles")
322        except exc.OpenStackCloudURINotFound as e:
323            self.log.debug(str(e), exc_info=True)
324            return []
325        return self._get_and_munchify('profiles', data)
326
327    def get_cluster_profile_by_id(self, profile_id):
328        try:
329            data = self._clustering_client.get(
330                "/profiles/{profile_id}".format(profile_id=profile_id),
331                error_message="Error fetching profile {name}".format(
332                    name=profile_id))
333            return self._get_and_munchify('profile', data)
334        except exc.OpenStackCloudURINotFound as e:
335            self.log.debug(str(e), exc_info=True)
336            return None
337
338    def get_cluster_profile(self, name_or_id, filters=None):
339        return _utils._get_entity(self, 'cluster_profile', name_or_id, filters)
340
341    def delete_cluster_profile(self, name_or_id):
342        profile = self.get_cluster_profile(name_or_id)
343        if profile is None:
344            self.log.debug("Profile %s not found for deleting", name_or_id)
345            return False
346
347        for cluster in self.list_clusters():
348            if (name_or_id, profile.id) in cluster.items():
349                self.log.debug(
350                    "Profile %s is being used by cluster %s, won't delete",
351                    name_or_id, cluster.name)
352                return False
353
354        self._clustering_client.delete(
355            "/profiles/{profile_id}".format(profile_id=profile['id']),
356            error_message="Error deleting profile "
357                          "{name}".format(name=name_or_id))
358
359        return True
360
361    def update_cluster_profile(self, name_or_id, metadata=None, new_name=None):
362        old_profile = self.get_cluster_profile(name_or_id)
363        if not old_profile:
364            raise exc.OpenStackCloudException(
365                'Invalid Profile {profile}'.format(profile=name_or_id))
366
367        profile = {}
368
369        if metadata is not None:
370            profile['metadata'] = metadata
371
372        if new_name is not None:
373            profile['name'] = new_name
374
375        data = self._clustering_client.patch(
376            "/profiles/{profile_id}".format(profile_id=old_profile.id),
377            json={'profile': profile},
378            error_message="Error updating profile {name}".format(
379                name=name_or_id))
380
381        return self._get_and_munchify(key=None, data=data)
382
383    def create_cluster_policy(self, name, spec):
384        policy = {
385            'name': name,
386            'spec': spec
387        }
388
389        data = self._clustering_client.post(
390            '/policies', json={'policy': policy},
391            error_message="Error creating policy {name}".format(
392                name=policy['name']))
393        return self._get_and_munchify('policy', data)
394
395    def search_cluster_policies(self, name_or_id=None, filters=None):
396        cluster_policies = self.list_cluster_policies()
397        return _utils._filter_list(cluster_policies, name_or_id, filters)
398
399    def list_cluster_policies(self):
400        endpoint = "/policies"
401        try:
402            data = self._clustering_client.get(
403                endpoint,
404                error_message="Error fetching cluster policies")
405        except exc.OpenStackCloudURINotFound as e:
406            self.log.debug(str(e), exc_info=True)
407            return []
408        return self._get_and_munchify('policies', data)
409
410    def get_cluster_policy_by_id(self, policy_id):
411        try:
412            data = self._clustering_client.get(
413                "/policies/{policy_id}".format(policy_id=policy_id),
414                error_message="Error fetching policy {name}".format(
415                    name=policy_id))
416            return self._get_and_munchify('policy', data)
417        except exc.OpenStackCloudURINotFound as e:
418            self.log.debug(str(e), exc_info=True)
419            return None
420
421    def get_cluster_policy(self, name_or_id, filters=None):
422        return _utils._get_entity(
423            self, 'cluster_policie', name_or_id, filters)
424
425    def delete_cluster_policy(self, name_or_id):
426        policy = self.get_cluster_policy_by_id(name_or_id)
427        if policy is None:
428            self.log.debug("Policy %s not found for deleting", name_or_id)
429            return False
430
431        for cluster in self.list_clusters():
432            if (name_or_id, policy.id) in cluster.items():
433                self.log.debug(
434                    "Policy %s is being used by cluster %s, won't delete",
435                    name_or_id, cluster.name)
436                return False
437
438        self._clustering_client.delete(
439            "/policies/{policy_id}".format(policy_id=name_or_id),
440            error_message="Error deleting policy "
441                          "{name}".format(name=name_or_id))
442
443        return True
444
445    def update_cluster_policy(self, name_or_id, new_name):
446        old_policy = self.get_cluster_policy(name_or_id)
447        if not old_policy:
448            raise exc.OpenStackCloudException(
449                'Invalid Policy {policy}'.format(policy=name_or_id))
450        policy = {'name': new_name}
451
452        data = self._clustering_client.patch(
453            "/policies/{policy_id}".format(policy_id=old_policy.id),
454            json={'policy': policy},
455            error_message="Error updating policy "
456                          "{name}".format(name=name_or_id))
457        return self._get_and_munchify(key=None, data=data)
458
459    def create_cluster_receiver(self, name, receiver_type,
460                                cluster_name_or_id=None, action=None,
461                                actor=None, params=None):
462        cluster = self.get_cluster(cluster_name_or_id)
463        if cluster is None:
464            raise exc.OpenStackCloudException(
465                'Invalid cluster {cluster}'.format(cluster=cluster_name_or_id))
466
467        receiver = {
468            'name': name,
469            'type': receiver_type
470        }
471
472        if cluster_name_or_id is not None:
473            receiver['cluster_id'] = cluster.id
474
475        if action is not None:
476            receiver['action'] = action
477
478        if actor is not None:
479            receiver['actor'] = actor
480
481        if params is not None:
482            receiver['params'] = params
483
484        data = self._clustering_client.post(
485            '/receivers', json={'receiver': receiver},
486            error_message="Error creating receiver {name}".format(name=name))
487        return self._get_and_munchify('receiver', data)
488
489    def search_cluster_receivers(self, name_or_id=None, filters=None):
490        cluster_receivers = self.list_cluster_receivers()
491        return _utils._filter_list(cluster_receivers, name_or_id, filters)
492
493    def list_cluster_receivers(self):
494        try:
495            data = self._clustering_client.get(
496                '/receivers',
497                error_message="Error fetching receivers")
498        except exc.OpenStackCloudURINotFound as e:
499            self.log.debug(str(e), exc_info=True)
500            return []
501        return self._get_and_munchify('receivers', data)
502
503    def get_cluster_receiver_by_id(self, receiver_id):
504        try:
505            data = self._clustering_client.get(
506                "/receivers/{receiver_id}".format(receiver_id=receiver_id),
507                error_message="Error fetching receiver {name}".format(
508                    name=receiver_id))
509            return self._get_and_munchify('receiver', data)
510        except exc.OpenStackCloudURINotFound as e:
511            self.log.debug(str(e), exc_info=True)
512            return None
513
514    def get_cluster_receiver(self, name_or_id, filters=None):
515        return _utils._get_entity(
516            self, 'cluster_receiver', name_or_id, filters)
517
518    def delete_cluster_receiver(self, name_or_id, wait=False, timeout=3600):
519        receiver = self.get_cluster_receiver(name_or_id)
520        if receiver is None:
521            self.log.debug("Receiver %s not found for deleting", name_or_id)
522            return False
523
524        receiver_id = receiver['id']
525
526        self._clustering_client.delete(
527            "/receivers/{receiver_id}".format(receiver_id=receiver_id),
528            error_message="Error deleting receiver {name}".format(
529                name=name_or_id))
530
531        if not wait:
532            return True
533
534        for count in utils.iterate_timeout(
535                timeout, "Timeout waiting for cluster receiver to delete"):
536
537            receiver = self.get_cluster_receiver_by_id(receiver_id)
538
539            if not receiver:
540                break
541
542        return True
543
544    def update_cluster_receiver(self, name_or_id, new_name=None, action=None,
545                                params=None):
546        old_receiver = self.get_cluster_receiver(name_or_id)
547        if old_receiver is None:
548            raise exc.OpenStackCloudException(
549                'Invalid receiver {receiver}'.format(receiver=name_or_id))
550
551        receiver = {}
552
553        if new_name is not None:
554            receiver['name'] = new_name
555
556        if action is not None:
557            receiver['action'] = action
558
559        if params is not None:
560            receiver['params'] = params
561
562        data = self._clustering_client.patch(
563            "/receivers/{receiver_id}".format(receiver_id=old_receiver.id),
564            json={'receiver': receiver},
565            error_message="Error updating receiver {name}".format(
566                name=name_or_id))
567        return self._get_and_munchify(key=None, data=data)
568