1# Copyright (c) 2011 OpenStack Foundation.
2# All Rights Reserved.
3#
4#    Licensed under the Apache License, Version 2.0 (the "License"); you may
5#    not use this file except in compliance with the License. You may obtain
6#    a copy of the License at
7#
8#         http://www.apache.org/licenses/LICENSE-2.0
9#
10#    Unless required by applicable law or agreed to in writing, software
11#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13#    License for the specific language governing permissions and limitations
14#    under the License.
15
16from oslo_log import log as logging
17
18from cinder.scheduler import filters
19from cinder.scheduler.filters import extra_specs_ops
20
21LOG = logging.getLogger(__name__)
22
23
24class CapabilitiesFilter(filters.BaseBackendFilter):
25    """BackendFilter to work with resource (instance & volume) type records."""
26
27    def _satisfies_extra_specs(self, capabilities, resource_type):
28        """Check if capabilities satisfy resource type requirements.
29
30        Check that the capabilities provided by the services satisfy
31        the extra specs associated with the resource type.
32        """
33
34        if not resource_type:
35            return True
36
37        extra_specs = resource_type.get('extra_specs', [])
38        if not extra_specs:
39            return True
40
41        for key, req in extra_specs.items():
42
43            # Either not scoped format, or in capabilities scope
44            scope = key.split(':')
45
46            # Ignore scoped (such as vendor-specific) capabilities
47            if len(scope) > 1 and scope[0] != "capabilities":
48                continue
49            # Strip off prefix if spec started with 'capabilities:'
50            elif scope[0] == "capabilities":
51                del scope[0]
52
53            cap = capabilities
54            for index in range(len(scope)):
55                try:
56                    cap = cap[scope[index]]
57                except (TypeError, KeyError):
58                    LOG.debug("Backend doesn't provide capability '%(cap)s' ",
59                              {'cap': scope[index]})
60                    return False
61
62            # Make all capability values a list so we can handle lists
63            cap_list = [cap] if not isinstance(cap, list) else cap
64
65            # Loop through capability values looking for any match
66            for cap_value in cap_list:
67                if extra_specs_ops.match(cap_value, req):
68                    break
69            else:
70                # Nothing matched, so bail out
71                LOG.debug('Volume type extra spec requirement '
72                          '"%(key)s=%(req)s" does not match reported '
73                          'capability "%(cap)s"',
74                          {'key': key, 'req': req, 'cap': cap})
75                return False
76        return True
77
78    def backend_passes(self, backend_state, filter_properties):
79        """Return a list of backends that can create resource_type."""
80        # Note(zhiteng) Currently only Cinder and Nova are using
81        # this filter, so the resource type is either instance or
82        # volume.
83        resource_type = filter_properties.get('resource_type')
84        if not self._satisfies_extra_specs(backend_state.capabilities,
85                                           resource_type):
86            LOG.debug("%(backend_state)s fails resource_type extra_specs "
87                      "requirements", {'backend_state': backend_state})
88            return False
89        return True
90