1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
|
# Copyright 2017 Red Hat, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
import os_service_types.data
from os_service_types import exc
__all__ = ['ServiceTypes']
BUILTIN_DATA = os_service_types.data.read_data('service-types.json')
SERVICE_TYPES_URL = "https://service-types.openstack.org/service-types.json"
def _normalize_type(service_type):
if service_type:
return service_type.replace('_', '-')
class ServiceTypes(object):
"""Encapsulation of the OpenStack Service Types Authority data.
The Service Types Authority data will be either pulled from its remote
location or from local files as is appropriate.
If the user passes a Session, remote data will be fetched. If the user
does not do that, local builtin data will be used.
:param session: An object that behaves like a `requests.sessions.Session`
or a `keystoneauth1.session.Session` that provides a get method
and returns an object that behaves like a `requests.models.Response`.
Optional. If session is omitted, no remote actions will be performed.
:param bool only_remote: By default if there is a problem fetching data
remotely the builtin data will be returned as a fallback. only_remote
will cause remote failures to raise an error instead of falling back.
Optional, defaults to False.
:param bool warn: Emit warnings when a non-official service_type is
provided. This provides an easy way for consuming applications to
warn users when they are using old types.
:raises ValueError: If session is None and only_remote is True
:raises IOError: If session is given and only_remote is True and there is
an error fetching remote data.
"""
def __init__(self, session=None, only_remote=False, warn=False):
if not session and only_remote:
raise ValueError(
"only_remote was requested but no Session was provided.")
self._service_types_data = BUILTIN_DATA
self._warn = warn
if session:
try:
response = session.get(SERVICE_TYPES_URL)
response.raise_for_status()
self._service_types_data = response.json()
except IOError:
# If we can't fetch, fall backto BUILTIN
if only_remote:
raise
def _canonical_project_name(self, name):
"Convert repo name to project name."
if name is None:
raise ValueError("Empty project name is not allowed")
# Handle openstack/ prefix going away from STA data
return name.rpartition('/')[-1]
@property
def url(self):
"The URL from which the data was retrieved."
return SERVICE_TYPES_URL
@property
def version(self):
"The version of the data."
return self._service_types_data['version']
@property
def forward(self):
"Mapping service-type names to their aliases."
return copy.deepcopy(self._service_types_data['forward'])
@property
def reverse(self):
"Mapping aliases to their service-type names."
return copy.deepcopy(self._service_types_data['reverse'])
@property
def services(self):
"Full service-type data listing."
return copy.deepcopy(self._service_types_data['services'])
@property
def all_types_by_service_type(self):
"Mapping of official service type to official type and aliases."
return copy.deepcopy(
self._service_types_data['all_types_by_service_type'])
@property
def primary_service_by_project(self):
"Mapping of project name to the primary associated service."
return copy.deepcopy(
self._service_types_data['primary_service_by_project'])
@property
def service_types_by_project(self):
"Mapping of project name to a list of all associated service-types."
return copy.deepcopy(
self._service_types_data['service_types_by_project'])
def get_official_service_data(self, service_type):
"""Get the service data for an official service_type.
:param str service_type: The official service-type to get data for.
:returns dict: Service data for the service or None if not found.
"""
service_type = _normalize_type(service_type)
for service in self._service_types_data['services']:
if service_type == service['service_type']:
return service
return None
def get_service_data(self, service_type):
"""Get the service data for a given service_type.
:param str service_type: The service-type or alias to get data for.
:returns dict: Service data for the service or None if not found.
"""
service_type = self.get_service_type(service_type)
if not service_type:
return None
return self.get_official_service_data(service_type)
def is_official(self, service_type):
"""Is the given service-type an official service-type?
:param str service_type: The service-type to test.
:returns bool: True if it's an official type, False otherwise.
"""
return self.get_official_service_data(service_type) is not None
def is_alias(self, service_type):
"""Is the given service-type an alias?
:param str service_type: The service-type to test.
:returns bool: True if it's an alias type, False otherwise.
"""
service_type = _normalize_type(service_type)
return service_type in self._service_types_data['reverse']
def is_known(self, service_type):
"""Is the given service-type an official type or an alias?
:param str service_type: The service-type to test.
:returns bool: True if it's a known type, False otherwise.
"""
return self.is_official(service_type) or self.is_alias(service_type)
def is_match(self, requested, found):
"""Does a requested service-type match one found in the catalog?
A requested service-type matches a service-type in the catalog if
it is either a direct match, if the service-type in the catalog is
an official type and the requested type is one of its aliases, or
if the requested type is an official type and the type in the catalog
is one of its aliases.
A requested alias cannot match a different alias because there are
historical implications related to versioning to some historical
aliases that cannot be safely reasoned about in an automatic fashion.
:param str requested: A service-type that someone is looking for.
:param str found: A service-type found in a catalog
:returns bool: True if the service-type being requested matches the
entry in the catalog. False if it does not.
"""
# Exact match
if requested == found:
return True
# Found is official type, requested is one of its aliases
if requested in self.get_aliases(found):
return True
# Found is an alias, requested is an official type
if requested == self.get_service_type(found):
return True
return False
def get_aliases(self, service_type):
"""Returns the list of aliases for a given official service-type.
:param str service_type: An official service-type.
:returns list: List of aliases, or empty list if there are none.
"""
service_type = _normalize_type(service_type)
return self._service_types_data['forward'].get(service_type, [])
def get_service_type(self, service_type, permissive=False):
"""Given a possible service_type, return the official type.
:param str service_type: A potential service-type.
:param bool permissive:
Return the original type if the given service_type is not found.
:returns str: The official service-type, or None if there is no match.
"""
service_type = _normalize_type(service_type)
if self.is_official(service_type):
return service_type
official = self._service_types_data['reverse'].get(service_type)
if permissive and official is None:
if self._warn:
exc.warn(
exc.UnofficialUsageWarning,
given=service_type)
return service_type
if self._warn:
exc.warn(
exc.AliasUsageWarning, given=service_type, official=official)
return official
def get_all_types(self, service_type):
"""Get a list of official types and all known aliases.
:param str service_type: The service-type or alias to get data for.
:returns dict: Service data for the service or None if not found.
"""
service_type = _normalize_type(service_type)
if not self.is_known(service_type):
return [service_type]
return self.all_types_by_service_type[
self.get_service_type(service_type)]
def get_project_name(self, service_type):
"""Return the OpenStack project name for a given service_type.
:param str service_type: An official service-type or alias.
:returns str: The OpenStack project name or None if there is no match.
"""
service_type = _normalize_type(service_type)
service = self.get_service_data(service_type)
if service:
return service['project']
return None
def get_service_data_for_project(self, project_name):
"""Return the service information associated with a project.
:param name: A repository or project name in the form
``'openstack/{project}'`` or just ``'{project}'``.
:type name: str
:raises ValueError: If project_name is None
:returns: dict or None if not found
"""
project_name = self._canonical_project_name(project_name)
return self.primary_service_by_project.get(project_name)
def get_all_service_data_for_project(self, project_name):
"""Return the information for every service associated with a project.
:param name: A repository or project name in the form
``'openstack/{project}'`` or just ``'{project}'``.
:type name: str
:raises ValueError: If project_name is None
:returns: list of dicts
"""
data = []
for service_type in self.service_types_by_project.get(
project_name, []):
data.append(self.get_service_data(service_type))
return data
|