Adds End To End Tests
This patchset adds the end to end tests for Poppy. The test steps include, 1) Deploying a WP site on a cloud server 2) Creating a Poppy Service for the WP site 3) Reporting page load metrics using webpagtest Change-Id: I5f96cd21ac59806310f314e27da4f07642d3cfb0
This commit is contained in:
parent
92b8e69942
commit
8a0e22fb60
|
@ -0,0 +1,91 @@
|
||||||
|
# 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 BeautifulSoup
|
||||||
|
from cafe.drivers.unittest import fixtures
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from tests.api.utils import client
|
||||||
|
from tests.endtoend.utils import config
|
||||||
|
from tests.endtoend.utils import heatclient
|
||||||
|
from tests.endtoend.utils import wptclient
|
||||||
|
|
||||||
|
|
||||||
|
class TestBase(fixtures.BaseTestFixture):
|
||||||
|
"""Base class for End To End CDN Tests
|
||||||
|
|
||||||
|
The tests do the following,
|
||||||
|
1. Spins up a wordpress site on a cloud server.
|
||||||
|
2. Create a Poppy service via API call using the origin & domain
|
||||||
|
feom Step 1.
|
||||||
|
3. Measures the pageload performance of the CDN enabled website.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
|
||||||
|
super(TestBase, cls).setUpClass()
|
||||||
|
|
||||||
|
cls.auth_config = config.AuthConfig()
|
||||||
|
cls.auth_client = client.AuthClient()
|
||||||
|
auth_token, project_id = cls.auth_client.authenticate_user(
|
||||||
|
cls.auth_config.base_url,
|
||||||
|
cls.auth_config.user_name,
|
||||||
|
cls.auth_config.api_key)
|
||||||
|
|
||||||
|
cls.poppy_config = config.PoppyConfig()
|
||||||
|
cls.url = cls.poppy_config.base_url
|
||||||
|
|
||||||
|
cls.poppy_client = client.PoppyClient(
|
||||||
|
cls.url, auth_token, project_id,
|
||||||
|
serialize_format='json',
|
||||||
|
deserialize_format='json')
|
||||||
|
|
||||||
|
cls.test_config = config.TestConfig()
|
||||||
|
|
||||||
|
cls.heat_config = config.OrchestrationConfig()
|
||||||
|
heat_url = cls.heat_config.base_url + '/' + project_id
|
||||||
|
cls.heat_client = heatclient.HeatClient(heat_url=heat_url,
|
||||||
|
token=auth_token)
|
||||||
|
|
||||||
|
cls.wpt_config = config.WebPageTestConfig()
|
||||||
|
cls.wpt_client = wptclient.WebpageTestClient(
|
||||||
|
wpt_url=cls.wpt_config.base_url, api_key=cls.wpt_config.api_key)
|
||||||
|
|
||||||
|
def get_content(self, url):
|
||||||
|
"""Get content from the url
|
||||||
|
|
||||||
|
:param url: url to get content from
|
||||||
|
:returns: content fetched from the url
|
||||||
|
"""
|
||||||
|
response = requests.get(url)
|
||||||
|
content = BeautifulSoup.BeautifulSoup(response.text)
|
||||||
|
return content.findAll()
|
||||||
|
|
||||||
|
def assertSameContent(self, origin_url, access_url):
|
||||||
|
"""Asserts that the origin & access_url serve the same content
|
||||||
|
|
||||||
|
:param origin: Origin website
|
||||||
|
:param access_url: CDN enabled url of the origin website
|
||||||
|
:returns: True/False
|
||||||
|
"""
|
||||||
|
origin_content = self.get_content(url=origin_url)
|
||||||
|
cdn_content = self.get_content(url=access_url)
|
||||||
|
self.assertEqual(origin_content, cdn_content)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
"""Deletes the added resources."""
|
||||||
|
super(TestBase, cls).tearDownClass()
|
|
@ -0,0 +1,99 @@
|
||||||
|
# coding= utf-8
|
||||||
|
|
||||||
|
# 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 random
|
||||||
|
import string
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from tests.endtoend import base
|
||||||
|
|
||||||
|
|
||||||
|
class TestWebsiteCDN(base.TestBase):
|
||||||
|
|
||||||
|
"""Tests for CDN enabling a website."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestWebsiteCDN, self).setUp()
|
||||||
|
|
||||||
|
def _random_string(length=12):
|
||||||
|
return ''.join([random.choice(string.ascii_letters)
|
||||||
|
for _ in range(length)])
|
||||||
|
|
||||||
|
self.stack_name = _random_string()
|
||||||
|
|
||||||
|
self.domain_name = 'TestCDN-' + _random_string() + '.org'
|
||||||
|
|
||||||
|
# Deploys a test website to a cloud server
|
||||||
|
self.heat_client.create_stack(yaml_path=self.heat_config.yaml_path,
|
||||||
|
stack_name=self.stack_name,
|
||||||
|
domain_name=self.domain_name)
|
||||||
|
print('Stack Name', self.stack_name)
|
||||||
|
print('Domain Name', self.domain_name)
|
||||||
|
|
||||||
|
self.heat_client.wait_for_stack_status(stack_name=self.stack_name)
|
||||||
|
self.origin = self.heat_client.get_server_ip(
|
||||||
|
stack_name=self.stack_name)
|
||||||
|
print('Origin', self.origin)
|
||||||
|
|
||||||
|
def test_enable_cdn(self):
|
||||||
|
|
||||||
|
# Create a Poppy Service for the test website
|
||||||
|
domain_list = [{"domain": self.domain_name}]
|
||||||
|
origin_list = [{"origin": self.origin,
|
||||||
|
"port": 80,
|
||||||
|
"ssl": False}]
|
||||||
|
caching_list = []
|
||||||
|
self.service_name = str(uuid.uuid1())
|
||||||
|
|
||||||
|
resp = self.poppy_client.create_service(
|
||||||
|
service_name=self.service_name,
|
||||||
|
domain_list=domain_list,
|
||||||
|
origin_list=origin_list,
|
||||||
|
caching_list=caching_list,
|
||||||
|
flavor_id=self.poppy_config.flavor)
|
||||||
|
|
||||||
|
self.assertEqual(resp.status_code, 202)
|
||||||
|
self.poppy_client.wait_for_service_status(
|
||||||
|
service_name=self.service_name,
|
||||||
|
status='DEPLOYED')
|
||||||
|
|
||||||
|
resp = self.poppy_client.get_service(service_name=self.service_name)
|
||||||
|
links = resp.json()['links']
|
||||||
|
access_url = [link['href'] for link in links if
|
||||||
|
link['rel'] == 'access_url']
|
||||||
|
access_url = 'http://' + access_url[0]
|
||||||
|
origin_url = 'http://' + self.origin
|
||||||
|
self.assertSameContent(origin_url=origin_url, access_url=access_url)
|
||||||
|
|
||||||
|
# Benchmark page load metrics for the CDN enabled website
|
||||||
|
wpt_test_results = {}
|
||||||
|
for location in self.wpt_config.test_locations:
|
||||||
|
wpt_test_url = self.wpt_client.start_test(access_url=access_url,
|
||||||
|
test_location=location,
|
||||||
|
runs=2)
|
||||||
|
wpt_test_results[location] = wpt_test_url
|
||||||
|
'''self.wpt_client.wait_for_test_status(status='COMPLETE',
|
||||||
|
test_url=wpt_test_url)
|
||||||
|
wpt_test_results[location] = self.wpt_client.get_test_details(
|
||||||
|
test_url=wpt_test_url)
|
||||||
|
'''
|
||||||
|
print(wpt_test_results)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.heat_client.delete_stack(stack_name=self.stack_name)
|
||||||
|
self.poppy_client.delete_service(service_name=self.service_name)
|
||||||
|
super(TestWebsiteCDN, self).tearDown()
|
|
@ -0,0 +1,106 @@
|
||||||
|
# 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 cafe.engine.models import data_interfaces
|
||||||
|
|
||||||
|
|
||||||
|
class PoppyConfig(data_interfaces.ConfigSectionInterface):
|
||||||
|
"""Defines the config values for poppy."""
|
||||||
|
SECTION_NAME = 'poppy'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def base_url(self):
|
||||||
|
"""poppy endpoint."""
|
||||||
|
return self.get('base_url')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def flavor(self):
|
||||||
|
"""poppy flavor to use in create service call."""
|
||||||
|
return self.get('flavor')
|
||||||
|
|
||||||
|
|
||||||
|
class TestConfig(data_interfaces.ConfigSectionInterface):
|
||||||
|
"""Defines the config values specific to test execution."""
|
||||||
|
SECTION_NAME = 'test_configuration'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def provider_validation(self):
|
||||||
|
"""Boolean value indicating if tests verify provider side details."""
|
||||||
|
return self.get_boolean('provider_validation')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status_check_retry_interval(self):
|
||||||
|
"""Int value to set retry intervals for status check."""
|
||||||
|
return int(self.get('status_check_retry_interval'))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status_check_retry_timeout(self):
|
||||||
|
"""Int value to set timeout for status check."""
|
||||||
|
return int(self.get('status_check_retry_timeout'))
|
||||||
|
|
||||||
|
|
||||||
|
class AuthConfig(data_interfaces.ConfigSectionInterface):
|
||||||
|
"""Defines the auth config values."""
|
||||||
|
SECTION_NAME = 'auth'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def base_url(self):
|
||||||
|
"""Auth endpoint."""
|
||||||
|
return self.get('base_url')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user_name(self):
|
||||||
|
"""The name of the user, if applicable."""
|
||||||
|
return self.get('user_name')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api_key(self):
|
||||||
|
"""The user's api key, if applicable."""
|
||||||
|
return self.get_raw('api_key')
|
||||||
|
|
||||||
|
|
||||||
|
class OrchestrationConfig(data_interfaces.ConfigSectionInterface):
|
||||||
|
"""Defines the Rackspace orchestration config values."""
|
||||||
|
SECTION_NAME = 'orchestration'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def base_url(self):
|
||||||
|
"""Orchestration base url."""
|
||||||
|
return self.get('base_url')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def yaml_path(self):
|
||||||
|
"""path to the heat yaml file."""
|
||||||
|
return self.get('yaml_path')
|
||||||
|
|
||||||
|
|
||||||
|
class WebPageTestConfig(data_interfaces.ConfigSectionInterface):
|
||||||
|
"""Defines the webpage test config values."""
|
||||||
|
SECTION_NAME = 'webpagetest'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def base_url(self):
|
||||||
|
"""Auth endpoint."""
|
||||||
|
return self.get('base_url')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api_key(self):
|
||||||
|
"""The user's api key."""
|
||||||
|
return self.get_raw('api_key')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def test_locations(self):
|
||||||
|
"""Locations from which to test page load."""
|
||||||
|
return self.get('test_locations').split(',')
|
|
@ -0,0 +1,116 @@
|
||||||
|
# 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 time
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
class HeatClient(object):
|
||||||
|
|
||||||
|
def __init__(self, heat_url, token):
|
||||||
|
self.heat_url = heat_url
|
||||||
|
|
||||||
|
self.headers = {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-Auth-Token': token
|
||||||
|
}
|
||||||
|
|
||||||
|
def create_stack(self, yaml_path, stack_name, domain_name):
|
||||||
|
"""Creates a HEAT stack
|
||||||
|
|
||||||
|
:param yaml_path: path to the uaml file relative to endtoend directory
|
||||||
|
(eg. endtoend/heat_file.yaml)
|
||||||
|
:param stack_name: name of the heat stack containing the test site
|
||||||
|
:param domain_name: domain name to use for the deployed website
|
||||||
|
:returns: response to HEAT API call
|
||||||
|
"""
|
||||||
|
url = self.heat_url + '/stacks'
|
||||||
|
|
||||||
|
template_file = open(yaml_path, 'r+')
|
||||||
|
template = template_file.read()
|
||||||
|
template = template.replace('example.com', domain_name)
|
||||||
|
request_data = {
|
||||||
|
"stack_name": stack_name,
|
||||||
|
"disable_rollback": True,
|
||||||
|
"parameters": {},
|
||||||
|
"template": template,
|
||||||
|
"timeout_mins": 60
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(url,
|
||||||
|
data=json.dumps(request_data),
|
||||||
|
headers=self.headers)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def get_stack_details(self, stack_name):
|
||||||
|
"""Gets details of the stack
|
||||||
|
|
||||||
|
:param stack_name: name of the stack to be queried
|
||||||
|
:returns: response containing stack details
|
||||||
|
"""
|
||||||
|
url = self.heat_url + '/stacks/' + stack_name
|
||||||
|
response = requests.get(url, headers=self.headers)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def wait_for_stack_status(self, stack_name, status='CREATE_COMPLETE',
|
||||||
|
retry_timeout=6000, retry_interval=10):
|
||||||
|
"""Wait for the stack to reach the specified status
|
||||||
|
|
||||||
|
:param stack_name: name of the stack to be queried
|
||||||
|
:param status: desired stack status
|
||||||
|
:param retry_interval: how frequently to ping for status change(sec)
|
||||||
|
:param retry_interval: max number of seconds to wait for status change
|
||||||
|
:returns: None
|
||||||
|
"""
|
||||||
|
current_status = ''
|
||||||
|
start_time = int(time.time())
|
||||||
|
stop_time = start_time + retry_timeout
|
||||||
|
while current_status != status:
|
||||||
|
time.sleep(retry_interval)
|
||||||
|
stack_details = self.get_stack_details(stack_name=stack_name)
|
||||||
|
body = stack_details.json()
|
||||||
|
current_status = body['stack']['stack_status']
|
||||||
|
if (current_status == status):
|
||||||
|
return
|
||||||
|
current_time = int(time.time())
|
||||||
|
if current_time > stop_time:
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_server_ip(self, stack_name):
|
||||||
|
"""Get the cloud server ip for the stack_name
|
||||||
|
|
||||||
|
:param stack_name: name of the stack
|
||||||
|
:returns: ip of the server that is part of the stack
|
||||||
|
"""
|
||||||
|
stack_details = self.get_stack_details(stack_name)
|
||||||
|
stack_details = stack_details.json()
|
||||||
|
outputs = stack_details['stack']['outputs']
|
||||||
|
server_ip = [output['output_value'] for
|
||||||
|
output in outputs if
|
||||||
|
output['output_key'] == 'server_ip']
|
||||||
|
return server_ip[0]
|
||||||
|
|
||||||
|
def delete_stack(self, stack_name):
|
||||||
|
"""Delete specified stack
|
||||||
|
|
||||||
|
:param stack_name: name of the stack
|
||||||
|
:returns: DELETE response
|
||||||
|
"""
|
||||||
|
url = self.heat_url + '/stacks/' + stack_name
|
||||||
|
response = requests.delete(url, headers=self.headers)
|
||||||
|
return response
|
|
@ -0,0 +1,76 @@
|
||||||
|
# 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 time
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
class WebpageTestClient(object):
|
||||||
|
|
||||||
|
def __init__(self, wpt_url, api_key):
|
||||||
|
self.wpt_url = wpt_url
|
||||||
|
self.api_key = api_key
|
||||||
|
|
||||||
|
def start_test(self, access_url, test_location, runs=5):
|
||||||
|
"""Starts webpage test
|
||||||
|
|
||||||
|
:param access_url: the url which needs performance metrics
|
||||||
|
:param test_location: location to run the test on
|
||||||
|
:param runs: number of test runs
|
||||||
|
:returns: url to access the test details
|
||||||
|
"""
|
||||||
|
start_url = (self.wpt_url + '/runtest.php?url=' + access_url +
|
||||||
|
'&k=' + self.api_key + '&location=' + test_location +
|
||||||
|
'&runs=' + str(runs) + '&f=json')
|
||||||
|
response = requests.post(start_url)
|
||||||
|
test = response.json()
|
||||||
|
return test['data']['jsonUrl']
|
||||||
|
|
||||||
|
def get_test_details(self, test_url):
|
||||||
|
"""Gets test results in json format
|
||||||
|
|
||||||
|
:param test_url: the url pointing to json test results
|
||||||
|
:returns: test details json
|
||||||
|
"""
|
||||||
|
response = requests.get(test_url)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def wait_for_test_status(self, test_url, status='COMPLETE',
|
||||||
|
retry_timeout=6000, retry_interval=10):
|
||||||
|
|
||||||
|
"""Waits for the wpt test to be completed.
|
||||||
|
|
||||||
|
:param test_url: the url pointing to json test results
|
||||||
|
:param status: expected status from WPT server
|
||||||
|
200 (COMPLETE) indicates test is completed.
|
||||||
|
1XX means the test is still in progress.
|
||||||
|
And 4XX indicates some error.
|
||||||
|
"""
|
||||||
|
current_status = ''
|
||||||
|
start_time = int(time.time())
|
||||||
|
stop_time = start_time + retry_timeout
|
||||||
|
if status == 'COMPLETE':
|
||||||
|
status_code = 200
|
||||||
|
while current_status != status:
|
||||||
|
time.sleep(retry_interval)
|
||||||
|
test_details = self.get_test_details(test_url=test_url)
|
||||||
|
current_status = test_details['statusCode']
|
||||||
|
print('status', test_details)
|
||||||
|
if (current_status == status_code):
|
||||||
|
return
|
||||||
|
current_time = int(time.time())
|
||||||
|
if current_time > stop_time:
|
||||||
|
return
|
|
@ -0,0 +1,390 @@
|
||||||
|
heat_template_version: 2013-05-23
|
||||||
|
|
||||||
|
description: |
|
||||||
|
This is a Heat template to deploy a single Linux server running WordPress.
|
||||||
|
|
||||||
|
parameter_groups:
|
||||||
|
|
||||||
|
- label: Server Settings
|
||||||
|
parameters:
|
||||||
|
- server_hostname
|
||||||
|
- image
|
||||||
|
- flavor
|
||||||
|
|
||||||
|
- label: WordPress Settings
|
||||||
|
parameters:
|
||||||
|
- domain
|
||||||
|
- username
|
||||||
|
|
||||||
|
- label: rax-dev-params
|
||||||
|
# These are parameters that will not be displayed in the portal. The purpose
|
||||||
|
# of these parameters are for users who are developing or testing newer or
|
||||||
|
# different setups. If any of these parameters are changed, there is a good
|
||||||
|
# chance this stack will fail to properly deploy.
|
||||||
|
parameters:
|
||||||
|
- kitchen
|
||||||
|
- chef_version
|
||||||
|
- version
|
||||||
|
- prefix
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
|
||||||
|
# Server settings
|
||||||
|
server_hostname:
|
||||||
|
label: Server Name
|
||||||
|
description: Hostname to use for the server that's built.
|
||||||
|
type: string
|
||||||
|
default: CDN-Test-WordPress
|
||||||
|
constraints:
|
||||||
|
- length:
|
||||||
|
min: 1
|
||||||
|
max: 64
|
||||||
|
- allowed_pattern: "^[a-zA-Z][a-zA-Z0-9-]*$"
|
||||||
|
description: |
|
||||||
|
Must begin with a letter and contain only alphanumeric characters.
|
||||||
|
|
||||||
|
image:
|
||||||
|
label: Operating System
|
||||||
|
description: |
|
||||||
|
Required: Server image used for all servers that are created as a part of
|
||||||
|
this deployment.
|
||||||
|
type: string
|
||||||
|
default: Ubuntu 12.04 LTS (Precise Pangolin) (PVHVM)
|
||||||
|
constraints:
|
||||||
|
- allowed_values:
|
||||||
|
- Ubuntu 12.04 LTS (Precise Pangolin) (PVHVM)
|
||||||
|
description: Must be a supported operating system.
|
||||||
|
|
||||||
|
flavor:
|
||||||
|
label: Server Size
|
||||||
|
description: |
|
||||||
|
Required: Rackspace Cloud Server flavor to use. The size is based on the
|
||||||
|
amount of RAM for the provisioned server.
|
||||||
|
type: string
|
||||||
|
default: 4 GB General Purpose v1
|
||||||
|
constraints:
|
||||||
|
- allowed_values:
|
||||||
|
- 1 GB General Purpose v1
|
||||||
|
- 2 GB General Purpose v1
|
||||||
|
- 4 GB General Purpose v1
|
||||||
|
- 8 GB General Purpose v1
|
||||||
|
- 15 GB I/O v1
|
||||||
|
- 30 GB I/O v1
|
||||||
|
- 1GB Standard Instance
|
||||||
|
- 2GB Standard Instance
|
||||||
|
- 4GB Standard Instance
|
||||||
|
- 8GB Standard Instance
|
||||||
|
- 15GB Standard Instance
|
||||||
|
- 30GB Standard Instance
|
||||||
|
description: |
|
||||||
|
Must be a valid Rackspace Cloud Server flavor for the region you have
|
||||||
|
selected to deploy into.
|
||||||
|
|
||||||
|
# WordPress settings
|
||||||
|
domain:
|
||||||
|
label: Site Domain
|
||||||
|
description: Domain to be used with WordPress site
|
||||||
|
type: string
|
||||||
|
default: "example.com"
|
||||||
|
constraints:
|
||||||
|
- allowed_pattern: "^[a-zA-Z0-9.-]{1,255}.[a-zA-Z]{2,15}$"
|
||||||
|
description: Must be a valid domain name
|
||||||
|
|
||||||
|
version:
|
||||||
|
label: WordPress Version
|
||||||
|
description: Version of WordPress to install
|
||||||
|
type: string
|
||||||
|
default: "3.9.2"
|
||||||
|
constraints:
|
||||||
|
- allowed_values:
|
||||||
|
- "3.9.2"
|
||||||
|
|
||||||
|
# Optional Apache settings (SSL certs)
|
||||||
|
# ssl_private_key:
|
||||||
|
# description: Private SSL key
|
||||||
|
# type: string
|
||||||
|
# default: ""
|
||||||
|
# constraints:
|
||||||
|
# - allowed_pattern: "^(.){0,5000}$"
|
||||||
|
# description: "Key values must be under 5,000 characters"
|
||||||
|
|
||||||
|
# ssl_certificate:
|
||||||
|
# description: Public SSL key
|
||||||
|
# type: string
|
||||||
|
# default: ""
|
||||||
|
# constraints:
|
||||||
|
# - allowed_pattern: "^(.){0,5000}$"
|
||||||
|
# description: "Certificate values must be under 5,000 characters"
|
||||||
|
|
||||||
|
# ssl_intermediate_key:
|
||||||
|
# description: Intermediate SSL key
|
||||||
|
# type: string
|
||||||
|
# default: ""
|
||||||
|
# constraints:
|
||||||
|
# - allowed_pattern: "^(.){0,5000}$"
|
||||||
|
# description: "Intermediate values must be under 5,000 characters."
|
||||||
|
|
||||||
|
# Database and system user configuration
|
||||||
|
prefix:
|
||||||
|
label: Database Prefix
|
||||||
|
description: Prefix to use for WordPress database tables
|
||||||
|
type: string
|
||||||
|
default: wp_
|
||||||
|
constraints:
|
||||||
|
- allowed_pattern: "^[0-9a-zA-Z$_]{0,10}$"
|
||||||
|
description: |
|
||||||
|
Prefix must be shorter than 10 characters, and can only include
|
||||||
|
letters, numbers, $, and/or underscores.
|
||||||
|
|
||||||
|
database_name:
|
||||||
|
label: Database Name
|
||||||
|
description: WordPress database name
|
||||||
|
type: string
|
||||||
|
default: wordpress
|
||||||
|
constraints:
|
||||||
|
- allowed_pattern: "^[0-9a-zA-Z$_]{1,64}$"
|
||||||
|
description: |
|
||||||
|
Maximum length of 64 characters, may only contain letters, numbers, and
|
||||||
|
underscores.
|
||||||
|
|
||||||
|
username:
|
||||||
|
label: Username
|
||||||
|
description: "Username for system, database, and WordPress logins."
|
||||||
|
type: string
|
||||||
|
default: wp_user
|
||||||
|
constraints:
|
||||||
|
- allowed_pattern: "^[a-zA-Z0-9 _.@-]{1,16}$"
|
||||||
|
description: |
|
||||||
|
Must be shorter than 16 characters and may only contain alphanumeric
|
||||||
|
characters, ' ', '_', '.', '@', and/or '-'.
|
||||||
|
|
||||||
|
kitchen:
|
||||||
|
label: Kitchen URL
|
||||||
|
description: "URL for a git repo containing required cookbooks"
|
||||||
|
type: string
|
||||||
|
default: https://github.com/rackspace-orchestration-templates/wordpress-single.git
|
||||||
|
|
||||||
|
chef_version:
|
||||||
|
label: Chef Version
|
||||||
|
description: Version of chef client to use
|
||||||
|
type: string
|
||||||
|
default: 11.16.2
|
||||||
|
|
||||||
|
resources:
|
||||||
|
# Random password generation
|
||||||
|
database_password:
|
||||||
|
type: "OS::Heat::RandomString"
|
||||||
|
properties:
|
||||||
|
length: 16
|
||||||
|
sequence: lettersdigits
|
||||||
|
|
||||||
|
mysql_root_password:
|
||||||
|
type: "OS::Heat::RandomString"
|
||||||
|
properties:
|
||||||
|
length: 16
|
||||||
|
sequence: lettersdigits
|
||||||
|
|
||||||
|
mysql_repl_password:
|
||||||
|
type: "OS::Heat::RandomString"
|
||||||
|
properties:
|
||||||
|
length: 16
|
||||||
|
sequence: lettersdigits
|
||||||
|
|
||||||
|
mysql_debian_password:
|
||||||
|
type: "OS::Heat::RandomString"
|
||||||
|
properties:
|
||||||
|
length: 16
|
||||||
|
sequence: lettersdigits
|
||||||
|
|
||||||
|
# Random strings for WP salting
|
||||||
|
wp_auth:
|
||||||
|
type: "OS::Heat::RandomString"
|
||||||
|
properties:
|
||||||
|
length: 32
|
||||||
|
sequence: hexdigits
|
||||||
|
|
||||||
|
wp_logged_in:
|
||||||
|
type: "OS::Heat::RandomString"
|
||||||
|
properties:
|
||||||
|
length: 32
|
||||||
|
sequence: hexdigits
|
||||||
|
|
||||||
|
wp_nonce:
|
||||||
|
type: "OS::Heat::RandomString"
|
||||||
|
properties:
|
||||||
|
length: 32
|
||||||
|
sequence: hexdigits
|
||||||
|
|
||||||
|
wp_secure_auth:
|
||||||
|
type: "OS::Heat::RandomString"
|
||||||
|
properties:
|
||||||
|
length: 32
|
||||||
|
sequence: hexdigits
|
||||||
|
|
||||||
|
# SSH KEYS
|
||||||
|
sync_key:
|
||||||
|
type: "OS::Nova::KeyPair"
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
str_replace:
|
||||||
|
template: "%stack_id%-sync"
|
||||||
|
params:
|
||||||
|
"%stack_id%": { get_param: "OS::stack_id" }
|
||||||
|
save_private_key: true
|
||||||
|
|
||||||
|
ssh_key:
|
||||||
|
type: "OS::Nova::KeyPair"
|
||||||
|
properties:
|
||||||
|
name: { get_param: "OS::stack_id" }
|
||||||
|
save_private_key: true
|
||||||
|
|
||||||
|
# Server resources
|
||||||
|
wordpress_server:
|
||||||
|
type: "Rackspace::Cloud::Server"
|
||||||
|
properties:
|
||||||
|
name: { get_param: server_hostname }
|
||||||
|
flavor: { get_param: flavor }
|
||||||
|
image: { get_param: image }
|
||||||
|
key_name: { get_resource: ssh_key }
|
||||||
|
metadata:
|
||||||
|
rax-heat: { get_param: "OS::stack_id" }
|
||||||
|
|
||||||
|
# Chef resources
|
||||||
|
wordpress_setup:
|
||||||
|
type: "OS::Heat::ChefSolo"
|
||||||
|
depends_on: wordpress_server
|
||||||
|
properties:
|
||||||
|
username: root
|
||||||
|
private_key: { get_attr: [ssh_key, private_key] }
|
||||||
|
host: { get_attr: [wordpress_server, accessIPv4] }
|
||||||
|
kitchen: { get_param: kitchen }
|
||||||
|
chef_version: { get_param: chef_version }
|
||||||
|
node:
|
||||||
|
apache:
|
||||||
|
listen_ports: [8080]
|
||||||
|
timeout: 30
|
||||||
|
serversignature: "Off"
|
||||||
|
traceenable: "Off"
|
||||||
|
hollandbackup:
|
||||||
|
main:
|
||||||
|
backup_directory: "/var/lib/mysqlbackup"
|
||||||
|
mysqldump:
|
||||||
|
user: "root"
|
||||||
|
host: "localhost"
|
||||||
|
password: { get_attr: [mysql_root_password, value] }
|
||||||
|
lsyncd:
|
||||||
|
interval: 5
|
||||||
|
memcached:
|
||||||
|
listen: "127.0.0.1"
|
||||||
|
monit:
|
||||||
|
notify_email: 'root@localhost'
|
||||||
|
mail_format:
|
||||||
|
from: 'monit@localhost'
|
||||||
|
mysql:
|
||||||
|
bind_address: "127.0.0.1"
|
||||||
|
server_root_password: { get_attr: [mysql_root_password, value] }
|
||||||
|
server_repl_password: { get_attr: [mysql_repl_password, value] }
|
||||||
|
server_debian_password: { get_attr: [mysql_debian_password, value] }
|
||||||
|
remove_test_database: true
|
||||||
|
remove_anonymous_users: true
|
||||||
|
sysctl:
|
||||||
|
values:
|
||||||
|
fs.inotify.max_user_watches: 1000000
|
||||||
|
varnish:
|
||||||
|
version: "3.0"
|
||||||
|
listen_port: "80"
|
||||||
|
vsftpd:
|
||||||
|
ipaddress: ''
|
||||||
|
write_enable: true
|
||||||
|
local_umask: "002"
|
||||||
|
chroot_local_user: false
|
||||||
|
hide_ids: false
|
||||||
|
ssl_enable: true
|
||||||
|
ssl_ciphers: "AES256-SHA"
|
||||||
|
wordpress:
|
||||||
|
version: { get_param: version }
|
||||||
|
server_aliases: [{ get_param: domain }]
|
||||||
|
dir:
|
||||||
|
str_replace:
|
||||||
|
template: "/var/www/vhosts/%domain%"
|
||||||
|
params:
|
||||||
|
"%domain%": { get_param: domain }
|
||||||
|
db:
|
||||||
|
name: { get_param: database_name }
|
||||||
|
user: { get_param: username }
|
||||||
|
pass: { get_attr: [database_password, value] }
|
||||||
|
host: "127.0.0.1"
|
||||||
|
keys:
|
||||||
|
auth: { get_attr: [wp_auth, value] }
|
||||||
|
logged_in: { get_attr: [wp_logged_in, value] }
|
||||||
|
nonce_key: { get_attr: [wp_nonce, value] }
|
||||||
|
secure_auth_key: { get_attr: [wp_secure_auth, value] }
|
||||||
|
rax:
|
||||||
|
apache:
|
||||||
|
# ssl_private_key: { get_param: ssl_private_key }
|
||||||
|
# ssl_certificate: { get_param: ssl_certificate }
|
||||||
|
# ssl_intermediate_certs: { get_param: ssl_intermediate_certs }
|
||||||
|
domain: { get_param: domain }
|
||||||
|
lsyncd:
|
||||||
|
ssh:
|
||||||
|
private_key: { get_attr: [sync_key, private_key] }
|
||||||
|
packages:
|
||||||
|
- php5-imagick
|
||||||
|
varnish:
|
||||||
|
master_backend: "localhost"
|
||||||
|
wordpress:
|
||||||
|
admin_user: { get_param: username }
|
||||||
|
admin_pass: { get_attr: [database_password, value] }
|
||||||
|
user:
|
||||||
|
name: { get_param: username }
|
||||||
|
group: { get_param: username }
|
||||||
|
run_list: ["recipe[apt]",
|
||||||
|
"recipe[build-essential]",
|
||||||
|
"recipe[rax-wordpress::apache-prep]",
|
||||||
|
"recipe[sysctl::attribute_driver]",
|
||||||
|
"recipe[mysql::server]",
|
||||||
|
"recipe[rax-wordpress::mysql]",
|
||||||
|
"recipe[hollandbackup]",
|
||||||
|
"recipe[hollandbackup::mysqldump]",
|
||||||
|
"recipe[hollandbackup::main]",
|
||||||
|
"recipe[hollandbackup::backupsets]",
|
||||||
|
"recipe[hollandbackup::cron]",
|
||||||
|
"recipe[rax-wordpress::x509]",
|
||||||
|
"recipe[memcached]",
|
||||||
|
"recipe[php]",
|
||||||
|
"recipe[rax-install-packages]",
|
||||||
|
"recipe[wordpress]",
|
||||||
|
"recipe[rax-wordpress::wp-setup]",
|
||||||
|
"recipe[rax-wordpress::user]",
|
||||||
|
"recipe[rax-wordpress::memcache]",
|
||||||
|
"recipe[lsyncd]",
|
||||||
|
"recipe[vsftpd]",
|
||||||
|
"recipe[rax-wordpress::vsftpd]",
|
||||||
|
"recipe[varnish::repo]",
|
||||||
|
"recipe[varnish]",
|
||||||
|
"recipe[rax-wordpress::apache]",
|
||||||
|
"recipe[rax-wordpress::varnish]",
|
||||||
|
"recipe[rax-wordpress::firewall]",
|
||||||
|
"recipe[rax-wordpress::vsftpd-firewall]",
|
||||||
|
"recipe[rax-wordpress::lsyncd]"]
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
private_key:
|
||||||
|
description: SSH Private Key
|
||||||
|
value: { get_attr: [ssh_key, private_key] }
|
||||||
|
|
||||||
|
server_ip:
|
||||||
|
description: Server IP
|
||||||
|
value: { get_attr: [wordpress_server, accessIPv4] }
|
||||||
|
|
||||||
|
wordpress_user:
|
||||||
|
description: WordPress User
|
||||||
|
value: { get_param: username }
|
||||||
|
|
||||||
|
wordpress_password:
|
||||||
|
description: WordPress Password
|
||||||
|
value: { get_attr: [database_password, value] }
|
||||||
|
|
||||||
|
mysql_root_password:
|
||||||
|
description: MySQL Root Password
|
||||||
|
value: { get_attr: [mysql_root_password, value] }
|
|
@ -0,0 +1,27 @@
|
||||||
|
#=============================================================================
|
||||||
|
# Configuration file to execute End To End tests.
|
||||||
|
#=============================================================================
|
||||||
|
|
||||||
|
[auth]
|
||||||
|
user_name={user name of the cloud account}
|
||||||
|
api_key={api key for this user name}
|
||||||
|
base_url=https://identity.api.rackspacecloud.com/v2.0
|
||||||
|
|
||||||
|
[test_configuration]
|
||||||
|
provider_validation=False
|
||||||
|
status_check_retry_interval=2
|
||||||
|
status_check_retry_timeout=30
|
||||||
|
|
||||||
|
[orchestration]
|
||||||
|
base_url=https://iad.orchestration.api.rackspacecloud.com/v1/{project_id}/
|
||||||
|
yaml_path=endtoend/wordpress-single.yaml
|
||||||
|
|
||||||
|
[poppy]
|
||||||
|
base_url=http://0.0.0.0:8888
|
||||||
|
flavor=standard
|
||||||
|
|
||||||
|
[webpagetest]
|
||||||
|
base_url=http://www.webpagetest.org/
|
||||||
|
api_key={api key for webpagetest instance}
|
||||||
|
# GET http://www.webpagetest.org/getLocations.php - Use <id> tag
|
||||||
|
test_locations=Wellington:Chrome, Indore:Firefox, Stockholm:Safari, Dulles:Firefox, Miami:Chrome
|
|
@ -8,3 +8,5 @@ openstack.nose_plugin
|
||||||
oslotest
|
oslotest
|
||||||
requests
|
requests
|
||||||
testtools
|
testtools
|
||||||
|
python-heatclient
|
||||||
|
beautifulsoup4
|
||||||
|
|
2
tox.ini
2
tox.ini
|
@ -18,7 +18,7 @@ deps = -r{toxinidir}/requirements/requirements.txt
|
||||||
-r{toxinidir}/tests/test-requirements.txt
|
-r{toxinidir}/tests/test-requirements.txt
|
||||||
commands = pip install git+https://github.com/stackforge/opencafe.git#egg=cafe
|
commands = pip install git+https://github.com/stackforge/opencafe.git#egg=cafe
|
||||||
pip install git+https://github.com/tonytan4ever/python-maxcdn.git#egg=maxcdn
|
pip install git+https://github.com/tonytan4ever/python-maxcdn.git#egg=maxcdn
|
||||||
nosetests {posargs:--exclude=api --nologcapture}
|
nosetests {posargs:--exclude=api --exclude=endtoend --nologcapture}
|
||||||
|
|
||||||
[tox:jenkins]
|
[tox:jenkins]
|
||||||
downloadcache = ~/cache/pip
|
downloadcache = ~/cache/pip
|
||||||
|
|
Loading…
Reference in New Issue