Implementing Akamai Driver
Change-Id: I822c0248995be0af3a880445b113225d26435698
This commit is contained in:
parent
6c938bc8e7
commit
89552af743
|
@ -92,3 +92,14 @@ consumer_key = "MYCONSUMERKEY"
|
|||
[drivers:provider:cloudfront]
|
||||
aws_access_key_id = "MY_AWS_ACCESS_KEY_ID"
|
||||
aws_secret_access_key = "MY_AWS_SECRET_ACCESS_KEY"
|
||||
|
||||
[drivers:provider:akamai]
|
||||
policy_api_client_token = "MY_POLICY_API_CLIENT_TOKEN"
|
||||
policy_api_client_secret = "MY_POLICY_API_CLIENT_SECRET"
|
||||
policy_api_access_token = "MY_POLICY_API_ACCESS_TOKEN"
|
||||
policy_api_base_url = "MY_POLICY_API_BASE_URL"
|
||||
ccu_api_client_token = "MY_CCU_API_CLIENT_TOKEN"
|
||||
ccu_api_client_secret = "MY_CCU_API_CLIENT_SECRET"
|
||||
ccu_api_access_token = "MY_CCU_API_ACCESS_TOKEN"
|
||||
ccu_api_base_url = "MY_CCU_API_BASE_URL"
|
||||
akamai_access_url_link = "MY_ACCESS_URL_LINK"
|
||||
|
|
|
@ -19,11 +19,11 @@ from poppy.model import common
|
|||
class Origin(common.DictSerializableModel):
|
||||
"""Origin."""
|
||||
|
||||
def __init__(self, origin, port=80, ssl=False):
|
||||
def __init__(self, origin, port=80, ssl=False, rules=[]):
|
||||
self._origin = origin
|
||||
self._port = port
|
||||
self._ssl = ssl
|
||||
self._rules = []
|
||||
self._rules = rules
|
||||
|
||||
@property
|
||||
def origin(self):
|
||||
|
@ -90,3 +90,10 @@ class Origin(common.DictSerializableModel):
|
|||
o.port = dict_obj.get("port", 80)
|
||||
o.ssl = dict_obj.get("ssl", False)
|
||||
return o
|
||||
|
||||
def to_dict(self):
|
||||
result = common.DictSerializableModel.to_dict(self)
|
||||
# need to deserialize the nested rules object
|
||||
rules_obj_list = result['rules']
|
||||
result['rules'] = [r.to_dict() for r in rules_obj_list]
|
||||
return result
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# Copyright (c) 2014 Rackspace, 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.
|
||||
|
||||
from poppy.provider.akamai import driver
|
||||
|
||||
# Hoist classes into package namespace
|
||||
Driver = driver.CDNProvider
|
|
@ -0,0 +1,27 @@
|
|||
# Copyright (c) 2013 Rackspace, 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.
|
||||
|
||||
"""Exports Akamai poppy controllers.
|
||||
|
||||
Field Mappings:
|
||||
In order to reduce the disk / memory space used,
|
||||
fields name will be, most of the time, the first
|
||||
letter of their long name. Fields mapping will be
|
||||
updated and documented in each controller class.
|
||||
"""
|
||||
|
||||
from poppy.provider.akamai import services
|
||||
|
||||
ServiceController = services.ServiceController
|
|
@ -0,0 +1,107 @@
|
|||
# Copyright (c) 2014 Rackspace, 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.
|
||||
|
||||
"""Akamai CDN Provider implementation."""
|
||||
|
||||
from akamai import edgegrid
|
||||
from oslo.config import cfg
|
||||
import requests
|
||||
|
||||
from poppy.provider.akamai import controllers
|
||||
from poppy.provider import base
|
||||
|
||||
AKAMAI_OPTIONS = [
|
||||
# credentials && base URL for policy API
|
||||
cfg.StrOpt(
|
||||
'policy_api_client_token',
|
||||
help='Akamai client token for policy API'),
|
||||
cfg.StrOpt(
|
||||
'policy_api_client_secret',
|
||||
help='Akamai client secret for policy API'),
|
||||
cfg.StrOpt(
|
||||
'policy_api_access_token',
|
||||
help='Akamai access token for policy API'),
|
||||
cfg.StrOpt(
|
||||
'policy_api_base_url',
|
||||
help='Akamai policy API base URL'),
|
||||
# credentials && base URL for CCU API
|
||||
# for purging
|
||||
cfg.StrOpt(
|
||||
'ccu_api_client_token',
|
||||
help='Akamai client token for CCU API'),
|
||||
cfg.StrOpt(
|
||||
'ccu_api_client_secret',
|
||||
help='Akamai client secret for CCU API'),
|
||||
cfg.StrOpt(
|
||||
'ccu_api_access_token',
|
||||
help='Akamai access token for CCU API'),
|
||||
cfg.StrOpt(
|
||||
'ccu_api_base_url',
|
||||
help='Akamai CCU Purge API base URL'),
|
||||
# Access URL in Akamai chain
|
||||
cfg.StrOpt(
|
||||
'akamai_access_url_link',
|
||||
help='Akamai domain access_url link'),
|
||||
]
|
||||
|
||||
AKAMAI_GROUP = 'drivers:provider:akamai'
|
||||
|
||||
|
||||
class CDNProvider(base.Driver):
|
||||
|
||||
def __init__(self, conf):
|
||||
super(CDNProvider, self).__init__(conf)
|
||||
|
||||
self._conf.register_opts(AKAMAI_OPTIONS,
|
||||
group=AKAMAI_GROUP)
|
||||
self.akamai_conf = self._conf[AKAMAI_GROUP]
|
||||
self.akamai_policy_api_base_url = self.akamai_conf.policy_api_base_url
|
||||
self.akamai_ccu_api_base_url = (
|
||||
self.akamai_conf.ccu_api_base_url)
|
||||
self.akamai_access_url_link = self.akamai_conf.akamai_access_url_link
|
||||
|
||||
self.akamai_policy_api_client = requests.Session()
|
||||
self.akamai_policy_api_client.auth = edgegrid.EdgeGridAuth(
|
||||
client_token=self.akamai_conf.policy_api_client_token,
|
||||
client_secret=self.akamai_conf.policy_api_client_secret,
|
||||
access_token=self.akamai_conf.policy_api_access_token
|
||||
)
|
||||
|
||||
self.akamai_ccu_api_client = requests.Session()
|
||||
self.akamai_ccu_api_client.auth = edgegrid.EdgeGridAuth(
|
||||
client_token=self.akamai_conf.ccu_api_client_token,
|
||||
client_secret=self.akamai_conf.ccu_api_client_secret,
|
||||
access_token=self.akamai_conf.ccu_api_access_token
|
||||
)
|
||||
|
||||
def is_alive(self):
|
||||
return True
|
||||
|
||||
@property
|
||||
def provider_name(self):
|
||||
return "Akamai"
|
||||
|
||||
@property
|
||||
def policy_api_client(self):
|
||||
return self.akamai_policy_api_client
|
||||
|
||||
@property
|
||||
def ccu_api_client(self):
|
||||
return self.akamai_ccu_api_client
|
||||
|
||||
@property
|
||||
def service_controller(self):
|
||||
"""Returns the driver's hostname controller."""
|
||||
return controllers.ServiceController(self)
|
|
@ -0,0 +1,340 @@
|
|||
# Copyright (c) 2014 Rackspace, 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 json
|
||||
|
||||
from poppy.common import decorators
|
||||
from poppy.openstack.common import log
|
||||
from poppy.provider import base
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class ServiceController(base.ServiceBase):
|
||||
|
||||
@property
|
||||
def policy_api_client(self):
|
||||
return self.driver.policy_api_client
|
||||
|
||||
@property
|
||||
def ccu_api_client(self):
|
||||
return self.driver.ccu_api_client
|
||||
|
||||
def __init__(self, driver):
|
||||
super(ServiceController, self).__init__(driver)
|
||||
|
||||
self.driver = driver
|
||||
self.policy_api_base_url = self.driver.akamai_policy_api_base_url
|
||||
self.ccu_api_base_url = self.driver.akamai_ccu_api_base_url
|
||||
self.request_header = {'Content-type': 'application/json',
|
||||
'Accept': 'text/plain'}
|
||||
|
||||
def create(self, service_obj):
|
||||
post_data = {
|
||||
'rules': []
|
||||
}
|
||||
|
||||
# for now global level akamai only supports 1 origin match global
|
||||
# * url. At this point we are guaranteed there is at least 1 origin
|
||||
# in incoming service_obj
|
||||
# form all origin rules for this service
|
||||
for origin in service_obj.origins:
|
||||
self._process_new_origin(origin, post_data['rules'])
|
||||
|
||||
classified_domains = self._classify_domains(service_obj.domains)
|
||||
|
||||
try:
|
||||
# NOTE(tonytan4ever): for akamai it might be possible to have
|
||||
# multiple policies associated with one poppy service, so we use
|
||||
# a list to represent provide_detail id
|
||||
ids = []
|
||||
links = []
|
||||
for classified_domain in classified_domains:
|
||||
# assign the content realm to be the digital property field
|
||||
# of each group
|
||||
dp = self._process_new_domain(classified_domain,
|
||||
post_data['rules'])
|
||||
|
||||
resp = self.policy_api_client.put(
|
||||
self.policy_api_base_url.format(
|
||||
policy_name=dp),
|
||||
data=json.dumps(post_data),
|
||||
headers=self.request_header)
|
||||
LOG.info('akamai response code: %s' % resp.status_code)
|
||||
LOG.info('akamai response text: %s' % resp.text)
|
||||
if resp.status_code != 200:
|
||||
raise RuntimeError(resp.text)
|
||||
ids.append(dp)
|
||||
# TODO(tonytan4ever): leave empty links for now
|
||||
# may need to work with dns integration
|
||||
LOG.info('Creating policy %s on domain %s complete' %
|
||||
(dp, ','.join(classified_domain)))
|
||||
links.append({'href': self.driver.akamai_access_url_link,
|
||||
"rel": 'access_url'
|
||||
})
|
||||
except Exception:
|
||||
return self.responder.failed("failed to create service")
|
||||
else:
|
||||
return self.responder.created(json.dumps(ids), links)
|
||||
|
||||
def _classify_domains(self, domains_list):
|
||||
# classify domains into different categories based on first two level
|
||||
# of domains, group them together
|
||||
result_dict = {}
|
||||
for domain in domains_list:
|
||||
# get the content_realm (1st and 2nd level domain of each domains)
|
||||
content_realm = '.'.join(domain.domain.split('.')[-2:])
|
||||
if content_realm not in result_dict:
|
||||
result_dict[content_realm] = [domain.domain]
|
||||
else:
|
||||
result_dict[content_realm].append(domain.domain)
|
||||
return [domain_mapping[1] for domain_mapping in result_dict.items()]
|
||||
|
||||
def _process_new_origin(self, origin, rules_list):
|
||||
rule_dict_template = {
|
||||
'matches': [],
|
||||
'behaviors': []
|
||||
}
|
||||
|
||||
origin_behavior_dict = {
|
||||
'name': 'origin',
|
||||
'value': '-',
|
||||
'params': {
|
||||
# missing digitalProperty(domain) for now
|
||||
'originDomain': '',
|
||||
'hostHeaderType': 'digital_property',
|
||||
'cacheKeyType': 'origin',
|
||||
'hostHeaderValue': '-',
|
||||
'cacheKeyValue': '-'
|
||||
}
|
||||
}
|
||||
# this is the global 'url-wildcard' rule
|
||||
if origin.rules == []:
|
||||
match_rule = {
|
||||
'name': 'url-wildcard',
|
||||
'value': '/*'
|
||||
}
|
||||
rule_dict_template['matches'].append(match_rule)
|
||||
else:
|
||||
for rule in origin.rules:
|
||||
match_rule = {
|
||||
'name': 'url-path',
|
||||
'value': rule.request_url
|
||||
}
|
||||
rule_dict_template['matches'].append(
|
||||
match_rule)
|
||||
|
||||
if origin.ssl:
|
||||
rule_dict_template['matches'].append({
|
||||
'name': 'url-scheme',
|
||||
'value': 'HTTPS'
|
||||
})
|
||||
|
||||
origin_behavior_dict['params']['originDomain'] = (
|
||||
origin.origin
|
||||
)
|
||||
rule_dict_template['behaviors'].append(
|
||||
origin_behavior_dict
|
||||
)
|
||||
# Append the new generated rules
|
||||
rules_list.append(rule_dict_template)
|
||||
|
||||
def _process_new_domain(self, domain, rules_list):
|
||||
dp = '.'.join(domain[0].split('.')[-2:])
|
||||
|
||||
for rule in rules_list:
|
||||
for behavior in rule['behaviors']:
|
||||
behavior['params']['digitalProperty'] = dp
|
||||
return dp
|
||||
|
||||
def get(self, service_name):
|
||||
pass
|
||||
|
||||
def update(self, provider_service_id,
|
||||
service_old,
|
||||
service_updates,
|
||||
service_obj):
|
||||
# depending on domains field presented or not, do PUT/POST
|
||||
# and depending on origins field presented or not, set behavior on
|
||||
# the data or not
|
||||
try:
|
||||
# get a list of policies
|
||||
policies = json.loads(provider_service_id)
|
||||
except Exception:
|
||||
# raise a more meaningful error for debugging info
|
||||
try:
|
||||
raise RuntimeError('Mal-formed Akaimai policy ids: %s' %
|
||||
provider_service_id)
|
||||
except Exception:
|
||||
return self.responder.failed("failed to update service")
|
||||
|
||||
ids = []
|
||||
links = []
|
||||
if len(service_obj.domains) > 0:
|
||||
# in this case we need to copy
|
||||
# and tweak the content of one old policy
|
||||
# and creates new policy for the new domains,
|
||||
# old policies ought to be deleted.
|
||||
try:
|
||||
resp = self.policy_api_client.get(
|
||||
self.policy_api_base_url.format(
|
||||
policy_name=policies[0]),
|
||||
headers=self.request_header)
|
||||
if resp.status_code != 200:
|
||||
raise RuntimeError(resp.text)
|
||||
except Exception:
|
||||
return self.responder.failed("failed to update service")
|
||||
else:
|
||||
policy_content = json.loads(resp.text)
|
||||
# Update origin if necessary
|
||||
if len(service_obj.origins) > 0:
|
||||
policy_content['rules'] = []
|
||||
for origin in service_obj.origins:
|
||||
self._process_new_origin(origin, policy_content['rules'])
|
||||
else:
|
||||
# to incorporate caching rule or referrer restriciton
|
||||
# if necessary
|
||||
pass
|
||||
|
||||
# Update domain if necessary ( by adjust digital property)
|
||||
classified_domains = self._classify_domains(service_obj.domains)
|
||||
|
||||
try:
|
||||
for classified_domain in classified_domains:
|
||||
# assign the content realm to be the digital property field
|
||||
# of each group
|
||||
dp = self._process_new_domain(classified_domain,
|
||||
policy_content['rules'])
|
||||
|
||||
if dp in policies:
|
||||
# in this case we should update existing policy
|
||||
# instead of create a new policy
|
||||
LOG.info('Start to update policy %s' % dp)
|
||||
resp = self.policy_api_client.put(
|
||||
self.policy_api_base_url.format(
|
||||
policy_name=dp),
|
||||
data=json.dumps(policy_content),
|
||||
headers=self.request_header)
|
||||
policies.remove(dp)
|
||||
else:
|
||||
LOG.info('Start to create new policy %s' % dp)
|
||||
resp = self.policy_api_client.put(
|
||||
self.policy_api_base_url.format(
|
||||
policy_name=dp),
|
||||
data=json.dumps(policy_content),
|
||||
headers=self.request_header)
|
||||
LOG.info('akamai response code: %s' % resp.status_code)
|
||||
LOG.info('akamai response text: %s' % resp.text)
|
||||
if resp.status_code != 200:
|
||||
raise RuntimeError(resp.text)
|
||||
ids.append(classified_domain[0])
|
||||
# TODO(tonytan4ever): leave empty links for now
|
||||
# may need to work with dns integration
|
||||
LOG.info('Creating/Updateing policy %s on domain %s '
|
||||
'complete' % (dp, ','.join(classified_domain)))
|
||||
links.append({'href': self.driver.akamai_access_url_link,
|
||||
'rel': 'access_url'
|
||||
})
|
||||
except Exception:
|
||||
return self.responder.failed("failed to update service")
|
||||
|
||||
try:
|
||||
for policy in policies:
|
||||
LOG.info('Starting to delete old policy %s' % policy)
|
||||
resp = self.policy_api_client.delete(
|
||||
self.policy_api_base_url.format(
|
||||
policy_name=policy))
|
||||
LOG.info('akamai response code: %s' % resp.status_code)
|
||||
LOG.info('akamai response text: %s' % resp.text)
|
||||
if resp.status_code != 200:
|
||||
raise RuntimeError(resp.text)
|
||||
LOG.info('Delete old policy %s complete' % policy)
|
||||
except Exception:
|
||||
return self.responder.failed("failed to update service")
|
||||
|
||||
else:
|
||||
# in this case we only need to adjust the existing policies
|
||||
for policy in policies:
|
||||
try:
|
||||
resp = self.policy_api_client.get(
|
||||
self.policy_api_base_url.format(policy_name=policy),
|
||||
headers=self.request_header)
|
||||
if resp.status_code != 200:
|
||||
raise RuntimeError(resp.text)
|
||||
except Exception:
|
||||
return self.responder.failed("failed to update service")
|
||||
else:
|
||||
policy_content = json.loads(resp.text)
|
||||
|
||||
if len(service_obj.origins) > 0:
|
||||
policy_content['rules'] = []
|
||||
for origin in service_obj.origins:
|
||||
self._process_new_origin(origin,
|
||||
policy_content['rules'])
|
||||
else:
|
||||
# TODO(tonytan4ever):
|
||||
# to incorporate caching rule and
|
||||
# referrer restriciton if necessary
|
||||
pass
|
||||
# post new policies back with Akamai Policy API
|
||||
try:
|
||||
LOG.info('Start to update policy %s ' % policy)
|
||||
resp = self.policy_api_client.put(
|
||||
self.policy_api_base_url.format(
|
||||
policy_name=policy),
|
||||
data=json.dumps(policy_content),
|
||||
headers=self.request_header)
|
||||
LOG.info('akamai response code: %s' % resp.status_code)
|
||||
LOG.info('akamai response text: %s' % resp.text)
|
||||
LOG.info('Update policy %s complete' % policy)
|
||||
except Exception:
|
||||
return self.responder.failed("failed to update service")
|
||||
ids = policies
|
||||
links.append({'href': self.driver.akamai_access_url_link,
|
||||
'rel': 'access_url'})
|
||||
return self.responder.updated(json.dumps(ids), links)
|
||||
|
||||
def delete(self, provider_service_id):
|
||||
# delete needs to provide a list of policy id/domains
|
||||
# then delete them accordingly
|
||||
try:
|
||||
policies = json.loads(provider_service_id)
|
||||
except Exception:
|
||||
# raise a more meaningful error for debugging info
|
||||
try:
|
||||
raise RuntimeError('Mal-formed Akaimai policy ids: %s' %
|
||||
provider_service_id)
|
||||
except Exception:
|
||||
return self.responder.failed("failed to delete service")
|
||||
try:
|
||||
for policy in policies:
|
||||
LOG.info('Starting to delete policy %s' % policy)
|
||||
resp = self.policy_api_client.delete(
|
||||
self.policy_api_base_url.format(policy_name=policy))
|
||||
LOG.info('akamai response code: %s' % resp.status_code)
|
||||
LOG.info('akamai response text: %s' % resp.text)
|
||||
if resp.status_code != 200:
|
||||
raise RuntimeError(resp.text)
|
||||
except Exception:
|
||||
return self.responder.failed("failed to delete service")
|
||||
else:
|
||||
return self.responder.deleted(provider_service_id)
|
||||
|
||||
def purge(self, service_id, purge_urls=None):
|
||||
pass
|
||||
|
||||
@decorators.lazy_property(write=False)
|
||||
def current_customer(self):
|
||||
return None
|
|
@ -1,3 +1,18 @@
|
|||
# Copyright (c) 2013 Rackspace, 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.
|
||||
|
||||
"""Fastly CDN Extension for CDN"""
|
||||
|
||||
from poppy.provider.fastly import driver
|
||||
|
|
|
@ -130,6 +130,7 @@ CQL_UPDATE_PROVIDER_DETAILS = '''
|
|||
|
||||
|
||||
class ServicesController(base.ServicesController):
|
||||
|
||||
"""Services Controller."""
|
||||
|
||||
@property
|
||||
|
@ -342,10 +343,16 @@ class ServicesController(base.ServicesController):
|
|||
name = result.get('service_name')
|
||||
origins = [json.loads(o) for o in result.get('origins', [])]
|
||||
domains = [json.loads(d) for d in result.get('domains', [])]
|
||||
origins = [origin.Origin(o['origin'],
|
||||
o.get('port', 80),
|
||||
o.get('ssl', False))
|
||||
for o in origins]
|
||||
origins = [
|
||||
origin.Origin(
|
||||
o['origin'], o.get(
|
||||
'port', 80), o.get(
|
||||
'ssl', False), [
|
||||
rule.Rule(
|
||||
rule_i.get('name'),
|
||||
request_url=rule_i.get('request_url'))
|
||||
for rule_i in o.get(
|
||||
'rules', [])]) for o in origins]
|
||||
domains = [domain.Domain(d['domain']) for d in domains]
|
||||
flavor_ref = result.get('flavor_id')
|
||||
|
||||
|
|
|
@ -14,10 +14,15 @@
|
|||
# limitations under the License.
|
||||
|
||||
from poppy.model.helpers import origin
|
||||
from poppy.transport.pecan.models.request import rule
|
||||
|
||||
|
||||
def load_from_json(json_data):
|
||||
origin_name = json_data.get("origin")
|
||||
port = json_data.get("port", 80)
|
||||
ssl = json_data.get("ssl", False)
|
||||
return origin.Origin(origin_name, port, ssl)
|
||||
rules = json_data.get("rules", [])
|
||||
rules = [rule.load_from_json(r) for r in rules]
|
||||
result = origin.Origin(origin_name, port, ssl)
|
||||
result.rules = rules
|
||||
return result
|
||||
|
|
|
@ -18,6 +18,8 @@ try:
|
|||
except ImportError: # pragma: no cover
|
||||
import collections # pragma: no cover
|
||||
|
||||
from poppy.transport.pecan.models.response import rule
|
||||
|
||||
|
||||
class Model(collections.OrderedDict):
|
||||
|
||||
|
@ -28,3 +30,4 @@ class Model(collections.OrderedDict):
|
|||
self['origin'] = origin.origin
|
||||
self['port'] = origin.port
|
||||
self['ssl'] = origin.ssl
|
||||
self['rules'] = [rule.Model(r) for r in origin.rules]
|
||||
|
|
|
@ -54,7 +54,10 @@ class ServiceSchema(schema_base.SchemaBase):
|
|||
'minItems': 1},
|
||||
'origins': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
# the first origin does not have to
|
||||
# have rules field, it will be defaulted
|
||||
# to global url matching
|
||||
'items': [{
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'origin': {
|
||||
|
@ -73,10 +76,69 @@ class ServiceSchema(schema_base.SchemaBase):
|
|||
80,
|
||||
443]},
|
||||
'ssl': {
|
||||
'type': 'boolean'}},
|
||||
},
|
||||
'required': True,
|
||||
'minItems': 1},
|
||||
'type': 'boolean'},
|
||||
'rules': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'required': True
|
||||
},
|
||||
'request_url': {
|
||||
'type': 'string',
|
||||
'required': True
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}],
|
||||
'minItems': 1,
|
||||
# the 2nd and successive items must have
|
||||
# 'rules' field which has at least one rule
|
||||
"additionalItems": {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'origin': {
|
||||
'type': 'string',
|
||||
'pattern': re.compile(
|
||||
'^(([^:/?#]+):)?'
|
||||
'(//([^/?#]*))?'
|
||||
'([^?#]*)(\?([^#]*))?'
|
||||
'(#(.*))?$',
|
||||
re.UNICODE
|
||||
),
|
||||
'required': True},
|
||||
'port': {
|
||||
'type': 'integer',
|
||||
'enum': [
|
||||
80,
|
||||
443]},
|
||||
'ssl': {
|
||||
'type': 'boolean'},
|
||||
'rules': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'required': True
|
||||
},
|
||||
'request_url': {
|
||||
'type': 'string',
|
||||
'required': True
|
||||
}
|
||||
}
|
||||
},
|
||||
'required': True,
|
||||
'minItems': 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
'caching': {
|
||||
'type': 'array',
|
||||
'required': False,
|
||||
|
@ -199,8 +261,10 @@ class ServiceSchema(schema_base.SchemaBase):
|
|||
'origins': {
|
||||
'type': 'array',
|
||||
'required': False,
|
||||
'minItems': 1,
|
||||
'items': {
|
||||
# the first origin does not have to
|
||||
# have rules field, it will be defaulted
|
||||
# to global url matching
|
||||
'items': [{
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'origin': {
|
||||
|
@ -219,8 +283,68 @@ class ServiceSchema(schema_base.SchemaBase):
|
|||
80,
|
||||
443]},
|
||||
'ssl': {
|
||||
'type': 'boolean'}},
|
||||
},
|
||||
'type': 'boolean'},
|
||||
'rules': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'required': True
|
||||
},
|
||||
'request_url': {
|
||||
'type': 'string',
|
||||
'required': True
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}],
|
||||
'minItems': 1,
|
||||
# the 2nd and successive items must have
|
||||
# 'rules' field which has at least one rule
|
||||
"additionalItems": {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'origin': {
|
||||
'type': 'string',
|
||||
'pattern': re.compile(
|
||||
'^(([^:/?#]+):)?'
|
||||
'(//([^/?#]*))?'
|
||||
'([^?#]*)(\?([^#]*))?'
|
||||
'(#(.*))?$',
|
||||
re.UNICODE
|
||||
),
|
||||
'required': True},
|
||||
'port': {
|
||||
'type': 'integer',
|
||||
'enum': [
|
||||
80,
|
||||
443]},
|
||||
'ssl': {
|
||||
'type': 'boolean'},
|
||||
'rules': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'required': True
|
||||
},
|
||||
'request_url': {
|
||||
'type': 'string',
|
||||
'required': True
|
||||
}
|
||||
}
|
||||
},
|
||||
'required': True,
|
||||
'minItems': 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
'caching': {
|
||||
'type': 'array',
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
edgegrid-python
|
|
@ -2,6 +2,7 @@
|
|||
-r docs.txt
|
||||
-r transport/pecan.txt
|
||||
-r storage/cassandra.txt
|
||||
-r provider/akamai.txt
|
||||
-r provider/cloudfront.txt
|
||||
-r provider/fastly.txt
|
||||
-r provider/maxcdn.txt
|
||||
|
|
|
@ -54,6 +54,7 @@ poppy.provider =
|
|||
mock = poppy.provider.mock:Driver
|
||||
cloudfront = poppy.provider.cloudfront:Driver
|
||||
maxcdn = poppy.provider.maxcdn:Driver
|
||||
akamai = poppy.provider.akamai:Driver
|
||||
|
||||
[wheel]
|
||||
universal = 1
|
||||
|
|
|
@ -31,7 +31,7 @@ def abort(code):
|
|||
|
||||
@decorators.validation_function
|
||||
def is_valid_json(r):
|
||||
"""Test for a valid JSON string."""
|
||||
'''Test for a valid JSON string.'''
|
||||
if len(r.body) == 0:
|
||||
return
|
||||
else:
|
||||
|
@ -48,29 +48,33 @@ class DummyRequest(object):
|
|||
|
||||
def __init__(self):
|
||||
self.headers = dict(header1='headervalue1')
|
||||
self.method = "POST"
|
||||
self.method = 'POST'
|
||||
self.body = json.dumps({
|
||||
"name": "fake_service_name",
|
||||
"domains": [
|
||||
{"domain": "www.mywebsite.com"},
|
||||
{"domain": "blog.mywebsite.com"},
|
||||
'name': 'fake_service_name',
|
||||
'domains': [
|
||||
{'domain': 'www.mywebsite.com'},
|
||||
{'domain': 'blog.mywebsite.com'},
|
||||
],
|
||||
"origins": [
|
||||
'origins': [
|
||||
{
|
||||
"origin": "mywebsite.com",
|
||||
"port": 80,
|
||||
"ssl": False
|
||||
'origin': 'mywebsite.com',
|
||||
'port': 80,
|
||||
'ssl': False
|
||||
},
|
||||
{
|
||||
"origin": "mywebsite.com",
|
||||
'origin': 'mywebsite.com',
|
||||
'rules': [{
|
||||
'name': 'img',
|
||||
'request_url': '/img'
|
||||
}]
|
||||
}
|
||||
],
|
||||
"caching": [
|
||||
{"name": "default", "ttl": 3600},
|
||||
{"name": "home",
|
||||
"ttl": 17200,
|
||||
"rules": [
|
||||
{"name": "index", "request_url": "/index.htm"}
|
||||
'caching': [
|
||||
{'name': 'default', 'ttl': 3600},
|
||||
{'name': 'home',
|
||||
'ttl': 17200,
|
||||
'rules': [
|
||||
{'name': 'index', 'request_url': '/index.htm'}
|
||||
]
|
||||
},
|
||||
{"name": "images",
|
||||
|
@ -80,7 +84,7 @@ class DummyRequest(object):
|
|||
]
|
||||
}
|
||||
],
|
||||
"flavor_ref": "https://www.poppycdn.io/v1.0/flavors/standard"
|
||||
'flavor_ref': 'https://www.poppycdn.io/v1.0/flavors/standard'
|
||||
})
|
||||
|
||||
|
||||
|
@ -96,38 +100,38 @@ class DummyRequestWithInvalidHeader(DummyRequest):
|
|||
fake_request_good = DummyRequest()
|
||||
fake_request_bad_missing_domain = DummyRequest()
|
||||
fake_request_bad_missing_domain.body = json.dumps({
|
||||
"name": "fake_service_name",
|
||||
"origins": [
|
||||
'name': 'fake_service_name',
|
||||
'origins': [
|
||||
{
|
||||
"origin": "mywebsite.com",
|
||||
"port": 80,
|
||||
"ssl": False
|
||||
'origin': 'mywebsite.com',
|
||||
'port': 80,
|
||||
'ssl': False
|
||||
}
|
||||
],
|
||||
"caching": [
|
||||
{"name": "default", "ttl": 3600},
|
||||
{"name": "home",
|
||||
"ttl": 17200,
|
||||
"rules": [
|
||||
{"name": "index", "request_url": "/index.htm"}
|
||||
'caching': [
|
||||
{'name': 'default', 'ttl': 3600},
|
||||
{'name': 'home',
|
||||
'ttl': 17200,
|
||||
'rules': [
|
||||
{'name': 'index', 'request_url': '/index.htm'}
|
||||
]
|
||||
},
|
||||
{"name": "images",
|
||||
"ttl": 12800,
|
||||
"rules": [
|
||||
{"name": "images", "request_url": "*.png"}
|
||||
{'name': 'images',
|
||||
'ttl': 12800,
|
||||
'rules': [
|
||||
{'name': 'images', 'request_url': '*.png'}
|
||||
]
|
||||
}
|
||||
],
|
||||
"flavor_ref": "https://www.poppycdn.io/v1.0/flavors/standard"
|
||||
'flavor_ref': 'https://www.poppycdn.io/v1.0/flavors/standard'
|
||||
})
|
||||
fake_request_bad_invalid_json_body = DummyRequest()
|
||||
fake_request_bad_invalid_json_body.body = "{"
|
||||
fake_request_bad_invalid_json_body.body = '{'
|
||||
|
||||
|
||||
class _AssertRaisesContext(object):
|
||||
|
||||
"""A context manager used to implement TestCase.assertRaises* methods."""
|
||||
'''A context manager used to implement TestCase.assertRaises* methods.'''
|
||||
|
||||
def __init__(self, expected, test_case, expected_regexp=None):
|
||||
self.expected = expected
|
||||
|
@ -144,7 +148,7 @@ class _AssertRaisesContext(object):
|
|||
except AttributeError:
|
||||
exc_name = str(self.expected)
|
||||
raise self.failureException(
|
||||
"{0} not raised".format(exc_name))
|
||||
'{0} not raised'.format(exc_name))
|
||||
if not issubclass(exc_type, self.expected):
|
||||
return False # let unexpected exceptions pass through
|
||||
self.exception = exc_value # store for later retrieval
|
||||
|
@ -170,7 +174,7 @@ class _AssertRaisesContext(object):
|
|||
class BaseTestCase(base.TestCase):
|
||||
|
||||
def assertRaises(self, excClass, callableObj=None, *args, **kwargs):
|
||||
"""Check the expected Exception is raised.
|
||||
'''Check the expected Exception is raised.
|
||||
|
||||
Fail unless an exception of class excClass is raised
|
||||
by callableObj when invoked with arguments args and keyword
|
||||
|
@ -193,7 +197,7 @@ class BaseTestCase(base.TestCase):
|
|||
do_something()
|
||||
the_exception = cm.exception
|
||||
self.assertEqual(the_exception.error_code, 3)
|
||||
"""
|
||||
'''
|
||||
context = _AssertRaisesContext(excClass, self)
|
||||
if callableObj is None:
|
||||
return context
|
||||
|
@ -202,7 +206,7 @@ class BaseTestCase(base.TestCase):
|
|||
|
||||
def assertRaisesRegexp(self, expected_exception, expected_regexp,
|
||||
callable_obj=None, *args, **kwargs):
|
||||
"""Asserts that the message in a raised exception matches a regexp."""
|
||||
'''Asserts that the message in a raised exception matches a regexp.'''
|
||||
context = _AssertRaisesContext(expected_exception, self,
|
||||
expected_regexp)
|
||||
if callable_obj is None:
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"single_one_origin": {
|
||||
"name" : "mysite.com",
|
||||
"domains": [
|
||||
{"domain": "parsely.sage.com"},
|
||||
{"domain": "densely.sage.com"},
|
||||
{"domain": "rosemary.thyme.net"}
|
||||
],
|
||||
"origins": [
|
||||
{"origin": "mockdomain.com", "ssl": false, "port": 80}
|
||||
],
|
||||
"flavor_ref" : "standard"
|
||||
},
|
||||
"multiple_origins": {
|
||||
"name" : "mysite.com",
|
||||
"domains": [
|
||||
{"domain": "parsely.sage.com"},
|
||||
{"domain": "densely.sage.com"},
|
||||
{"domain": "rosemary.thyme.net"}
|
||||
],
|
||||
"origins": [
|
||||
{"origin": "mockdomain.com", "ssl": false, "port": 80},
|
||||
{"origin": "mockdomain-image.com",
|
||||
"rules": [{"name": "img", "request_url": "/img"}] }
|
||||
],
|
||||
"flavor_ref" : "standard"
|
||||
},
|
||||
"multiple_origins_complicated": {
|
||||
"name" : "mysite.com",
|
||||
"domains": [
|
||||
{"domain": "parsely.sage.com"},
|
||||
{"domain": "densely.sage.com"},
|
||||
{"domain": "rosemary.thyme.net"}
|
||||
],
|
||||
"origins": [
|
||||
{"origin": "mockdomain-text.com", "ssl": false, "port": 80,
|
||||
"rules": [{"name": "global", "request_url": "/*"},
|
||||
{"name": "text", "request_url": "/text"}]},
|
||||
{"origin": "mockdomain-image.com",
|
||||
"rules": [{"name": "img", "request_url": "/img"}] }
|
||||
],
|
||||
"flavor_ref" : "standard"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"single_one_origin_with_domains": {
|
||||
"name" : "mysite.com",
|
||||
"domains": [
|
||||
{"domain": "parsely.sage.com"},
|
||||
{"domain": "densely.sage.com"},
|
||||
{"domain": "rosemary.thyme.net"}
|
||||
],
|
||||
"origins": [
|
||||
{"origin": "mockdomain.com", "ssl": false, "port": 80}
|
||||
],
|
||||
"flavor_ref" : "standard"
|
||||
},
|
||||
"multiple_origins_with_domains": {
|
||||
"name" : "mysite.com",
|
||||
"domains": [
|
||||
{"domain": "parsely.sage.com"},
|
||||
{"domain": "densely.sage.com"},
|
||||
{"domain": "rosemary.thyme.net"}
|
||||
],
|
||||
"origins": [
|
||||
{"origin": "mockdomain.com", "ssl": false, "port": 80},
|
||||
{"origin": "mockdomain-image.com",
|
||||
"rules": [{"name": "img", "request_url": "/img"}] }
|
||||
],
|
||||
"flavor_ref" : "standard"
|
||||
},
|
||||
"single_one_origin_without_domains": {
|
||||
"name" : "mysite.com",
|
||||
"origins": [
|
||||
{"origin": "mockdomain.com", "ssl": false, "port": 80}
|
||||
],
|
||||
"flavor_ref" : "standard"
|
||||
},
|
||||
"multiple_origins_without_domains": {
|
||||
"name" : "mysite.com",
|
||||
"origins": [
|
||||
{"origin": "mockdomain.com", "ssl": false, "port": 80},
|
||||
{"origin": "mockdomain-image.com",
|
||||
"rules": [{"name": "img", "request_url": "/img"}] }
|
||||
],
|
||||
"flavor_ref" : "standard"
|
||||
},
|
||||
"no_origin_with_domains": {
|
||||
"name" : "mysite.com",
|
||||
"domains": [
|
||||
{"domain": "parsely.sage.com"},
|
||||
{"domain": "densely.sage.com"},
|
||||
{"domain": "rosemary.thyme.net"}
|
||||
],
|
||||
"flavor_ref" : "standard"
|
||||
},
|
||||
"no_origin_without_domains": {
|
||||
"name" : "mysite.com",
|
||||
"flavor_ref" : "standard"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"single_domain_list": [
|
||||
"www.mysite.com",
|
||||
"www.myawsomesite.com",
|
||||
"www.myawsomesite2.com"
|
||||
],
|
||||
"multiple_domain_list": [
|
||||
"www.mysite.com",
|
||||
"blog.mysite.com",
|
||||
"www.myawsomesite.com",
|
||||
"test.myawsomesite.com",
|
||||
"www.myawsomesite2.com"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
# Copyright (c) 2014 Rackspace, 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 mock
|
||||
from oslo.config import cfg
|
||||
|
||||
from poppy.provider.akamai import driver
|
||||
from tests.unit import base
|
||||
|
||||
AKAMAI_OPTIONS = [
|
||||
# credentials && base URL for policy API
|
||||
cfg.StrOpt(
|
||||
'policy_api_client_token', default='ccc',
|
||||
help='Akamai client token for policy API'),
|
||||
cfg.StrOpt(
|
||||
'policy_api_client_secret', default='sss',
|
||||
help='Akamai client secret for policy API'),
|
||||
cfg.StrOpt(
|
||||
'policy_api_access_token', default='aaa',
|
||||
help='Akamai access token for policy API'),
|
||||
cfg.StrOpt(
|
||||
'policy_api_base_url', default='/abc',
|
||||
help='Akamai policy API base URL'),
|
||||
# credentials && base URL for CCU API
|
||||
# for purging
|
||||
cfg.StrOpt(
|
||||
'ccu_api_client_token', default='ccc',
|
||||
help='Akamai client token for CCU API'),
|
||||
cfg.StrOpt(
|
||||
'ccu_api_client_secret', default='sss',
|
||||
help='Akamai client secret for CCU API'),
|
||||
cfg.StrOpt(
|
||||
'ccu_api_access_token', default='aaa',
|
||||
help='Akamai access token for CCU API'),
|
||||
cfg.StrOpt(
|
||||
'ccu_api_base_url', default='/abc',
|
||||
help='Akamai CCU Purge API base URL'),
|
||||
# Access URL in Akamai chain
|
||||
cfg.StrOpt(
|
||||
'akamai_access_url_link', default='abc.def.org',
|
||||
help='Akamai domain access_url link'),
|
||||
]
|
||||
|
||||
|
||||
class TestDriver(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDriver, self).setUp()
|
||||
|
||||
self.conf = cfg.ConfigOpts()
|
||||
|
||||
@mock.patch('akamai.edgegrid.EdgeGridAuth')
|
||||
@mock.patch.object(driver, 'AKAMAI_OPTIONS', new=AKAMAI_OPTIONS)
|
||||
def test_init(self, mock_connect):
|
||||
provider = driver.CDNProvider(self.conf)
|
||||
akamai_conf = provider._conf['drivers:provider:akamai']
|
||||
mock_connect.assert_called_with(
|
||||
client_token=(
|
||||
akamai_conf.policy_api_client_token),
|
||||
client_secret=(
|
||||
akamai_conf.policy_api_client_secret),
|
||||
access_token=(
|
||||
akamai_conf.policy_api_access_token)
|
||||
)
|
||||
|
||||
mock_connect.assert_called_with(
|
||||
client_token=(
|
||||
akamai_conf.ccu_api_client_token),
|
||||
client_secret=(
|
||||
akamai_conf.ccu_api_client_secret),
|
||||
access_token=(
|
||||
akamai_conf.ccu_api_access_token)
|
||||
)
|
||||
self.assertEqual('Akamai', provider.provider_name)
|
||||
|
||||
def test_is_alive(self):
|
||||
provider = driver.CDNProvider(self.conf)
|
||||
self.assertEqual(True, provider.is_alive())
|
||||
|
||||
@mock.patch('akamai.edgegrid.EdgeGridAuth')
|
||||
@mock.patch.object(driver, 'AKAMAI_OPTIONS', new=AKAMAI_OPTIONS)
|
||||
def test_get_client(self, mock_connect):
|
||||
mock_connect.return_value = mock.Mock()
|
||||
provider = driver.CDNProvider(self.conf)
|
||||
self.assertNotEqual(None, provider.policy_api_client)
|
||||
self.assertNotEqual(None, provider.ccu_api_client)
|
|
@ -0,0 +1,169 @@
|
|||
# Copyright (c) 2014 Rackspace, 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 json
|
||||
import uuid
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
|
||||
from poppy.model.helpers import domain
|
||||
from poppy.provider.akamai import services
|
||||
from poppy.transport.pecan.models.request import service
|
||||
from tests.unit import base
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestServices(base.TestCase):
|
||||
|
||||
@mock.patch(
|
||||
'poppy.provider.akamai.services.ServiceController.policy_api_client')
|
||||
@mock.patch(
|
||||
'poppy.provider.akamai.services.ServiceController.ccu_api_client')
|
||||
@mock.patch('poppy.provider.akamai.driver.CDNProvider')
|
||||
def setUp(self, mock_controller_policy_api_client,
|
||||
mock_controller_ccu_api_client,
|
||||
mock_driver):
|
||||
super(TestServices, self).setUp()
|
||||
self.driver = mock_driver()
|
||||
self.controller = services.ServiceController(self.driver)
|
||||
|
||||
@ddt.file_data('domains_list.json')
|
||||
def test_classify_domains(self, domains_list):
|
||||
domains_list = [domain.Domain(domain_s) for domain_s in domains_list]
|
||||
c_domains_list = self.controller._classify_domains(domains_list)
|
||||
prev_content_realm = ''
|
||||
for c_domains in c_domains_list:
|
||||
self.assertTrue(len(c_domains) >= 1)
|
||||
content_realm = '.'.join(c_domains[0].split('.')[-2:])
|
||||
if len(c_domains) > 1:
|
||||
# inside a group the content realm should be
|
||||
# the same
|
||||
for c_domain in c_domains:
|
||||
self.assertEqual(content_realm,
|
||||
'.'.join(c_domain.split('.')[-2:]))
|
||||
next_content_realm = content_realm
|
||||
# assert different group's content real is not eaual
|
||||
self.assertNotEqual(prev_content_realm, next_content_realm,
|
||||
'classified domains\'s content realm'
|
||||
' should not equal')
|
||||
prev_content_realm = next_content_realm
|
||||
|
||||
@ddt.file_data('data_service.json')
|
||||
def test_create_with_exception(self, service_json):
|
||||
# ASSERTIONS
|
||||
# create_service
|
||||
service_obj = service.load_from_json(service_json)
|
||||
|
||||
self.controller.policy_api_client.put.side_effect = (
|
||||
RuntimeError('Creating service failed.'))
|
||||
resp = self.controller.create(service_obj)
|
||||
self.assertIn('error', resp[self.driver.provider_name])
|
||||
|
||||
@ddt.file_data('data_service.json')
|
||||
def test_create_with_4xx_return(self, service_json):
|
||||
service_obj = service.load_from_json(service_json)
|
||||
|
||||
# test exception
|
||||
self.controller.policy_api_client.delete.return_value = mock.Mock(
|
||||
status_code=400,
|
||||
text='Some create service error happened'
|
||||
)
|
||||
resp = self.controller.create(service_obj)
|
||||
|
||||
self.assertIn('error', resp[self.driver.provider_name])
|
||||
|
||||
@ddt.file_data('data_service.json')
|
||||
def test_create(self, service_json):
|
||||
service_obj = service.load_from_json(service_json)
|
||||
self.controller.create(service_obj)
|
||||
|
||||
self.controller.policy_api_client.put.assert_called_once()
|
||||
|
||||
def test_delete_with_exception(self):
|
||||
provider_service_id = json.dumps([str(uuid.uuid1())])
|
||||
|
||||
# test exception
|
||||
exception = RuntimeError('ding')
|
||||
self.controller.policy_api_client.delete.side_effect = exception
|
||||
resp = self.controller.delete(provider_service_id)
|
||||
|
||||
self.assertIn('error', resp[self.driver.provider_name])
|
||||
|
||||
def test_delete_with_service_id_json_load_error(self):
|
||||
# This should trigger a json.loads error
|
||||
provider_service_id = None
|
||||
resp = self.controller.delete(provider_service_id)
|
||||
self.assertIn('error', resp[self.driver.provider_name])
|
||||
|
||||
def test_delete_with_4xx_return(self):
|
||||
provider_service_id = json.dumps([str(uuid.uuid1())])
|
||||
|
||||
# test exception
|
||||
self.controller.policy_api_client.delete.return_value = mock.Mock(
|
||||
status_code=400,
|
||||
text='Some error happened'
|
||||
)
|
||||
resp = self.controller.delete(provider_service_id)
|
||||
|
||||
self.assertIn('error', resp[self.driver.provider_name])
|
||||
|
||||
def test_delete(self):
|
||||
provider_service_id = json.dumps([str(uuid.uuid1())])
|
||||
|
||||
self.controller.delete(provider_service_id)
|
||||
self.controller.policy_api_client.delete.assert_called_once()
|
||||
|
||||
@ddt.file_data('data_update_service.json')
|
||||
def test_update_with_get_error(self, service_json):
|
||||
provider_service_id = json.dumps([str(uuid.uuid1())])
|
||||
controller = services.ServiceController(self.driver)
|
||||
controller.policy_api_client.get.return_value = mock.Mock(
|
||||
status_code=400,
|
||||
text='Some get error happened'
|
||||
)
|
||||
controller.policy_api_client.put.return_value = mock.Mock(
|
||||
status_code=200,
|
||||
text='Put successful'
|
||||
)
|
||||
controller.policy_api_client.delete.return_value = mock.Mock(
|
||||
status_code=200,
|
||||
text='Delete successful'
|
||||
)
|
||||
service_obj = service.load_from_json(service_json)
|
||||
resp = controller.update(
|
||||
provider_service_id, service_obj, service_obj, service_obj)
|
||||
self.assertIn('error', resp[self.driver.provider_name])
|
||||
|
||||
@ddt.file_data('data_update_service.json')
|
||||
def test_update(self, service_json):
|
||||
provider_service_id = json.dumps([str(uuid.uuid1())])
|
||||
controller = services.ServiceController(self.driver)
|
||||
controller.policy_api_client.get.return_value = mock.Mock(
|
||||
status_code=200,
|
||||
text=json.dumps(dict(rules=[]))
|
||||
)
|
||||
controller.policy_api_client.put.return_value = mock.Mock(
|
||||
status_code=200,
|
||||
text='Put successful'
|
||||
)
|
||||
controller.policy_api_client.delete.return_value = mock.Mock(
|
||||
status_code=200,
|
||||
text='Delete successful'
|
||||
)
|
||||
service_obj = service.load_from_json(service_json)
|
||||
resp = controller.update(
|
||||
provider_service_id, service_obj, service_obj, service_obj)
|
||||
self.assertIn('id', resp[self.driver.provider_name])
|
Loading…
Reference in New Issue