-
Notifications
You must be signed in to change notification settings - Fork 6
Usage enforcement upstream #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: upstream
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # Copyright (c) 2020 University of Chicago. | ||
| # | ||
| # 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. | ||
|
|
||
| from blazar.enforcement.enforcement import UsageEnforcement | ||
|
|
||
| __all__ = ['UsageEnforcement'] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| # Copyright (c) 2020 University of Chicago. | ||
| # | ||
| # 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. | ||
|
|
||
| from blazar.enforcement import filters | ||
| from blazar.utils.openstack import base | ||
|
|
||
| from oslo_config import cfg | ||
| from oslo_log import log as logging | ||
|
|
||
| CONF = cfg.CONF | ||
|
|
||
| enforcement_opts = [ | ||
| cfg.ListOpt('enabled_filters', | ||
| default=[], | ||
| help='List of enabled usage enforcement filters.'), | ||
| ] | ||
|
|
||
| CONF.register_opts(enforcement_opts, group='enforcement') | ||
| LOG = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class UsageEnforcement: | ||
|
|
||
| def __init__(self): | ||
| self.load_filters() | ||
|
|
||
| def load_filters(self): | ||
| self.enabled_filters = set() | ||
| for filter_name in CONF.enforcement.enabled_filters: | ||
| _filter = getattr(filters, filter_name) | ||
|
|
||
| if filter_name in filters.all_filters: | ||
| self.enabled_filters.add(_filter(conf=CONF)) | ||
| else: | ||
| LOG.error("{} not in filters module.".format(filter_name)) | ||
|
|
||
| self.enabled_filters = list(self.enabled_filters) | ||
|
|
||
| def format_context(self, context, lease_values): | ||
| ctx = context.to_dict() | ||
| region_name = CONF.os_region_name | ||
| auth_url = base.url_for( | ||
| ctx['service_catalog'], CONF.identity_service, | ||
| os_region_name=region_name) | ||
|
|
||
| return dict(user_id=lease_values['user_id'], | ||
| project_id=lease_values['project_id'], | ||
| auth_url=auth_url, region_name=region_name) | ||
|
|
||
| def format_lease(self, lease_values, reservations, allocations): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same, could be private |
||
| lease = lease_values.copy() | ||
| lease['reservations'] = [] | ||
|
|
||
| for reservation in reservations: | ||
| res = reservation.copy() | ||
| resource_type = res['resource_type'] | ||
| res['allocations'] = allocations[resource_type] | ||
| lease['reservations'].append(res) | ||
|
|
||
| return lease | ||
|
|
||
| def check_create(self, context, lease_values, reservations, allocations): | ||
| context = self.format_context(context, lease_values) | ||
| lease = self.format_lease(lease_values, reservations, allocations) | ||
|
|
||
| for filter_ in self.enabled_filters: | ||
| filter_.check_create(context, lease) | ||
|
|
||
| def check_update(self, context, current_lease, new_lease, | ||
| current_allocations, new_allocations, | ||
| current_reservations, new_reservations): | ||
| context = self.format_context(context, current_lease) | ||
| current_lease = self.format_lease(current_lease, current_reservations, | ||
| current_allocations) | ||
| new_lease = self.format_lease(new_lease, new_reservations, | ||
| new_allocations) | ||
|
|
||
| for filter_ in self.enabled_filters: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Supreme nitpick: |
||
| filter_.check_update(context, current_lease, new_lease) | ||
|
|
||
| def on_end(self, context, lease, allocations): | ||
| context = self.format_context(context, lease) | ||
| lease_values = self.format_lease(lease, lease['reservations'], | ||
| allocations) | ||
|
|
||
| for filter_ in self.enabled_filters: | ||
| filter_.on_end(context, lease_values) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| # Copyright (c) 2013 Bull. | ||
| # | ||
| # 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. | ||
|
|
||
| from blazar.enforcement.filters.external_service_filter import ( | ||
| ExternalServiceFilter) | ||
| from blazar.enforcement.filters.max_reservation_length_filter import ( | ||
| MaxReservationLengthFilter) | ||
|
|
||
| __all__ = ['MaxReservationLengthFilter', 'ExternalServiceFilter'] | ||
|
|
||
| all_filters = __all__ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # Copyright (c) 2020 University of Chicago. | ||
| # | ||
| # 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 abc | ||
| import six | ||
|
|
||
|
|
||
| @six.add_metaclass(abc.ABCMeta) | ||
| class BaseFilter: | ||
|
|
||
| enforcement_opts = [] | ||
|
|
||
| def __init__(self, conf=None): | ||
| self.conf = conf | ||
|
|
||
| for opt in self.enforcement_opts: | ||
| self.conf.register_opt(opt, 'enforcement') | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the spec we outlined that some filters may define their own group. E.g., the external filter has its options all under a new |
||
|
|
||
| def __getattr__(self, name): | ||
| func = getattr(self.conf.enforcement, name) | ||
| return func | ||
|
|
||
| @abc.abstractmethod | ||
| def check_create(self, context, lease_values): | ||
| pass | ||
|
|
||
| @abc.abstractmethod | ||
| def check_update(self, context, current_lease_values, new_lease_values): | ||
| pass | ||
|
|
||
| @abc.abstractmethod | ||
| def on_end(self, context, lease_values): | ||
| pass | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| # Copyright (c) 2020 University of Chicago. | ||
| # | ||
| # 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 datetime | ||
| import json | ||
| import requests | ||
|
|
||
| from blazar import context | ||
| from blazar.enforcement.filters import base_filter | ||
| from blazar import exceptions | ||
| from blazar.i18n import _ | ||
| from blazar.utils.openstack import base | ||
| from blazar.utils.openstack.keystone import BlazarKeystoneClient | ||
|
|
||
| from oslo_config import cfg | ||
| from oslo_log import log as logging | ||
|
|
||
| LOG = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class DateTimeEncoder(json.JSONEncoder): | ||
| def default(self, o): | ||
| if isinstance(o, datetime.datetime): | ||
| return str(o) | ||
|
|
||
| return json.JSONEncoder.default(self, o) | ||
|
|
||
|
|
||
| class ExternalServiceUnsupportedHTTPResponse(exceptions.BlazarException): | ||
| code = 400 | ||
| msg_fmt = _('External Service Filter returned a %(status)s http response. ' | ||
| 'Only 204 and 403 responses are supported.') | ||
|
|
||
|
|
||
| class ExternalServiceFilterException(exceptions.BlazarException): | ||
| code = 400 | ||
| msg_fmt = _('%(message)s') | ||
|
|
||
|
|
||
| class ExternalServiceFilter(base_filter.BaseFilter): | ||
|
|
||
| enforcement_opts = [ | ||
| cfg.StrOpt( | ||
| 'external_service_endpoint', | ||
| default="", | ||
| help='The url of the external service API. A value of -1 will ' | ||
| 'disabled the service.'), | ||
| cfg.StrOpt( | ||
| 'external_service_token', | ||
| default=False, | ||
| help='Authentication token for token based authentication.') | ||
| ] | ||
|
|
||
| def __init__(self, conf=None): | ||
| super(ExternalServiceFilter, self).__init__(conf=conf) | ||
|
|
||
| def get_headers(self): | ||
| headers = {'Content-Type': 'application/json'} | ||
|
|
||
| if self.external_service_token: | ||
| headers['X-Auth-Token'] = self.external_service_token | ||
| else: | ||
| auth_url = "%s://%s:%s/%s" % (self.conf.os_auth_protocol, | ||
| base.get_os_auth_host(self.conf), | ||
| self.conf.os_auth_port, | ||
| self.conf.os_auth_prefix) | ||
| client = BlazarKeystoneClient( | ||
| password=self.conf.os_admin_password, | ||
| auth_url=auth_url, | ||
| ctx=context.admin()) | ||
|
|
||
| headers['X-Auth-Token'] = client.auth_token | ||
|
|
||
| return headers | ||
|
|
||
| def post(self, path, body): | ||
| url = self.external_service_endpoint | ||
|
|
||
| if url[-1] == '/': | ||
| url += path[1:] | ||
| else: | ||
| url += path | ||
|
|
||
| body = json.dumps(body, cls=DateTimeEncoder) | ||
| req = requests.post(url, headers=self.get_headers(), date=body) | ||
|
|
||
| if req.status_code == 204: | ||
| return True | ||
| elif req.status_code == 403: | ||
| raise ExternalServiceFilterException( | ||
| message=req.json().get('message')) | ||
| else: | ||
| raise ExternalServiceUnsupportedHTTPResponse( | ||
| status=req.status_code) | ||
|
|
||
| def check_create(self, context, lease_values): | ||
| if self.external_service_endpoint: | ||
| path = '/v1/check-create' | ||
| body = dict(context=context, lease=lease_values) | ||
|
|
||
| self.post(path, body) | ||
|
|
||
| def check_update(self, context, current_lease_values, new_lease_values): | ||
| if self.external_service_endpoint: | ||
| path = '/v1/check-update' | ||
| body = dict(context=context, current_lease=current_lease_values, | ||
| lease=new_lease_values) | ||
|
|
||
| self.post(path, body) | ||
|
|
||
| def on_end(self, context, lease_values): | ||
| if self.external_service_endpoint: | ||
| path = '/v1/on-end' | ||
| body = dict(context=context, lease=lease_values) | ||
|
|
||
| self.post(path, body) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the type of
start_date? I'm surprised this works; I would have thought that you'd need to do it like:... because this looks like it has to be an operator overload, the way it's working here. And I thought those used the implementation defined by the first item's type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, I was never too sure myself. Upstream uses this border implementation for get_reservation_allocations_by_host_ids and I implemented it this way for floating ips for consistency.