├── README.md
├── ansible-playbooks
├── inventory
│ ├── group_vars
│ │ └── all-sample.yaml
│ └── terraform_inv.py
├── roles
│ ├── common
│ │ └── tasks
│ │ │ └── main.yaml
│ ├── db
│ │ └── tasks
│ │ │ ├── configure-replication-master.yaml
│ │ │ ├── configure-replication-slave.yaml
│ │ │ ├── create-wordpress-db.yaml
│ │ │ └── main.yaml
│ └── web
│ │ ├── files
│ │ ├── db-config.php
│ │ ├── db.php
│ │ ├── default.conf
│ │ ├── wordpress.config
│ │ └── wp-config.php
│ │ └── tasks
│ │ ├── configure-nginx.yaml
│ │ ├── configure-wordpress.yaml
│ │ └── main.yaml
└── site.yaml
├── cis.tf
├── cloud-init-dbtier.tf
├── cloud-init-webapptier.tf
├── compute.tf
├── docs
├── ansible.md
├── images
│ ├── application-data-flow.png
│ ├── browser-language.png
│ ├── customize.png
│ ├── infrastructure-architecture.png
│ ├── install-wordpress.png
│ └── log-in.png
├── terraform.md
└── vpn.md
├── lbaas.tf
├── main.tf
├── network-acls.tf
├── outputs.tf
├── provider.tf
├── securitygroups.tf
├── variables.tf
└── vpn.tf
/README.md:
--------------------------------------------------------------------------------
1 | # Deploying a n-Tier Web App in a Virtual Private Cloud using Terraform & Ansible
2 |
3 | ## Purpose
4 |
5 | The purpose of this project is to demonstrate, through the use of a reuseable asset, the concept of [Infrastructure as code](https://en.wikipedia.org/wiki/Infrastructure_as_code)
6 | and how it can enable the ability to automate deployment facilitating a more consistent and faster development, testing, and deployment of workloads into a cloud, using the
7 | [IBM Cloud VPC Infrastructure](https://www.ibm.com/cloud/vpc), [HashiCorp's Terraform](https://www.terraform.io/), and [Red Hat's Ansible](https://www.redhat.com/en/technologies/management/ansible).
8 |
9 | A [n-tier](https://en.wikipedia.org/wiki/Multitier_architecture) architecture was chosen as a typical cloud workload for this example. A n-tier architecture separates the web / application and data tiers
10 | by placing them into separate sub-networks which are logically isolated using virtual network security constructs which can be defined and configured via an API. [WordPress](https://wordpress.com),
11 | a popular web, blog and e-commerce platform and [MySQL](https://www.mysql.com/), a typical open source database, installed on top of a [LAMP stack](https://en.wikipedia.org/wiki/LAMP) were chosen
12 | as the core software stack because to their simplicity and broad acceptance. [Nginx](https://www.nginx.com/) and [Nginx Unit](https://www.nginx.com/products/nginx-unit/) were chosen as the Web Server
13 | and Application Servers respectively.
14 |
15 | The main objectives of this project is to educate enterprise DevOps users and system administrators on how to leverage both the features of [IBM Cloud VPC Infrastructure](https://cloud.ibm.com/docs/vpc?topic=vpc-about-vpc)
16 | as well as how to use the [IBM Cloud Terraform Provider](https://github.com/IBM-Cloud/terraform-provider-ibm) and Ansible to deploy and fully configure a working n-tier application.
17 |
18 | This automated approach leveraged previous [Solution Tutorials - Highly Available & Scalable Web App](https://cloud.ibm.com/docs/tutorials?topic=solution-tutorials-highly-available-and-scalable-web-application#use-virtual-servers-to-build-highly-available-and-scalable-web-app) documentation.
19 |
20 | High Level Architecture
21 |
22 | 1. Infrastructure
23 | - Public Cloud isolation using a VPC
24 | - RFC1918 private bring-your-own-IP addresses
25 | - Application and data layers deployed on isolated subnets accross different availability zones
26 | - Network isolation defined logically using Security Groups and ACLs
27 | - Global DDOS and Global Load Balancing
28 | - VPN-as-a-Service to establish remote secure connectivity between on-pream and the VPC
29 | - SysDig & LogDNA for infrastructure and application monitoring
30 |
31 | 2. Application
32 | - A horizontally scaleable web application deployed into a two different availability zones
33 | - Multiple database servers across two availability zones
34 | - A master/slave data replication strategy across availability zones
35 |
36 | ## VPC Architecture
37 | Below is the IBM Virtual Private Cloud (VPC) architecture of the solution showing public isolation for both Application (through a Load Balancer) and data.
38 |
39 | ### Infrastructure Architecture
40 | 
41 |
42 | ### Application Architecture
43 | 
44 |
45 | #### *Not depicted in drawings*
46 | - VPNaaS or any VPN Connections
47 | - Cloud Internet Services (GLB function or DNS)
48 | - Management Flows
49 |
50 | ## Assumptions and Limitations
51 |
52 | - This documentation is meant to be used for illustrative and learning purposes primarily.
53 | - This document expects the reader to have a basic level of understanding of network infrastructure, Terraform, Ansible and application deployment on a Linux environment.
54 | - The solution will implement HTTP only for simplicity.
55 | - A MySQL database server was implemented on Infrastructure versus as-a-service to illustrate both the ability to define logical tiers between subnets as well
56 | as to show the ability to automate deployment and configuration tasks.
57 | - Ansible is used for all post configuration tasks.
58 |
59 |
60 | ## VPC Functional Coverage
61 | | Function | Demonstrated | Notes |
62 | | -------- | ------ | ----- |
63 | | VPC | :white_check_mark: | |
64 | | Terraform | :white_check_mark: | |
65 | | Ansible | :white_check_mark: | |
66 | | Resource Groups | :white_check_mark: | Assigned, but assumed to be created already. |
67 | | Access Groups | :white_check_mark: | Inherited, but assumed to already be created |
68 | | Subnets | :white_check_mark: | |
69 | | Private (RFC1918) IP (BYOIP) | :white_check_mark: | |
70 | | ACLs | :white_check_mark: | |
71 | | Security Groups | :white_check_mark: | |
72 | | Virtual Server Instance (VSI) | :white_check_mark: | |
73 | | Cloud-init | :white_check_mark: | Package installation and configuration beyond base OS image. |
74 | | Secondary Storage | | Not used in this scenario |
75 | | Multiple Network Interfaces in VSI | :white_check_mark: | |
76 | | Load Balancer as a Service | :white_check_mark: | Public Only |
77 | | Floating IPv4 | | Not required for workload. |
78 | | Public Gateway | :white_check_mark: | |
79 | | VPNaaS | :white_check_mark: | |
80 | | Cloud Internet Services (CIS) | :white_check_mark: | GLB configured for illustrative purposes with DDOS proxy |
81 | | IBM Cloud Monitoring with Sysdig | :white_check_mark: | Public endpoint used |
82 | | IBM Cloud Log Analysis with LogDNA | :white_check_mark: | Public endpoint Used
83 |
84 | ### System Requirements
85 |
86 | #### Operating system
87 |
88 | | Tier | Operating system |
89 | | ------------- | ------------- |
90 | | Web Server & Application | Ubuntu 16.04 |
91 | | Data | Ubuntu 16.04 |
92 |
93 | #### Hardware
94 |
95 | | Tier | Type | Profile |
96 | | ------------- | ------------- | ------- |
97 | | Web Server and Application | VSI | cc1-2x4 |
98 | | Data| VSI | bc1-4x16 |
99 |
100 | #### Runtime Services
101 |
102 | | Service Name | Demonstrated | Notes
103 | | ------------ | ------------ | -----
104 | | Cloud Internet Services (CIS) GLB | :white_check_mark: | GLB configured for illustrative purposes with DDOS proxy. Alternatively a CNAME could have been used to publish the application URL. |
105 | | IBM Cloud Monitoring with Sysdig | :white_check_mark: | Public endpoint used |
106 | | IBM Cloud Log Analysis with LogDNA | :white_check_mark: | Public endpoint Used |
107 | | IBM Cloud Databases | | A VSI based instance of MySQL was chosen instead of a Database-as-a-Service capability to illustrate both the ability to create logial network constructs and security and the ability to use Terraform and Ansible to configure the environment.|
108 |
109 | ## Documented Steps
110 |
111 | ### Prerequisites
112 |
113 | The following software needs to be installed:
114 | 1. Terraform 0.11 or greater
115 | 2. [IBM Cloud Terraform Provider version 0.17.1](https://github.com/IBM-Cloud/terraform-provider-ibm)
116 | 2. Ansible 2.8
117 |
118 | The following must be configured prior to running Terraform / Ansible
119 | 1. A Public SSH key as described in [SSH Keys](https://cloud.ibm.com/docs/vpc-on-classic-vsi?topic=vpc-on-classic-vsi-ssh-keys#ssh-keys).
120 | 2. A resource group exists and is referenced in configuration as described in [Managing resource groups](https://cloud.ibm.com/docs/resources?topic=resources-rgs#rgs)
121 | 3. User permissions and the required access as described in [Managing user permissions for VPC resources](https://cloud.ibm.com/docs/vpc-on-classic?topic=vpc-on-classic-managing-user-permissions-for-vpc-resources)
122 |
123 | ### Deploy VPC Infrastructure using Terraform & Ansible
124 |
125 | 1. [Deploy Infrastructure using Terraform](docs/terraform.md)
126 | 2. [Establish site-to-site VPN](docs/vpn.md)
127 | 3. [Configure Application Layer using Ansible](docs/ansible.md)
128 |
129 |
130 | ## Additional Documentation Provided
131 |
132 | Useful links for Terraform and Ansible
133 |
134 | [Terraform Documentation](https://www.terraform.io/docs/index.html)
135 |
136 | [The IBM Cloud Provider for Terraform Documentation](https://ibm-cloud.github.io/tf-ibm-docs/v0.17.1/)
137 |
138 | [Ansible Documentation](https://docs.ansible.com/ansible/latest/index.html)
139 |
140 |
141 | Useful links for IBM Cloud VPC documentation.
142 |
143 | [Getting started with IBM Cloud Virtual Private Cloud](https://cloud.ibm.com/docs/vpc-on-classic?topic=vpc-on-classic-getting-started)
144 |
145 | [Assigning role-based access to VPC resources](https://cloud.ibm.com/docs/vpc-on-classic?topic=vpc-on-classic-assigning-role-based-access-to-vpc-resources)
146 |
147 | [IBM Cloud CLI for VPC Reference](https://cloud.ibm.com/docs/vpc-on-classic?topic=vpc-infrastructure-cli-plugin-vpc-reference)
148 |
149 | [VPC API](https://cloud.ibm.com/apidocs/vpc-on-classic)
150 |
151 | [IBM Cloud Virtual Private Cloud API error messages](https://cloud.ibm.com/docs/vpc-on-classic?topic=vpc-on-classic-rias-error-messages)
152 |
153 |
--------------------------------------------------------------------------------
/ansible-playbooks/inventory/group_vars/all-sample.yaml:
--------------------------------------------------------------------------------
1 | # Update passwords and API keys
2 |
3 | dbpassword: securepassw0rd
4 | logdna_key: logdna key goes here
5 | sysdig_key: sysdig key goes here
--------------------------------------------------------------------------------
/ansible-playbooks/inventory/terraform_inv.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Terraform-Ansible dynamic inventory for IBM Cloud VPC Infrastructure
4 | # Copyright (c) 2019
5 | #
6 | ti_version = '1.0'
7 | # Based on dynamic inventory for IBM Cloud from steve_strutt@uk.ibm.com
8 | # 05-16-2019 - 1.0 - Extended for use with the IBM VPC version 0.17.1 TF
9 | #
10 | # Licensed under the Apache License, Version 2.0 (the "License");
11 | # you may not use this file except in compliance with the License.
12 | # You may obtain a copy of the License at
13 | # http://www.apache.org/licenses/LICENSE-2.0
14 | # Unless required by applicable law or agreed to in writing, software
15 | # distributed under the License is distributed on an "AS IS" BASIS,
16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | # See the License for the specific language governing permissions and
18 | # limitations under the License.
19 | #
20 | # Can be used alongside static inventory files in the same directory
21 | #
22 | # terraform_inv.ini file in the same directory as this script, points to the
23 | # location of the terraform.tfstate file to be inventoried. Tags and will be
24 | # created based on security group membership and zone.
25 | #
26 | # [TFSTATE]
27 | # TFSTATE_FILE = /usr/share/terraform/ibm/Demoapp2x/terraform.tfstate
28 | ##
29 | # Validate correct execution:
30 | # With supplied test files - './terraform_inv.py -t ../tr_test_files/terraform.tfstate'
31 | # With ini file './terraform.py'
32 | # Successful execution returns groups with lists of hosts and _meta/hostvars with a detailed
33 | # host listing.
34 | #
35 | # Validate successful operation with ansible:
36 | # With - 'ansible-inventory -i inventory --list'
37 | #
38 | # Resources imported into Ansible
39 | # ibm_is_instance
40 | #
41 | # Groups created for each availability zone, and security group.
42 | # Security group groups extract the middle section between "-" of the security group name
43 | # in the format: vpcname-tier-securitygroup
44 | #
45 | # TF Output variables are extracted and stored under all vars.
46 |
47 | import json, configparser, os
48 | from collections import defaultdict
49 | from argparse import ArgumentParser
50 |
51 |
52 | def parse_params():
53 | parser = ArgumentParser('IBM Cloud Terraform inventory')
54 | parser.add_argument('--list', action='store_true', default=True, help='List Terraform hosts')
55 | parser.add_argument('--tfstate', '-t', action='store', dest='tfstate', help='Terraform state file in current or specified directory (terraform.tfstate default)')
56 | parser.add_argument('--version', '-v', action='store_true', help='Show version')
57 | args = parser.parse_args()
58 | # read location of terrafrom state file from ini if it exists
59 | if not args.tfstate:
60 | dirpath = os.getcwd()
61 | print ()
62 | config = configparser.ConfigParser()
63 | ini_file = 'terraform_inv.ini'
64 | try:
65 | # attempt to open ini file first. Only proceed if found
66 | # assume execution from the ansible playbook directory
67 | filepath = dirpath + "/inventory/" + ini_file
68 | open(filepath)
69 |
70 | except FileNotFoundError:
71 | try:
72 | # If file is not found it may be because command is executed
73 | # in inventory directory
74 | filepath = dirpath + "/" + ini_file
75 | open(filepath)
76 |
77 | except FileNotFoundError:
78 | raise Exception("Unable to find or open specified ini file")
79 | else:
80 | config.read(filepath)
81 | else:
82 | config.read(filepath)
83 |
84 | config.read(filepath)
85 | tf_file = config['TFSTATE']['TFSTATE_FILE']
86 | tf_file = os.path.expanduser(tf_file)
87 | args.tfstate = tf_file
88 |
89 | return args
90 |
91 |
92 | def get_tfstate(filename):
93 | return json.load(open(filename))
94 |
95 | def parse_state(tf_source, prefix, sep='.'):
96 | for key, value in list(tf_source.items()):
97 | try:
98 | curprefix, rest = key.split(sep, 1)
99 | except ValueError:
100 | continue
101 | if curprefix != prefix or rest == '#':
102 | continue
103 |
104 | yield rest, value
105 |
106 |
107 | def parse_attributes(tf_source, prefix, sep='.'):
108 | attributes = defaultdict(dict)
109 | for key, value in parse_state(tf_source, prefix, sep):
110 | index, key = key.split(sep, 1)
111 | attributes[index][key] = value
112 |
113 | return list(attributes.values())
114 |
115 |
116 | def parse_dict(tf_source, prefix, sep='.'):
117 | return dict(parse_state(tf_source, prefix, sep))
118 |
119 | def parse_list(tf_source, prefix, sep='.'):
120 | return [value for _, value in parse_state(tf_source, prefix, sep)]
121 |
122 | class TerraformInventory:
123 | def __init__(self):
124 | self.args = parse_params()
125 | if self.args.version:
126 | print(ti_version)
127 | elif self.args.list:
128 | print(self.list_all())
129 |
130 | def list_all(self):
131 | tf_hosts = []
132 | vars = {}
133 | hosts_vars = {}
134 | attributes = {}
135 | groups = {}
136 | groups_json = {}
137 | inv_output = {}
138 | group_hosts = defaultdict(list)
139 |
140 | for name, attributes, groups in self.get_tf_instances():
141 | tf_hosts.append(name)
142 | hosts_vars[name] = attributes
143 | for group in list(groups):
144 | group_hosts[group].append(name)
145 |
146 | inv_output["All"] = {
147 | "hosts": tf_hosts,
148 | "vars": self.get_tf_output()
149 | }
150 |
151 | inv_output["_meta"] = {'hostvars': hosts_vars}
152 |
153 | for group in group_hosts:
154 | inv_output[group] = {'hosts': group_hosts[group]}
155 |
156 | return json.dumps(inv_output, indent=2)
157 |
158 | def get_tf_output(self):
159 | ################################################
160 | ## Get Terraform Output variables
161 | ################################################
162 |
163 | tfstate = get_tfstate(self.args.tfstate)
164 | vars = {}
165 | for module in tfstate['modules']:
166 | for key, value in module['outputs'].items():
167 | vars.update({key: value["value"]})
168 | return vars
169 |
170 | def get_tf_security_group_name(self, id):
171 | ################################################
172 | ## Get security groups
173 | ################################################
174 |
175 | tfstate = get_tfstate(self.args.tfstate)
176 | security_groups = {}
177 | for module in tfstate['modules']:
178 | for resource in module['resources'].values():
179 | if resource['type'] == 'ibm_is_security_group' :
180 | tf_attrib = resource['primary']['attributes']
181 | if tf_attrib["id"] == id:
182 | return tf_attrib["name"]
183 |
184 | def get_tf_vpc(self, id):
185 | ################################################
186 | ## Get VPC name from ID
187 | ################################################
188 |
189 | tfstate = get_tfstate(self.args.tfstate)
190 | for module in tfstate['modules']:
191 | for resource in module['resources'].values():
192 | if resource['type'] == 'ibm_is_vpc':
193 | tf_attrib = resource['primary']['attributes']
194 | if tf_attrib["id"] == id:
195 | return tf_attrib["name"]
196 |
197 | def get_tf_subnet_name(self, id):
198 | ################################################
199 | ## Get Subnet Name
200 | ################################################
201 |
202 | tfstate = get_tfstate(self.args.tfstate)
203 | for module in tfstate['modules']:
204 | for resource in module['resources'].values():
205 | if resource['type'] == 'ibm_is_subnet':
206 | tf_attrib = resource['primary']['attributes']
207 | if tf_attrib["id"] == id:
208 | return tf_attrib["name"]
209 |
210 |
211 | def get_tf_instances(self):
212 |
213 | tfstate = get_tfstate(self.args.tfstate)
214 |
215 | for module in tfstate['modules']:
216 | for resource in module['resources'].values():
217 | if resource['type'] == 'ibm_is_instance':
218 |
219 | tf_attrib = resource['primary']['attributes']
220 | id = tf_attrib['id']
221 |
222 | name = tf_attrib['name']
223 |
224 | # Get Security Group ID, and derive name
225 | security_group_id = 0
226 | for key, value in tf_attrib.items():
227 | if "primary_network_interface.0.security_groups." in key:
228 | security_group_id = value
229 |
230 | security_group = self.get_tf_security_group_name(security_group_id)
231 |
232 | # Remove VPC prefix + "securitygroup" from name and change - to _ characters
233 | tags = "group:" +security_group.split("-")[1].translate({ord(c): "_" for c in '-'})
234 |
235 | attributes = {
236 | 'id': id,
237 | 'subnet': self.get_tf_subnet_name(tf_attrib["primary_network_interface.0.subnet"]),
238 | 'securitygroup': security_group,
239 | 'vpc': self.get_tf_vpc(tf_attrib["vpc"]),
240 | 'zone': tf_attrib['zone'],
241 | 'ram': tf_attrib['memory'],
242 | 'cpu': tf_attrib['cpu.0.cores'],
243 | 'profile': tf_attrib['profile'],
244 | 'ansible_host': tf_attrib['primary_network_interface.0.primary_ipv4_address'],
245 | 'ansible_ssh_user': 'root',
246 | 'provider': 'ibm',
247 | 'tags': tags
248 | }
249 |
250 | # create groups based on tags (security group)
251 | value = attributes["tags"]
252 | group = []
253 | try:
254 | curprefix, rest = value.split(":", 1)
255 | except ValueError:
256 | continue
257 | if curprefix != "group" :
258 | continue
259 | group.append(rest)
260 |
261 | # create group based on zone, remove any invalid group characters
262 | group.append(tf_attrib['zone'].translate({ord(c): None for c in '-'}))
263 |
264 |
265 | yield name, attributes, group
266 |
267 |
268 |
269 | if __name__ == '__main__':
270 |
271 |
272 | TerraformInventory()
273 |
--------------------------------------------------------------------------------
/ansible-playbooks/roles/common/tasks/main.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # Common stuff goes here
3 |
4 | - name: Add LogDNA repo and install
5 | shell: |
6 | echo "deb https://repo.logdna.com stable main" | sudo tee /etc/apt/sources.list.d/logdna.list
7 | wget -O- https://repo.logdna.com/logdna.gpg | sudo apt-key add -
8 | apt-get update
9 | apt-get install logdna-agent < "/dev/null"
10 | become: yes
11 |
12 | - name: Configure LogDNA
13 | shell: |
14 | logdna-agent -k {{ logdna_key }}
15 | logdna-agent -s LOGDNA_APIHOST=api.us-south.logging.cloud.ibm.com
16 | logdna-agent -s LOGDNA_LOGHOST=logs.us-south.logging.cloud.ibm.com
17 | become: yes
18 |
19 | - name: Set LogDNA to autostart
20 | shell: systemctl enable logdna-agent
21 | become: yes
22 |
--------------------------------------------------------------------------------
/ansible-playbooks/roles/db/tasks/configure-replication-master.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # Configure the master mysql database for replication
3 |
4 | - name: Add replication settings to server-id my.cnf for master
5 | ini_file:
6 | path: /etc/mysql/my.cnf
7 | section: mysqld
8 | option: server-id
9 | value: "1"
10 | backup: yes
11 |
12 | - name: Add replication settings to log_bin my.cnf for master
13 | ini_file:
14 | path: /etc/mysql/my.cnf
15 | section: mysqld
16 | option: log_bin
17 | value: /var/log/mysql/mysql-bin.log
18 | backup: yes
19 |
20 | - name: Add replication settings to binlog_do_db my.cnf for master
21 | ini_file:
22 | path: /etc/mysql/my.cnf
23 | section: mysqld
24 | option: binlog_do_db
25 | value: wordpress
26 | backup: yes
27 |
28 | - name: Add sql_mode
29 | ini_file:
30 | path: /etc/mysql/my.cnf
31 | section: mysqld
32 | option: sql_mode
33 | value: NO_ENGINE_SUBSTITUTION
34 | backup: yes
35 |
36 | - name: Create slave user
37 | mysql_user:
38 | login_user: root
39 | login_unix_socket: /var/run/mysqld/mysqld.sock
40 | name: slave
41 | password: "{{ dbpassword}}"
42 | priv: '*.*:ALL,GRANT'
43 | host: '%'
44 | state: present
45 |
46 | - name: restart mysql
47 | shell: service mysql restart
--------------------------------------------------------------------------------
/ansible-playbooks/roles/db/tasks/configure-replication-slave.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # Configure the master mysql database for replication
3 |
4 | - name: Add replication settings to server-id my.cnf for slave
5 | ini_file:
6 | path: /etc/mysql/my.cnf
7 | section: mysqld
8 | option: server-id
9 | value: "2"
10 | backup: yes
11 |
12 | - name: Add replication settings to relay-log my.cnf for master
13 | ini_file:
14 | path: /etc/mysql/my.cnf
15 | section: mysqld
16 | option: relay-log
17 | value: /var/log/mysql/mysql-relay-bin.log
18 | backup: yes
19 |
20 | - name: Add replication settings to log_bin my.cnf for master
21 | ini_file:
22 | path: /etc/mysql/my.cnf
23 | section: mysqld
24 | option: log_bin
25 | value: /var/log/mysql/mysql-bin.log
26 | backup: yes
27 |
28 | - name: Add replication settings to binlog_do_db my.cnf for master
29 | ini_file:
30 | path: /etc/mysql/my.cnf
31 | section: mysqld
32 | option: binlog_do_db
33 | value: wordpress
34 | backup: yes
35 |
36 | - name: Add sql_mode
37 | ini_file:
38 | path: /etc/mysql/my.cnf
39 | section: mysqld
40 | option: sql_mode
41 | value: NO_ENGINE_SUBSTITUTION
42 | backup: yes
43 |
44 | - name: restart mysql
45 | shell: service mysql restart
46 |
47 | - name: Create slave user
48 | mysql_user:
49 | login_user: root
50 | login_unix_socket: /var/run/mysqld/mysqld.sock
51 | name: slave
52 | password: "{{ dbpassword}}"
53 | priv: '*.*:ALL,GRANT'
54 | host: '%'
55 | state: present
56 |
57 | - name: Stop Slave
58 | mysql_replication:
59 | login_user: root
60 | login_unix_socket: /var/run/mysqld/mysqld.sock
61 | mode: stopslave
62 |
63 | - name: Configure Slave Replication
64 | mysql_replication:
65 | login_user: root
66 | login_unix_socket: /var/run/mysqld/mysqld.sock
67 | mode: changemaster
68 | master_user: slave
69 | master_password: "{{ dbpassword }}"
70 | master_host: "{{ master_db }}"
71 |
72 | - name: Start Slave
73 | mysql_replication:
74 | login_user: root
75 | login_unix_socket: /var/run/mysqld/mysqld.sock
76 | mode: startslave
77 |
--------------------------------------------------------------------------------
/ansible-playbooks/roles/db/tasks/create-wordpress-db.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # Basic Setup of MySQL
3 | - name: Change mysqld.cnf to listen on all interfaces
4 | shell: |
5 | sed -i "s/^\(bind-address\s*=\s*\).*\$/\10.0.0.0/" /etc/mysql/mysql.conf.d/mysqld.cnf
6 | become: yes
7 |
8 | - name: Create wordpress database on the server
9 | mysql_db:
10 | name: wordpress
11 | login_user: root
12 | login_unix_socket: /var/run/mysqld/mysqld.sock
13 | state: present
14 |
15 | - name: Create wordpress user
16 | mysql_user:
17 | login_user: root
18 | login_unix_socket: /var/run/mysqld/mysqld.sock
19 | name: wpuser
20 | password: "{{ dbpassword }}"
21 | priv: 'wordpress.*:ALL,GRANT'
22 | host: '%'
23 | state: present
24 |
25 | - name: Add logDNA tag
26 | shell: logdna-agent -t database
27 | become: yes
28 |
29 | - name: restart LogDNA
30 | shell: service logdna-agent restart
31 | become: yes
32 |
33 | - name: Install & Configure sysdig on DB server
34 | shell: |
35 | curl -sL https://ibm.biz/install-sysdig-agent | sudo bash -s -- --access_key {{ sysdig_key }} -c ingest.us-south.monitoring.cloud.ibm.com --collector_port 6443 --secure true -ac "sysdig_capture_enabled: false" --tags role:database,location:{{ zone }}
36 | become: yes
--------------------------------------------------------------------------------
/ansible-playbooks/roles/db/tasks/main.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # Install & Configure mySQL for wordpress
3 |
4 | - include: create-wordpress-db.yaml
5 | - include: configure-replication-master.yaml
6 | when: zone == "us-south-1"
7 | - include: configure-replication-slave.yaml
8 | when: zone == "us-south-2"
9 |
--------------------------------------------------------------------------------
/ansible-playbooks/roles/web/files/db-config.php:
--------------------------------------------------------------------------------
1 | queries. It is not
37 | * a constant because you might want to use it momentarily.
38 | * Default: false
39 | */
40 | $wpdb->save_queries = false;
41 |
42 | /**
43 | * persistent (bool)
44 | * This determines whether to use mysql_connect or mysql_pconnect. The effects
45 | * of this setting may vary and should be carefully tested.
46 | * Default: false
47 | */
48 | $wpdb->persistent = false;
49 |
50 | /**
51 | * max_connections (int)
52 | * This is the number of mysql connections to keep open. Increase if you expect
53 | * to reuse a lot of connections to different servers. This is ignored if you
54 | * enable persistent connections.
55 | * Default: 10
56 | */
57 | $wpdb->max_connections = 10;
58 |
59 | /**
60 | * check_tcp_responsiveness
61 | * Enables checking TCP responsiveness by fsockopen prior to mysql_connect or
62 | * mysql_pconnect. This was added because PHP's mysql functions do not provide
63 | * a variable timeout setting. Disabling it may improve average performance by
64 | * a very tiny margin but lose protection against connections failing slowly.
65 | * Default: true
66 | */
67 | $wpdb->check_tcp_responsiveness = true;
68 |
69 | /** Configuration Functions **/
70 |
71 | /**
72 | * $wpdb->add_database( $database );
73 | *
74 | * $database is an associative array with these parameters:
75 | * host (required) Hostname with optional :port. Default port is 3306.
76 | * user (required) MySQL user name.
77 | * password (required) MySQL user password.
78 | * name (required) MySQL database name.
79 | * read (optional) Whether server is readable. Default is 1 (readable).
80 | * Also used to assign preference. See "Network topology".
81 | * write (optional) Whether server is writable. Default is 1 (writable).
82 | * Also used to assign preference in multi-master mode.
83 | * dataset (optional) Name of dataset. Default is 'global'.
84 | * timeout (optional) Seconds to wait for TCP responsiveness. Default is 0.2
85 | * lag_threshold (optional) The minimum lag on a slave in seconds before we consider it lagged.
86 | * Set null to disable. When not set, the value of $wpdb->default_lag_threshold is used.
87 | */
88 |
89 | /**
90 | * $wpdb->add_table( $dataset, $table );
91 | *
92 | * $dataset and $table are strings.
93 | */
94 |
95 | /**
96 | * $wpdb->add_callback( $callback, $callback_group = 'dataset' );
97 | *
98 | * $callback is a callable function or method. $callback_group is the
99 | * group of callbacks, this $callback belongs to.
100 | *
101 | * Callbacks are executed in the order in which they are registered until one
102 | * of them returns something other than null.
103 | *
104 | * The default $callback_group is 'dataset'. Callback in this group
105 | * will be called with two arguments and expected to compute a dataset or return null.
106 | * $dataset = $callback($table, &$wpdb);
107 | *
108 | * Anything evaluating to false will cause the query to be aborted.
109 | *
110 | * For more complex setups, the callback may be used to overwrite properties of
111 | * $wpdb or variables within hyperdb::connect_db(). If a callback returns an
112 | * array, HyperDB will extract the array. It should be an associative array and
113 | * it should include a $dataset value corresponding to a database added with
114 | * $wpdb->add_database(). It may also include $server, which will be extracted
115 | * to overwrite the parameters of each randomly selected database server prior
116 | * to connection. This allows you to dynamically vary parameters such as the
117 | * host, user, password, database name, lag_threshold and TCP check timeout.
118 | */
119 |
120 | /** Masters and slaves
121 | *
122 | * A database definition can include 'read' and 'write' parameters. These
123 | * operate as boolean switches but they are typically specified as integers.
124 | * They allow or disallow use of the database for reading or writing.
125 | *
126 | * A master database might be configured to allow reading and writing:
127 | * 'write' => 1,
128 | * 'read' => 1,
129 | * while a slave would be allowed only to read:
130 | * 'write' => 0,
131 | * 'read' => 1,
132 | *
133 | * It might be advantageous to disallow reading from the master, such as when
134 | * there are many slaves available and the master is very busy with writes.
135 | * 'write' => 1,
136 | * 'read' => 0,
137 | * HyperDB tracks the tables that it has written since instantiation and sending
138 | * subsequent read queries to the same server that received the write query.
139 | * Thus a master set up this way will still receive read queries, but only
140 | * subsequent to writes.
141 | */
142 |
143 |
144 | /**
145 | * Network topology / Datacenter awareness
146 | *
147 | * When your databases are located in separate physical locations there is
148 | * typically an advantage to connecting to a nearby server instead of a more
149 | * distant one. The read and write parameters can be used to place servers into
150 | * logical groups of more or less preferred connections. Lower numbers indicate
151 | * greater preference.
152 | *
153 | * This configuration instructs HyperDB to try reading from one of the local
154 | * slaves at random. If that slave is unreachable or refuses the connection,
155 | * the other slave will be tried, followed by the master, and finally the
156 | * remote slaves in random order.
157 | * Local slave 1: 'write' => 0, 'read' => 1,
158 | * Local slave 2: 'write' => 0, 'read' => 1,
159 | * Local master: 'write' => 1, 'read' => 2,
160 | * Remote slave 1: 'write' => 0, 'read' => 3,
161 | * Remote slave 2: 'write' => 0, 'read' => 3,
162 | *
163 | * In the other datacenter, the master would be remote. We would take that into
164 | * account while deciding where to send reads. Writes would always be sent to
165 | * the master, regardless of proximity.
166 | * Local slave 1: 'write' => 0, 'read' => 1,
167 | * Local slave 2: 'write' => 0, 'read' => 1,
168 | * Remote slave 1: 'write' => 0, 'read' => 2,
169 | * Remote slave 2: 'write' => 0, 'read' => 2,
170 | * Remote master: 'write' => 1, 'read' => 3,
171 | *
172 | * There are many ways to achieve different configurations in different
173 | * locations. You can deploy different config files. You can write code to
174 | * discover the web server's location, such as by inspecting $_SERVER or
175 | * php_uname(), and compute the read/write parameters accordingly. An example
176 | * appears later in this file using the legacy function add_db_server().
177 | */
178 |
179 | /**
180 | * Slaves lag awareness
181 | *
182 | * HyperDB accommodates slave lag by making decisions, based on the defined lag
183 | * threshold. If the lag threshold is not set, it will ignore the slave lag.
184 | * Otherwise, it will try to find a non-lagged slave, before connecting to a lagged one.
185 | *
186 | * A slave is considered lagged, if it's replication lag is bigger than the lag threshold
187 | * you have defined in $wpdb->$default_lag_threshold or in the per-database settings, using
188 | * add_database(). You can also rewrite the lag threshold, by returning
189 | * $server['lag_threshold'] variable with the 'dataset' group callbacks.
190 | *
191 | * HyperDB does not check the lag on the slaves. You have to define two callbacks
192 | * callbacks to do that:
193 | *
194 | * $wpdb->add_callback( $callback, 'get_lag_cache' );
195 | *
196 | * and
197 | *
198 | * $wpdb->add_callback( $callback, 'get_lag' );
199 | *
200 | * The first one is called, before connecting to a slave and should return
201 | * the replication lag in seconds or false, if unknown, based on $wpdb->lag_cache_key.
202 | *
203 | * The second callback is called after a connection to a slave is established.
204 | * It should return it's replication lag or false, if unknown,
205 | * based on the connection in $wpdb->dbhs[ $wpdb->dbhname ].
206 | */
207 |
208 | /** Sample Configuration 1: Using the Default Server **/
209 | /** NOTE: THIS IS ACTIVE BY DEFAULT. COMMENT IT OUT. **/
210 |
211 | /**
212 | * This is the most basic way to add a server to HyperDB using only the
213 | * required parameters: host, user, password, name.
214 | * This adds the DB defined in wp-config.php as a read/write server for
215 | * the 'global' dataset. (Every table is in 'global' by default.)
216 | */
217 | $wpdb->add_database(array(
218 | 'host' => DB_HOST, // If port is other than 3306, use host:port.
219 | 'user' => DB_USER,
220 | 'password' => DB_PASSWORD,
221 | 'name' => DB_NAME,
222 | 'write' => 1,
223 | 'read' => 1,
224 | ));
225 |
226 | /**
227 | * This adds the same server again, only this time it is configured as a slave.
228 | * The last three parameters are set to the defaults but are shown for clarity.
229 | */
230 | $wpdb->add_database(array(
231 | 'host' => SLAVE_DB_HOST, // If port is other than 3306, use host:port.
232 | 'user' => DB_USER,
233 | 'password' => DB_PASSWORD,
234 | 'name' => DB_NAME,
235 | 'write' => 0,
236 | 'read' => 1,
237 | 'dataset' => 'global',
238 | 'timeout' => 0.2,
239 | ));
240 |
241 | /** Sample Configuration 2: Partitioning **/
242 |
243 | /**
244 | * This example shows a setup where the multisite blog tables have been
245 | * separated from the global dataset.
246 | */
247 | /*
248 | $wpdb->add_database(array(
249 | 'host' => 'global.db.example.com',
250 | 'user' => 'globaluser',
251 | 'password' => 'globalpassword',
252 | 'name' => 'globaldb',
253 | ));
254 | $wpdb->add_database(array(
255 | 'host' => 'blog.db.example.com',
256 | 'user' => 'bloguser',
257 | 'password' => 'blogpassword',
258 | 'name' => 'blogdb',
259 | 'dataset' => 'blog',
260 | ));
261 | $wpdb->add_callback('my_db_callback');
262 | function my_db_callback($query, $wpdb) {
263 | // Multisite blog tables are "{$base_prefix}{$blog_id}_*"
264 | if ( preg_match("/^{$wpdb->base_prefix}\d+_/i", $wpdb->table) )
265 | return 'blog';
266 | }
267 | */
268 |
269 |
270 | /** Sample helper functions from WordPress.com **/
271 |
272 | /**
273 | * This is back-compatible with an older config style. It is for convenience.
274 | * lhost, part, and dc were removed from hyperdb because the read and write
275 | * parameters provide enough power to achieve the desired effects via config.
276 | *
277 | * @param string $dataset Datset: the name of the dataset. Just use "global" if you don't need horizontal partitioning.
278 | * @param int $part Partition: the vertical partition number (1, 2, 3, etc.). Use "0" if you don't need vertical partitioning.
279 | * @param string $dc Datacenter: where the database server is located. Airport codes are convenient. Use whatever.
280 | * @param int $read Read group: tries all servers in lowest number group before trying higher number group. Typical: 1 for slaves, 2 for master. This will cause reads to go to slaves unless all slaves are unreachable. Zero for no reads.
281 | * @param bool $write Write flag: is this server writable? Works the same as $read. Typical: 1 for master, 0 for slaves.
282 | * @param string $host Internet address: host:port of server on internet.
283 | * @param string $lhost Local address: host:port of server for use when in same datacenter. Leave empty if no local address exists.
284 | * @param string $name Database name.
285 | * @param string $user Database user.
286 | * @param string $password Database password.
287 | */
288 | /*
289 | function add_db_server($dataset, $part, $dc, $read, $write, $host, $lhost, $name, $user, $password, $timeout = 0.2 ) {
290 | global $wpdb;
291 |
292 | // dc is not used in hyperdb. This produces the desired effect of
293 | // trying to connect to local servers before remote servers. Also
294 | // increases time allowed for TCP responsiveness check.
295 | if ( !empty($dc) && defined(DATACENTER) && $dc != DATACENTER ) {
296 | if ( $read )
297 | $read += 10000;
298 | if ( $write )
299 | $write += 10000;
300 | $timeout = 0.7;
301 | }
302 |
303 | // You'll need a hyperdb::add_callback() callback function to use partitioning.
304 | // $wpdb->add_callback( 'my_func' );
305 | if ( $part )
306 | $dataset = $dataset . '_' . $part;
307 |
308 | $database = compact('dataset', 'read', 'write', 'host', 'name', 'user', 'password', 'timeout');
309 |
310 | $wpdb->add_database($database);
311 |
312 | // lhost is not used in hyperdb. This configures hyperdb with an
313 | // additional server to represent the local hostname so it tries to
314 | // connect over the private interface before the public one.
315 | if ( !empty( $lhost ) ) {
316 | if ( $read )
317 | $database['read'] = $read - 0.5;
318 | if ( $write )
319 | $database['write'] = $write - 0.5;
320 | $wpdb->add_database( $database );
321 | }
322 | }
323 | */
324 |
325 | /**
326 | * Sample replication lag detection configuration.
327 | *
328 | * We use mk-heartbeat (http://www.maatkit.org/doc/mk-heartbeat.html)
329 | * to detect replication lag.
330 | *
331 | * This implementation requires the database user
332 | * to have read access to the heartbeat table.
333 | *
334 | * The cache uses shared memory for portability.
335 | * Can be modified to work with Memcached, APC and etc.
336 | */
337 |
338 | /*
339 |
340 | $wpdb->lag_cache_ttl = 30;
341 | $wpdb->shmem_key = ftok( __FILE__, "Y" );
342 | $wpdb->shmem_size = 128 * 1024;
343 |
344 | $wpdb->add_callback( 'get_lag_cache', 'get_lag_cache' );
345 | $wpdb->add_callback( 'get_lag', 'get_lag' );
346 |
347 | function get_lag_cache( $wpdb ) {
348 | $segment = shm_attach( $wpdb->shmem_key, $wpdb->shmem_size, 0600 );
349 | $lag_data = @shm_get_var( $segment, 0 );
350 | shm_detach( $segment );
351 |
352 | if ( !is_array( $lag_data ) || !is_array( $lag_data[ $wpdb->lag_cache_key ] ) )
353 | return false;
354 |
355 | if ( $wpdb->lag_cache_ttl < time() - $lag_data[ $wpdb->lag_cache_key ][ 'timestamp' ] )
356 | return false;
357 |
358 | return $lag_data[ $wpdb->lag_cache_key ][ 'lag' ];
359 | }
360 |
361 | function get_lag( $wpdb ) {
362 | $dbh = $wpdb->dbhs[ $wpdb->dbhname ];
363 |
364 | if ( !mysql_select_db( 'heartbeat', $dbh ) )
365 | return false;
366 |
367 | $result = mysql_query( "SELECT UNIX_TIMESTAMP() - UNIX_TIMESTAMP(ts) AS lag FROM heartbeat LIMIT 1", $dbh );
368 |
369 | if ( !$result || false === $row = mysql_fetch_assoc( $result ) )
370 | return false;
371 |
372 | // Cache the result in shared memory with timestamp
373 | $sem_id = sem_get( $wpdb->shmem_key, 1, 0600, 1 ) ;
374 | sem_acquire( $sem_id );
375 | $segment = shm_attach( $wpdb->shmem_key, $wpdb->shmem_size, 0600 );
376 | $lag_data = @shm_get_var( $segment, 0 );
377 |
378 | if ( !is_array( $lag_data ) )
379 | $lag_data = array();
380 |
381 | $lag_data[ $wpdb->lag_cache_key ] = array( 'timestamp' => time(), 'lag' => $row[ 'lag' ] );
382 | shm_put_var( $segment, 0, $lag_data );
383 | shm_detach( $segment );
384 | sem_release( $sem_id );
385 |
386 | return $row[ 'lag' ];
387 | }
388 |
389 | */
390 |
391 | // The ending PHP tag is omitted. This is actually safer than including it.
392 |
--------------------------------------------------------------------------------
/ansible-playbooks/roles/web/files/db.php:
--------------------------------------------------------------------------------
1 | dbh) for established mysql connections
84 | * @var array
85 | */
86 | var $dbhs;
87 |
88 | /**
89 | * The multi-dimensional array of datasets and servers
90 | * @var array
91 | */
92 | var $hyper_servers = array();
93 |
94 | /**
95 | * Optional directory of tables and their datasets
96 | * @var array
97 | */
98 | var $hyper_tables = array();
99 |
100 | /**
101 | * Optional directory of callbacks to determine datasets from queries
102 | * @var array
103 | */
104 | var $hyper_callbacks = array();
105 |
106 | /**
107 | * Custom callback to save debug info in $this->queries
108 | * @var callable
109 | */
110 | var $save_query_callback = null;
111 |
112 | /**
113 | * Whether to use persistent connections
114 | * @var bool
115 | */
116 | var $persistent = false;
117 |
118 | /**
119 | * The maximum number of db links to keep open. The least-recently used
120 | * link will be closed when the number of links exceeds this.
121 | * @var int
122 | */
123 | var $max_connections = 10;
124 |
125 | /**
126 | * Whether to check with fsockopen prior to connecting to mysql.
127 | * @var bool
128 | */
129 | var $check_tcp_responsiveness = true;
130 |
131 | /**
132 | * Minimum number of connections to try before bailing
133 | * @var int
134 | */
135 | var $min_tries = 3;
136 |
137 | /**
138 | * Send Reads To Masters. This disables slave connections while true.
139 | * Otherwise it is an array of written tables.
140 | * @var array
141 | */
142 | var $srtm = array();
143 |
144 | /**
145 | * The log of db connections made and the time each one took
146 | * @var array
147 | */
148 | var $db_connections;
149 |
150 | /**
151 | * The list of unclosed connections sorted by LRU
152 | */
153 | var $open_connections = array();
154 |
155 | /**
156 | * The last server used and the database name selected
157 | * @var array
158 | */
159 | var $last_used_server;
160 |
161 | /**
162 | * Lookup array (dbhname => (server, db name) ) for re-selecting the db
163 | * when a link is re-used.
164 | * @var array
165 | */
166 | var $used_servers = array();
167 |
168 | /**
169 | * Whether to save debug_backtrace in save_query_callback. You may wish
170 | * to disable this, e.g. when tracing out-of-memory problems.
171 | */
172 | var $save_backtrace = true;
173 |
174 | /**
175 | * Maximum lag in seconds. Set null to disable. Requires callbacks.
176 | * @var integer
177 | */
178 | var $default_lag_threshold = null;
179 |
180 | /**
181 | * Lookup array (dbhname => host:port)
182 | * @var array
183 | */
184 | var $dbh2host = array();
185 |
186 | /**
187 | * Keeps track of the dbhname usage and errors.
188 | */
189 | var $dbhname_heartbeats = array();
190 |
191 | /**
192 | * Counter for how many queries have failed during the life of the $wpdb object
193 | */
194 | var $num_failed_queries = 0;
195 |
196 | /**
197 | * Gets ready to make database connections
198 | * @param array db class vars
199 | */
200 | function __construct( $args = null ) {
201 | if ( is_array($args) )
202 | foreach ( get_class_vars(__CLASS__) as $var => $value )
203 | if ( isset($args[$var]) )
204 | $this->$var = $args[$var];
205 |
206 | $this->use_mysqli = $this->should_use_mysqli();
207 |
208 | $this->init_charset();
209 | }
210 |
211 | /**
212 | * Triggers __construct() for backwards compatibility with PHP4
213 | */
214 | function hyperdb( $args = null ) {
215 | return $this->__construct($args);
216 | }
217 |
218 | /**
219 | * Sets $this->charset and $this->collate
220 | */
221 | function init_charset() {
222 | if ( function_exists('is_multisite') && is_multisite() ) {
223 | $this->charset = 'utf8';
224 | if ( defined( 'DB_COLLATE' ) && DB_COLLATE )
225 | $this->collate = DB_COLLATE;
226 | else
227 | $this->collate = 'utf8_general_ci';
228 | } elseif ( defined( 'DB_COLLATE' ) ) {
229 | $this->collate = DB_COLLATE;
230 | }
231 |
232 | if ( defined( 'DB_CHARSET' ) )
233 | $this->charset = DB_CHARSET;
234 | }
235 |
236 | /**
237 | * Add the connection parameters for a database
238 | */
239 | function add_database( $db ) {
240 | extract($db, EXTR_SKIP);
241 | isset($dataset) or $dataset = 'global';
242 | isset($read) or $read = 1;
243 | isset($write) or $write = 1;
244 | unset($db['dataset']);
245 |
246 | if ( $read )
247 | $this->hyper_servers[ $dataset ][ 'read' ][ $read ][] = $db;
248 | if ( $write )
249 | $this->hyper_servers[ $dataset ][ 'write' ][ $write ][] = $db;
250 | }
251 |
252 | /**
253 | * Specify the dateset where a table is found
254 | */
255 | function add_table( $dataset, $table ) {
256 | $this->hyper_tables[ $table ] = $dataset;
257 | }
258 |
259 | /**
260 | * Add a callback to a group of callbacks.
261 | * The default group is 'dataset', used to examine
262 | * queries and determine dataset.
263 | */
264 | function add_callback( $callback, $group = 'dataset' ) {
265 | $this->hyper_callbacks[ $group ][] = $callback;
266 | }
267 |
268 | /**
269 | * Find the first table name referenced in a query
270 | * @param string query
271 | * @return string table
272 | */
273 | function get_table_from_query( $q ) {
274 | // Remove characters that can legally trail the table name
275 | $q = rtrim($q, ';/-#');
276 | // allow (select...) union [...] style queries. Use the first queries table name.
277 | $q = ltrim($q, "\t (");
278 | // Strip everything between parentheses except nested
279 | // selects and use only 1500 chars of the query
280 | $q = preg_replace( '/\((?!\s*select)[^(]*?\)/is', '()', substr( $q, 0, 1500 ) );
281 |
282 | // Refer to the previous query
283 | // wpdb doesn't implement last_table, so we run it first.
284 | if ( preg_match('/^\s*SELECT.*?\s+FOUND_ROWS\(\)/is', $q) )
285 | return $this->last_table;
286 |
287 | if( method_exists( get_parent_class( $this ), 'get_table_from_query' ) ) {
288 | // WPDB has added support for get_table_from_query, which should take precedence
289 | return parent::get_table_from_query( $q );
290 | }
291 |
292 | // Quickly match most common queries
293 | if ( preg_match('/^\s*(?:'
294 | . 'SELECT.*?\s+FROM'
295 | . '|INSERT(?:\s+IGNORE)?(?:\s+INTO)?'
296 | . '|REPLACE(?:\s+INTO)?'
297 | . '|UPDATE(?:\s+IGNORE)?'
298 | . '|DELETE(?:\s+IGNORE)?(?:\s+FROM)?'
299 | . ')\s+`?([\w-]+)`?/is', $q, $maybe) )
300 | return $maybe[1];
301 |
302 | // SHOW TABLE STATUS and SHOW TABLES
303 | if ( preg_match('/^\s*(?:'
304 | . 'SHOW\s+TABLE\s+STATUS.+(?:LIKE\s+|WHERE\s+Name\s*=\s*)'
305 | . '|SHOW\s+(?:FULL\s+)?TABLES.+(?:LIKE\s+|WHERE\s+Name\s*=\s*)'
306 | . ')\W([\w-]+)\W/is', $q, $maybe) )
307 | return $maybe[1];
308 |
309 | // Big pattern for the rest of the table-related queries in MySQL 5.0
310 | if ( preg_match('/^\s*(?:'
311 | . '(?:EXPLAIN\s+(?:EXTENDED\s+)?)?SELECT.*?\s+FROM'
312 | . '|INSERT(?:\s+LOW_PRIORITY|\s+DELAYED|\s+HIGH_PRIORITY)?(?:\s+IGNORE)?(?:\s+INTO)?'
313 | . '|REPLACE(?:\s+LOW_PRIORITY|\s+DELAYED)?(?:\s+INTO)?'
314 | . '|UPDATE(?:\s+LOW_PRIORITY)?(?:\s+IGNORE)?'
315 | . '|DELETE(?:\s+LOW_PRIORITY|\s+QUICK|\s+IGNORE)*(?:\s+FROM)?'
316 | . '|DESCRIBE|DESC|EXPLAIN|HANDLER'
317 | . '|(?:LOCK|UNLOCK)\s+TABLE(?:S)?'
318 | . '|(?:RENAME|OPTIMIZE|BACKUP|RESTORE|CHECK|CHECKSUM|ANALYZE|OPTIMIZE|REPAIR).*\s+TABLE'
319 | . '|TRUNCATE(?:\s+TABLE)?'
320 | . '|CREATE(?:\s+TEMPORARY)?\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?'
321 | . '|ALTER(?:\s+IGNORE)?\s+TABLE'
322 | . '|DROP\s+TABLE(?:\s+IF\s+EXISTS)?'
323 | . '|CREATE(?:\s+\w+)?\s+INDEX.*\s+ON'
324 | . '|DROP\s+INDEX.*\s+ON'
325 | . '|LOAD\s+DATA.*INFILE.*INTO\s+TABLE'
326 | . '|(?:GRANT|REVOKE).*ON\s+TABLE'
327 | . '|SHOW\s+(?:.*FROM|.*TABLE)'
328 | . ')\s+`?([\w-]+)`?/is', $q, $maybe) )
329 | return $maybe[1];
330 | }
331 |
332 | /**
333 | * Determine the likelihood that this query could alter anything
334 | * @param string query
335 | * @return bool
336 | */
337 | function is_write_query( $q ) {
338 | // Quick and dirty: only SELECT statements are considered read-only.
339 | $q = ltrim($q, "\r\n\t (");
340 | return !preg_match('/^(?:SELECT|SHOW|DESCRIBE|DESC|EXPLAIN)\s/i', $q);
341 | }
342 |
343 | /**
344 | * Set a flag to prevent reading from slaves which might be lagging after a write
345 | */
346 | function send_reads_to_masters() {
347 | $this->srtm = true;
348 | }
349 |
350 | /**
351 | * Callbacks are executed in the order in which they are registered until one
352 | * of them returns something other than null.
353 | */
354 | function run_callbacks( $group, $args = null) {
355 | if ( ! isset( $this->hyper_callbacks[ $group ] ) || ! is_array( $this->hyper_callbacks[ $group ] ) )
356 | return null;
357 |
358 | if ( ! isset( $args ) ) {
359 | $args = array( &$this );
360 | } elseif ( is_array( $args ) ) {
361 | $args[] = &$this;
362 | } else {
363 | $args = array( $args, &$this );
364 | }
365 |
366 | foreach ( $this->hyper_callbacks[ $group ] as $func ) {
367 | $result = call_user_func_array($func, $args);
368 | if ( isset($result) )
369 | return $result;
370 | }
371 | }
372 |
373 | /**
374 | * Figure out which database server should handle the query, and connect to it.
375 | * @param string query
376 | * @return resource mysql database connection
377 | */
378 | function db_connect( $query = '' ) {
379 | if ( empty( $query ) )
380 | return false;
381 |
382 | $this->last_table = $this->table = $this->get_table_from_query($query);
383 |
384 | if ( isset($this->hyper_tables[$this->table]) ) {
385 | $dataset = $this->hyper_tables[$this->table];
386 | $this->callback_result = null;
387 | } elseif ( null !== $this->callback_result = $this->run_callbacks( 'dataset', $query ) ) {
388 | if ( is_array($this->callback_result) )
389 | extract( $this->callback_result, EXTR_OVERWRITE );
390 | else
391 | $dataset = $this->callback_result;
392 | }
393 |
394 | if ( ! isset($dataset) )
395 | $dataset = 'global';
396 |
397 | if ( ! $dataset )
398 | return $this->log_and_bail("Unable to determine dataset (for table: $this->table)");
399 | else
400 | $this->dataset = $dataset;
401 |
402 | $this->run_callbacks( 'dataset_found', $dataset );
403 |
404 | if ( empty( $this->hyper_servers ) ) {
405 | if ( $this->is_mysql_connection( $this->dbh ) )
406 | return $this->dbh;
407 | if (
408 | !defined('DB_HOST')
409 | || !defined('DB_USER')
410 | || !defined('DB_PASSWORD')
411 | || !defined('DB_NAME') )
412 | return $this->log_and_bail("We were unable to query because there was no database defined");
413 | $this->dbh = $this->ex_mysql_connect( DB_HOST, DB_USER, DB_PASSWORD, $this->persistent );
414 | if ( ! $this->is_mysql_connection( $this->dbh ) )
415 | return $this->log_and_bail("We were unable to connect to the database. (DB_HOST)");
416 | if ( ! $this->ex_mysql_select_db( DB_NAME, $this->dbh ) )
417 | return $this->log_and_bail("We were unable to select the database");
418 | if ( ! empty( $this->charset ) ) {
419 | $collation_query = "SET NAMES '$this->charset'";
420 | if ( !empty( $this->collate ) )
421 | $collation_query .= " COLLATE '$this->collate'";
422 | $this->ex_mysql_query( $collation_query, $this->dbh );
423 | }
424 | return $this->dbh;
425 | }
426 |
427 | // Determine whether the query must be sent to the master (a writable server)
428 | if ( !empty( $use_master ) || $this->srtm === true || isset($this->srtm[$this->table]) ) {
429 | $use_master = true;
430 | } elseif ( $is_write = $this->is_write_query($query) ) {
431 | $use_master = true;
432 | if ( is_array($this->srtm) )
433 | $this->srtm[$this->table] = true;
434 | } elseif ( !isset($use_master) && is_array($this->srtm) && !empty($this->srtm) ) {
435 | // Detect queries that have a join in the srtm array.
436 | $use_master = false;
437 | $query_match = substr( $query, 0, 1000 );
438 | foreach ( $this->srtm as $key => $value ) {
439 | if ( false !== stripos( $query_match, $key ) ) {
440 | $use_master = true;
441 | break;
442 | }
443 | }
444 | } else {
445 | $use_master = false;
446 | }
447 |
448 | if ( $use_master ) {
449 | $this->dbhname = $dbhname = $dataset . '__w';
450 | $operation = 'write';
451 | } else {
452 | $this->dbhname = $dbhname = $dataset . '__r';
453 | $operation = 'read';
454 | }
455 |
456 | // Try to reuse an existing connection
457 | while ( isset( $this->dbhs[$dbhname] ) && $this->is_mysql_connection( $this->dbhs[$dbhname] ) ) {
458 | // Find the connection for incrementing counters
459 | foreach ( array_keys($this->db_connections) as $i )
460 | if ( $this->db_connections[$i]['dbhname'] == $dbhname )
461 | $conn =& $this->db_connections[$i];
462 |
463 | if ( isset($server['name']) ) {
464 | $name = $server['name'];
465 | // A callback has specified a database name so it's possible the existing connection selected a different one.
466 | if ( $name != $this->used_servers[$dbhname]['name'] ) {
467 | if ( ! $this->ex_mysql_select_db( $name, $this->dbhs[$dbhname] ) ) {
468 | // this can happen when the user varies and lacks permission on the $name database
469 | if ( isset( $conn['disconnect (select failed)'] ) )
470 | ++$conn['disconnect (select failed)'];
471 | else
472 | $conn['disconnect (select failed)'] = 1;
473 |
474 | $this->disconnect($dbhname);
475 | break;
476 | }
477 | $this->used_servers[$dbhname]['name'] = $name;
478 | }
479 | } else {
480 | $name = $this->used_servers[$dbhname]['name'];
481 | }
482 |
483 | $this->current_host = $this->dbh2host[$dbhname];
484 |
485 | // Keep this connection at the top of the stack to prevent disconnecting frequently-used connections
486 | if ( $k = array_search($dbhname, $this->open_connections) ) {
487 | unset($this->open_connections[$k]);
488 | $this->open_connections[] = $dbhname;
489 | }
490 |
491 | $this->last_used_server = $this->used_servers[$dbhname];
492 | $this->last_connection = compact('dbhname', 'name');
493 |
494 | if ( $this->should_mysql_ping() && ! $this->ex_mysql_ping( $this->dbhs[$dbhname] ) ) {
495 | if ( isset( $conn['disconnect (ping failed)'] ) )
496 | ++$conn['disconnect (ping failed)'];
497 | else
498 | $conn['disconnect (ping failed)'] = 1;
499 |
500 | $this->disconnect($dbhname);
501 | break;
502 | }
503 |
504 | if ( isset( $conn['queries'] ) )
505 | ++$conn['queries'];
506 | else
507 | $conn['queries'] = 1;
508 |
509 | return $this->dbhs[$dbhname];
510 | }
511 |
512 | if ( $use_master && defined( "MASTER_DB_DEAD" ) ) {
513 | return $this->bail("We're updating the database, please try back in 5 minutes. If you are posting to your blog please hit the refresh button on your browser in a few minutes to post the data again. It will be posted as soon as the database is back online again.");
514 | }
515 |
516 | if ( empty($this->hyper_servers[$dataset][$operation]) )
517 | return $this->log_and_bail("No databases available with $this->table ($dataset)");
518 |
519 | // Put the groups in order by priority
520 | ksort($this->hyper_servers[$dataset][$operation]);
521 |
522 | // Make a list of at least $this->min_tries connections to try, repeating as necessary.
523 | $servers = array();
524 | do {
525 | foreach ( $this->hyper_servers[$dataset][$operation] as $group => $items ) {
526 | $keys = array_keys($items);
527 | shuffle($keys);
528 | foreach ( $keys as $key )
529 | $servers[] = compact('group', 'key');
530 | }
531 |
532 | if ( !$tries_remaining = count( $servers ) )
533 | return $this->log_and_bail("No database servers were found to match the query ($this->table, $dataset)");
534 |
535 | if ( !isset( $unique_servers ) )
536 | $unique_servers = $tries_remaining;
537 |
538 | } while ( $tries_remaining < $this->min_tries );
539 |
540 | // Connect to a database server
541 | do {
542 | $unique_lagged_slaves = array();
543 | $success = false;
544 |
545 | foreach ( $servers as $group_key ) {
546 | --$tries_remaining;
547 |
548 | // If all servers are lagged, we need to start ignoring the lag and retry
549 | if ( count( $unique_lagged_slaves ) == $unique_servers )
550 | break;
551 |
552 | // $group, $key
553 | extract($group_key, EXTR_OVERWRITE);
554 |
555 | // $host, $user, $password, $name, $read, $write [, $lag_threshold, $timeout ]
556 | extract($this->hyper_servers[$dataset][$operation][$group][$key], EXTR_OVERWRITE);
557 | $port = null;
558 |
559 | // Split host:port into $host and $port
560 | if ( strpos($host, ':') )
561 | list($host, $port) = explode(':', $host);
562 |
563 | // Overlay $server if it was extracted from a callback
564 | if ( isset($server) && is_array($server) )
565 | extract($server, EXTR_OVERWRITE);
566 |
567 | // Split again in case $server had host:port
568 | if ( strpos($host, ':') )
569 | list($host, $port) = explode(':', $host);
570 |
571 | // Make sure there's always a port number
572 | if ( empty($port) )
573 | $port = 3306;
574 |
575 | // Use a default timeout of 200ms
576 | if ( !isset($timeout) )
577 | $timeout = 0.2;
578 |
579 | // Get the minimum group here, in case $server rewrites it
580 | if ( !isset( $min_group ) || $min_group > $group )
581 | $min_group = $group;
582 |
583 | // Can be used by the lag callbacks
584 | $this->lag_cache_key = "$host:$port";
585 | $this->lag_threshold = isset($lag_threshold) ? $lag_threshold : $this->default_lag_threshold;
586 |
587 | // Check for a lagged slave, if applicable
588 | if ( !$use_master && !$write && !isset( $ignore_slave_lag )
589 | && isset($this->lag_threshold) && !isset( $server['host'] )
590 | && ( $lagged_status = $this->get_lag_cache() ) === HYPERDB_LAG_BEHIND
591 | ) {
592 | // If it is the last lagged slave and it is with the best preference we will ignore its lag
593 | if ( !isset( $unique_lagged_slaves["$host:$port"] )
594 | && $unique_servers == count( $unique_lagged_slaves ) + 1
595 | && $group == $min_group )
596 | {
597 | $this->lag_threshold = null;
598 | } else {
599 | $unique_lagged_slaves["$host:$port"] = $this->lag;
600 | continue;
601 | }
602 | }
603 |
604 | $this->timer_start();
605 |
606 | // Connect if necessary or possible
607 | $server_state = null;
608 | if ( $use_master || ! $tries_remaining ||
609 | 'up' == $server_state = $this->get_server_state( $host, $port, $timeout ) )
610 | {
611 | $this->set_connect_timeout( 'pre_connect', $use_master, $tries_remaining );
612 | $this->dbhs[$dbhname] = $this->ex_mysql_connect( "$host:$port", $user, $password, $this->persistent );
613 | $this->set_connect_timeout( 'post_connect', $use_master, $tries_remaining );
614 | } else {
615 | $this->dbhs[$dbhname] = false;
616 | }
617 |
618 | $elapsed = $this->timer_stop();
619 |
620 | if ( $this->is_mysql_connection( $this->dbhs[$dbhname] ) ) {
621 | /**
622 | * If we care about lag, disconnect lagged slaves and try to find others.
623 | * We don't disconnect if it is the last lagged slave and it is with the best preference.
624 | */
625 | if ( !$use_master && !$write && !isset( $ignore_slave_lag )
626 | && isset($this->lag_threshold) && !isset( $server['host'] )
627 | && $lagged_status !== HYPERDB_LAG_OK
628 | && ( $lagged_status = $this->get_lag() ) === HYPERDB_LAG_BEHIND
629 | && !(
630 | !isset( $unique_lagged_slaves["$host:$port"] )
631 | && $unique_servers == count( $unique_lagged_slaves ) + 1
632 | && $group == $min_group
633 | )
634 | ) {
635 | $success = false;
636 | $unique_lagged_slaves["$host:$port"] = $this->lag;
637 | $this->disconnect( $dbhname );
638 | $this->dbhs[$dbhname] = false;
639 | $msg = "Replication lag of {$this->lag}s on $host:$port ($dbhname)";
640 | $this->print_error( $msg );
641 | continue;
642 | } elseif ( $this->ex_mysql_select_db( $name, $this->dbhs[$dbhname] ) ) {
643 | $success = true;
644 | $this->current_host = "$host:$port";
645 | $this->dbh2host[$dbhname] = "$host:$port";
646 | $queries = 1;
647 | $lag = isset( $this->lag ) ? $this->lag : 0;
648 | $this->last_connection = compact('dbhname', 'host', 'port', 'user', 'name', 'tcp', 'elapsed', 'success', 'queries', 'lag');
649 | $this->db_connections[] = $this->last_connection;
650 | $this->open_connections[] = $dbhname;
651 | break;
652 | }
653 | }
654 |
655 | if ( 'down' == $server_state )
656 | continue; // don't flood the logs if already down
657 |
658 | if ( HYPERDB_CONN_HOST_ERROR == $this->ex_mysql_errno() &&
659 | ( 'up' == $server_state || ! $tries_remaining ) )
660 | {
661 | $this->mark_server_as_down( $host, $port );
662 | $server_state = 'down';
663 | }
664 |
665 | $success = false;
666 | $this->last_connection = compact('dbhname', 'host', 'port', 'user', 'name', 'tcp', 'elapsed', 'success');
667 | $this->db_connections[] = $this->last_connection;
668 | $msg = date( "Y-m-d H:i:s" ) . " Can't select $dbhname - \n";
669 | $msg .= "'referrer' => '{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}',\n";
670 | $msg .= "'server' => {$server},\n";
671 | $msg .= "'host' => {$host},\n";
672 | $msg .= "'error' => " . $this->ex_mysql_error() . ",\n";
673 | $msg .= "'errno' => " . $this->ex_mysql_errno() . ",\n";
674 | $msg .= "'server_state' => $server_state\n";
675 | $msg .= "'lagged_status' => " . ( isset( $lagged_status ) ? $lagged_status : HYPERDB_LAG_UNKNOWN );
676 |
677 | $this->print_error( $msg );
678 | }
679 |
680 | if ( ! $success || ! isset( $this->dbhs[$dbhname] ) || ! $this->is_mysql_connection( $this->dbhs[$dbhname] ) ) {
681 | if ( !isset( $ignore_slave_lag ) && count( $unique_lagged_slaves ) ) {
682 | // Lagged slaves were not used. Ignore the lag for this connection attempt and retry.
683 | $ignore_slave_lag = true;
684 | $tries_remaining = count( $servers );
685 | continue;
686 | }
687 |
688 | $error_details = array(
689 | 'host' => $host,
690 | 'port' => $port,
691 | 'operation' => $operation,
692 | 'table' => $this->table,
693 | 'dataset' => $dataset,
694 | 'dbhname' => $dbhname
695 | );
696 | $this->run_callbacks( 'db_connection_error', $error_details );
697 |
698 | return $this->bail( "Unable to connect to $host:$port to $operation table '$this->table' ($dataset)" );
699 | }
700 |
701 | break;
702 | } while ( true );
703 |
704 | if ( !isset( $charset ) )
705 | $charset = null;
706 |
707 | if ( !isset( $collate ) )
708 | $collate = null;
709 |
710 | $this->set_charset($this->dbhs[$dbhname], $charset, $collate);
711 |
712 | $this->dbh = $this->dbhs[$dbhname]; // needed by $wpdb->_real_escape()
713 |
714 | $this->last_used_server = compact('host', 'user', 'name', 'read', 'write');
715 |
716 | $this->used_servers[$dbhname] = $this->last_used_server;
717 |
718 | while ( !$this->persistent && count($this->open_connections) > $this->max_connections ) {
719 | $oldest_connection = array_shift($this->open_connections);
720 | if ( $this->dbhs[$oldest_connection] != $this->dbhs[$dbhname] )
721 | $this->disconnect($oldest_connection);
722 | }
723 |
724 | return $this->dbhs[$dbhname];
725 | }
726 |
727 | /**
728 | * Sets the connection's character set.
729 | * @param resource $dbh The resource given by ex_mysql_connect
730 | * @param string $charset The character set (optional)
731 | * @param string $collate The collation (optional)
732 | */
733 | function set_charset($dbh, $charset = null, $collate = null) {
734 | if ( !isset($charset) )
735 | $charset = $this->charset;
736 | if ( !isset($collate) )
737 | $collate = $this->collate;
738 |
739 | if ( ! $this->has_cap( 'collation', $dbh ) || empty( $charset ) )
740 | return;
741 |
742 | if ( ! in_array( strtolower( $charset ), array( 'utf8', 'utf8mb4', 'latin1' ) ) )
743 | wp_die( "$charset charset isn't supported in HyperDB for security reasons" );
744 |
745 | if ( $this->is_mysql_set_charset_callable() && $this->has_cap( 'set_charset', $dbh ) ) {
746 | $this->ex_mysql_set_charset( $charset, $dbh );
747 | $this->real_escape = true;
748 | } else {
749 | $query = $this->prepare( 'SET NAMES %s', $charset );
750 | if ( ! empty( $collate ) )
751 | $query .= $this->prepare( ' COLLATE %s', $collate );
752 | $this->ex_mysql_query( $query, $dbh );
753 | }
754 | }
755 |
756 | /*
757 | * Force addslashes() for the escapes.
758 | *
759 | * HyperDB makes connections when a query is made
760 | * which is why we can't use mysql_real_escape_string() for escapes.
761 | * This is also the reason why we don't allow certain charsets. See set_charset().
762 | */
763 | function _real_escape( $string ) {
764 | $escaped = addslashes( $string );
765 | if ( method_exists( get_parent_class( $this ), 'add_placeholder_escape' ) ) {
766 | $escaped = $this->add_placeholder_escape( $escaped );
767 | }
768 | return $escaped;
769 | }
770 | /**
771 | * Disconnect and remove connection from open connections list
772 | * @param string $tdbhname
773 | */
774 | function disconnect($dbhname) {
775 | if ( false !== $k = array_search($dbhname, $this->open_connections) )
776 | unset($this->open_connections[$k]);
777 |
778 | foreach ( array_keys( $this->db_connections ) as $i )
779 | if ( $this->db_connections[$i]['dbhname'] == $dbhname )
780 | unset( $this->db_connections[$i] );
781 |
782 | if ( $this->is_mysql_connection( $this->dbhs[$dbhname] ) )
783 | $this->ex_mysql_close( $this->dbhs[$dbhname] );
784 |
785 | unset($this->dbhs[$dbhname]);
786 | }
787 |
788 | /**
789 | * Kill cached query results
790 | */
791 | function flush() {
792 | $this->last_error = '';
793 | $this->last_errno = 0;
794 | $this->num_rows = 0;
795 | parent::flush();
796 | }
797 |
798 | /**
799 | * Basic query. See docs for more details.
800 | * @param string $query
801 | * @return int number of rows
802 | */
803 | function query( $query ) {
804 | // some queries are made before the plugins have been loaded, and thus cannot be filtered with this method
805 | if ( function_exists('apply_filters') )
806 | $query = apply_filters('query', $query);
807 |
808 | // initialise return
809 | $return_val = 0;
810 | $this->flush();
811 |
812 | // Log how the function was called
813 | $this->func_call = "\$db->query(\"$query\")";
814 |
815 | // Keep track of the last query for debug..
816 | $this->last_query = $query;
817 |
818 | if ( preg_match( '/^\s*SELECT\s+FOUND_ROWS(\s*)/i', $query ) ) {
819 | if ( $this->is_mysql_result( $this->last_found_rows_result ) ) {
820 | $this->result = $this->last_found_rows_result;
821 | $elapsed = 0;
822 | } else {
823 | $this->print_error( "Attempted SELECT FOUND_ROWS() without prior SQL_CALC_FOUND_ROWS." );
824 | return false;
825 | }
826 | } else {
827 | $this->dbh = $this->db_connect( $query );
828 |
829 | if ( ! $this->is_mysql_connection( $this->dbh ) ) {
830 | $this->check_current_query = true;
831 | $this->last_error = 'Database connection failed';
832 | $this->num_failed_queries++;
833 | do_action( 'sql_query_log', $query, false, $this->last_error );
834 | return false;
835 | }
836 |
837 | $query_comment = $this->run_callbacks( 'get_query_comment', $query );
838 | if ( ! empty( $query_comment ) )
839 | $query = rtrim( $query, ";\t \n\r" ) . ' /* ' . $query_comment . ' */';
840 |
841 | // If we're writing to the database, make sure the query will write safely.
842 | if ( $this->check_current_query && method_exists( $this, 'check_ascii' ) && ! $this->check_ascii( $query ) ) {
843 | $stripped_query = $this->strip_invalid_text_from_query( $query );
844 | if ( $stripped_query !== $query ) {
845 | $this->insert_id = 0;
846 | $this->last_error = 'Invalid query';
847 | $this->num_failed_queries++;
848 | do_action( 'sql_query_log', $query, false, $this->last_error );
849 | return false;
850 | }
851 | }
852 |
853 | $this->check_current_query = true;
854 |
855 | // Inject setup and teardown statements
856 | $statement_before_query = $this->run_callbacks( 'statement_before_query' );
857 | $statement_after_query = $this->run_callbacks( 'statement_after_query' );
858 | $query_for_log = $query;
859 |
860 | $this->timer_start();
861 | if ( $statement_before_query ) {
862 | $query_for_log = "$statement_before_query; $query_for_log";
863 | $this->ex_mysql_query( $statement_before_query, $this->dbh );
864 | }
865 |
866 | $this->result = $this->ex_mysql_query( $query, $this->dbh );
867 |
868 | if ( $statement_after_query ) {
869 | $query_for_log = "$query_for_log; $statement_after_query";
870 | $this->ex_mysql_query( $statement_after_query, $this->dbh );
871 | }
872 | $elapsed = $this->timer_stop();
873 | ++$this->num_queries;
874 |
875 | if ( preg_match('/^\s*SELECT\s+SQL_CALC_FOUND_ROWS\s/i', $query) ) {
876 | if ( false === strpos($query, "NO_SELECT_FOUND_ROWS") ) {
877 | $this->timer_start();
878 | $this->last_found_rows_result = $this->ex_mysql_query( "SELECT FOUND_ROWS()", $this->dbh );
879 | $elapsed += $this->timer_stop();
880 | ++$this->num_queries;
881 | $query .= "; SELECT FOUND_ROWS()";
882 | }
883 | } else {
884 | $this->last_found_rows_result = null;
885 | }
886 |
887 | $this->dbhname_heartbeats[$this->dbhname]['last_used'] = microtime( true );
888 |
889 | if ( $this->save_queries ) {
890 | if ( is_callable($this->save_query_callback) ) {
891 | $saved_query = call_user_func_array( $this->save_query_callback, array( $query_for_log, $elapsed, $this->save_backtrace ? debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ) : null, &$this ) );
892 | if ( $saved_query !== null ) {
893 | $this->queries[] = $saved_query;
894 | }
895 | } else {
896 | $this->queries[] = array( $query_for_log, $elapsed, $this->get_caller() );
897 | }
898 | }
899 | }
900 |
901 | // If there is an error then take note of it
902 | if ( $this->last_error = $this->ex_mysql_error( $this->dbh ) ) {
903 | $this->last_errno = $this->ex_mysql_errno( $this->dbh );
904 | $this->dbhname_heartbeats[$this->dbhname]['last_errno'] = $this->last_errno;
905 | $this->print_error($this->last_error);
906 | $this->num_failed_queries++;
907 | do_action( 'sql_query_log', $query, false, $this->last_error );
908 | return false;
909 | }
910 |
911 | if ( preg_match('/^\s*(insert|delete|update|replace|alter)\s/i',$query) ) {
912 | $this->rows_affected = $this->ex_mysql_affected_rows( $this->dbh );
913 |
914 | // Take note of the insert_id
915 | if ( preg_match('/^\s*(insert|replace)\s/i',$query) ) {
916 | $this->insert_id = $this->ex_mysql_insert_id( $this->dbh );
917 | }
918 | // Return number of rows affected
919 | $return_val = $this->rows_affected;
920 | } else if ( is_bool( $this->result ) ) {
921 | $return_val = $this->result;
922 | $this->result = null;
923 | } else {
924 | $i = 0;
925 | $this->col_info = array();
926 | while ( $i < $this->ex_mysql_num_fields( $this->result ) ) {
927 | $this->col_info[$i] = $this->ex_mysql_fetch_field( $this->result );
928 | $i++;
929 | }
930 | $num_rows = 0;
931 | $this->last_result = array();
932 | while ( $row = $this->ex_mysql_fetch_object( $this->result ) ) {
933 | $this->last_result[$num_rows] = $row;
934 | $num_rows++;
935 | }
936 |
937 | $this->ex_mysql_free_result( $this->result );
938 | $this->result = null;
939 |
940 | // Log number of rows the query returned
941 | $this->num_rows = $num_rows;
942 |
943 | // Return number of rows selected
944 | $return_val = $this->num_rows;
945 | }
946 |
947 | do_action( 'sql_query_log', $query, $return_val, $this->last_error );
948 | return $return_val;
949 | }
950 |
951 | /**
952 | * Whether or not MySQL database is at least the required minimum version.
953 | * The additional argument allows the caller to check a specific database.
954 | *
955 | * @since 2.5.0
956 | * @uses $wp_version
957 | *
958 | * @return WP_Error
959 | */
960 | function check_database_version( $dbh_or_table = false ) {
961 | global $wp_version;
962 | // Make sure the server has MySQL 4.1.2
963 | $mysql_version = preg_replace( '|[^0-9\.]|', '', $this->db_version( $dbh_or_table ) );
964 | if ( version_compare($mysql_version, '4.1.2', '<') )
965 | return new WP_Error( 'database_version', sprintf(__('ERROR: WordPress %s requires MySQL 4.1.2 or higher'), $wp_version) );
966 | }
967 |
968 | /**
969 | * This function is called when WordPress is generating the table schema to determine wether or not the current database
970 | * supports or needs the collation statements.
971 | * The additional argument allows the caller to check a specific database.
972 | * @return bool
973 | */
974 | function supports_collation( $dbh_or_table = false ) {
975 | return $this->has_cap( 'collation', $dbh_or_table );
976 | }
977 |
978 | /**
979 | * Generic function to determine if a database supports a particular feature
980 | * The additional argument allows the caller to check a specific database.
981 | * @param string $db_cap the feature
982 | * @param false|string|resource $dbh_or_table the databaese (the current database, the database housing the specified table, or the database of the mysql resource)
983 | * @return bool
984 | */
985 | function has_cap( $db_cap, $dbh_or_table = false ) {
986 | $version = $this->db_version( $dbh_or_table );
987 |
988 | switch ( strtolower( $db_cap ) ) :
989 | case 'collation' :
990 | case 'group_concat' :
991 | case 'subqueries' :
992 | return version_compare($version, '4.1', '>=');
993 | case 'set_charset' :
994 | return version_compare($version, '5.0.7', '>=');
995 | endswitch;
996 |
997 | return false;
998 | }
999 |
1000 | /**
1001 | * The database version number
1002 | * @param false|string|resource $dbh_or_table the databaese (the current database, the database housing the specified table, or the database of the mysql resource)
1003 | * @return false|string false on failure, version number on success
1004 | */
1005 | function db_version( $dbh_or_table = false ) {
1006 | if ( !$dbh_or_table && $this->dbh )
1007 | $dbh =& $this->dbh;
1008 | elseif ( $this->is_mysql_connection( $dbh_or_table ) )
1009 | $dbh =& $dbh_or_table;
1010 | else
1011 | $dbh = $this->db_connect( "SELECT FROM $dbh_or_table $this->users" );
1012 |
1013 | if ( $dbh )
1014 | return preg_replace( '/[^0-9.].*/', '', $this->ex_mysql_get_server_info( $dbh ) );
1015 | return false;
1016 | }
1017 |
1018 | /**
1019 | * Get the name of the function that called wpdb.
1020 | * @return string the name of the calling function
1021 | */
1022 | function get_caller() {
1023 | // requires PHP 4.3+
1024 | if ( !is_callable('debug_backtrace') )
1025 | return '';
1026 |
1027 | $hyper_callbacks = array();
1028 | foreach ( $this->hyper_callbacks as $group_name => $group_callbacks )
1029 | $hyper_callbacks = array_merge( $hyper_callbacks, $group_callbacks );
1030 |
1031 | $bt = debug_backtrace( false );
1032 | $caller = '';
1033 |
1034 | foreach ( (array) $bt as $trace ) {
1035 | if ( isset($trace['class']) && is_a( $this, $trace['class'] ) )
1036 | continue;
1037 | elseif ( !isset($trace['function']) )
1038 | continue;
1039 | elseif ( strtolower($trace['function']) == 'call_user_func_array' )
1040 | continue;
1041 | elseif ( strtolower($trace['function']) == 'apply_filters' )
1042 | continue;
1043 | elseif ( strtolower($trace['function']) == 'do_action' )
1044 | continue;
1045 |
1046 | if ( in_array( strtolower($trace['function']), $hyper_callbacks ) )
1047 | continue;
1048 |
1049 | if ( isset($trace['class']) )
1050 | $caller = $trace['class'] . '::' . $trace['function'];
1051 | else
1052 | $caller = $trace['function'];
1053 | break;
1054 | }
1055 | return $caller;
1056 | }
1057 |
1058 | function log_and_bail( $msg ) {
1059 | $logged = $this->run_callbacks( 'log_and_bail', $msg );
1060 |
1061 | if ( ! $logged )
1062 | error_log( "WordPress database error $msg for query {$this->last_query} made by " . $this->get_caller() );
1063 |
1064 | return $this->bail( $msg );
1065 | }
1066 |
1067 | /**
1068 | * Check the responsiveness of a tcp/ip daemon
1069 | * @return (string) 'up' when $host:$post responds within $float_timeout seconds,
1070 | * otherwise a string with details about the failure.
1071 | */
1072 | function check_tcp_responsiveness( $host, $port, $float_timeout ) {
1073 | if ( function_exists( 'apcu_store' ) ) {
1074 | $use_apc = true;
1075 | $apcu_key = "tcp_responsive_{$host}{$port}";
1076 | $apcu_ttl = 10;
1077 | } else {
1078 | $use_apc = false;
1079 | }
1080 |
1081 | if ( $use_apc ) {
1082 | $server_state = apcu_fetch( $apcu_key );
1083 | if ( $server_state )
1084 | return $server_state;
1085 | }
1086 |
1087 | $socket = @ fsockopen( $host, $port, $errno, $errstr, $float_timeout );
1088 | if ( $socket === false ) {
1089 | $server_state = "down [ > $float_timeout ] ($errno) '$errstr'";
1090 | if ( $use_apc )
1091 | apcu_store( $apcu_key, $server_state, $apcu_ttl );
1092 |
1093 | return $server_state;
1094 | }
1095 |
1096 | fclose( $socket );
1097 |
1098 | if ( $use_apc )
1099 | apcu_store( $apcu_key, 'up', $apcu_ttl );
1100 |
1101 | return 'up';
1102 | }
1103 |
1104 | function get_server_state( $host, $port, $timeout ) {
1105 | // We still do the check_tcp_responsiveness() until we have
1106 | // mysql connect function with less than 1 second timeout
1107 | if ( $this->check_tcp_responsiveness ) {
1108 | $server_state = $this->check_tcp_responsiveness( $host, $port, $timeout );
1109 | if ( 'up' !== $server_state )
1110 | return $server_state;
1111 | }
1112 |
1113 | if ( ! function_exists( 'apcu_store' ) )
1114 | return 'up';
1115 |
1116 | $server_state = apcu_fetch( "server_state_$host$port" );
1117 | if ( ! $server_state )
1118 | return 'up';
1119 |
1120 | return $server_state;
1121 | }
1122 |
1123 | function mark_server_as_down( $host, $port, $apcu_ttl = 10 ) {
1124 | if ( ! function_exists( 'apcu_store' ) )
1125 | return;
1126 |
1127 | apcu_add( "server_state_$host$port", 'down', $apcu_ttl );
1128 | }
1129 |
1130 | function set_connect_timeout( $tag, $use_master, $tries_remaining ) {
1131 | static $default_connect_timeout;
1132 |
1133 | if ( ! isset ( $default_connect_timeout ) )
1134 | $default_connect_timeout = $this->ex_mysql_connect_timeout();
1135 |
1136 | switch ( $tag ) {
1137 | case 'pre_connect':
1138 | if ( ! $use_master && $tries_remaining )
1139 | $this->ex_mysql_connect_timeout( 1 );
1140 | break;
1141 | case 'post_connect':
1142 | default:
1143 | if ( ! $use_master && $tries_remaining )
1144 | $this->ex_mysql_connect_timeout( $default_connect_timeout );
1145 | break;
1146 | }
1147 | }
1148 |
1149 | function get_lag_cache() {
1150 | $this->lag = $this->run_callbacks( 'get_lag_cache' );
1151 |
1152 | return $this->check_lag();
1153 | }
1154 |
1155 | function get_lag() {
1156 | $this->lag = $this->run_callbacks( 'get_lag' );
1157 |
1158 | return $this->check_lag();
1159 | }
1160 |
1161 | function check_lag() {
1162 | if ( $this->lag === false )
1163 | return HYPERDB_LAG_UNKNOWN;
1164 |
1165 | if ( $this->lag > $this->lag_threshold )
1166 | return HYPERDB_LAG_BEHIND;
1167 |
1168 | return HYPERDB_LAG_OK;
1169 | }
1170 |
1171 | function should_use_mysqli() {
1172 | if ( ! function_exists( 'mysqli_connect' ) )
1173 | return false;
1174 |
1175 | if ( defined( 'WP_USE_EXT_MYSQL' ) && WP_USE_EXT_MYSQL )
1176 | return false;
1177 |
1178 | return true;
1179 | }
1180 |
1181 | function should_mysql_ping() {
1182 | // Shouldn't happen
1183 | if ( ! isset( $this->dbhname_heartbeats[$this->dbhname] ) )
1184 | return true;
1185 |
1186 | // MySQL server has gone away
1187 | if ( isset( $this->dbhname_heartbeats[$this->dbhname]['last_errno'] ) &&
1188 | HYPERDB_SERVER_GONE_ERROR == $this->dbhname_heartbeats[$this->dbhname]['last_errno'] )
1189 | {
1190 | unset( $this->dbhname_heartbeats[$this->dbhname]['last_errno'] );
1191 | return true;
1192 | }
1193 |
1194 | // More than 0.1 seconds of inactivity on that dbhname
1195 | if ( microtime( true ) - $this->dbhname_heartbeats[$this->dbhname]['last_used'] > 0.1 )
1196 | return true;
1197 |
1198 | return false;
1199 | }
1200 |
1201 | function is_mysql_connection( $dbh ) {
1202 | if ( ! $this->use_mysqli )
1203 | return is_resource( $dbh );
1204 |
1205 | return $dbh instanceof mysqli;
1206 | }
1207 |
1208 | function is_mysql_result( $result ) {
1209 | if ( ! $this->use_mysqli )
1210 | return is_resource( $result );
1211 |
1212 | return $result instanceof mysqli_result;
1213 | }
1214 |
1215 | function is_mysql_set_charset_callable() {
1216 | if ( ! $this->use_mysqli )
1217 | return function_exists( 'mysql_set_charset' );
1218 |
1219 | return function_exists( 'mysqli_set_charset' );
1220 | }
1221 |
1222 | // MySQL execution functions.
1223 | // They perform the appropriate calls based on whether we use MySQLi.
1224 |
1225 | function ex_mysql_query( $query, $dbh ) {
1226 | if ( ! $this->use_mysqli )
1227 | return mysql_query( $query, $dbh );
1228 |
1229 | return mysqli_query( $dbh, $query );
1230 | }
1231 |
1232 | function ex_mysql_unbuffered_query( $query, $dbh ) {
1233 | if ( ! $this->use_mysqli )
1234 | return mysql_unbuffered_query( $query, $dbh );
1235 |
1236 | return mysqli_query( $dbh, $query, MYSQLI_USE_RESULT );
1237 | }
1238 |
1239 | function ex_mysql_connect( $db_host, $db_user, $db_password, $persistent ) {
1240 | if ( ! $this->use_mysqli ) {
1241 | $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect';
1242 | return @$connect_function( $db_host, $db_user, $db_password, true );
1243 | }
1244 |
1245 | $dbh = mysqli_init();
1246 |
1247 | // mysqli_real_connect doesn't support the host param including a port or socket
1248 | // like mysql_connect does. This duplicates how mysql_connect detects a port and/or socket file.
1249 | $port = null;
1250 | $socket = null;
1251 | $port_or_socket = strstr( $db_host, ':' );
1252 | if ( ! empty( $port_or_socket ) ) {
1253 | $db_host = substr( $db_host, 0, strpos( $db_host, ':' ) );
1254 | $port_or_socket = substr( $port_or_socket, 1 );
1255 | if ( 0 !== strpos( $port_or_socket, '/' ) ) {
1256 | $port = intval( $port_or_socket );
1257 | $maybe_socket = strstr( $port_or_socket, ':' );
1258 | if ( ! empty( $maybe_socket ) ) {
1259 | $socket = substr( $maybe_socket, 1 );
1260 | }
1261 | } else {
1262 | $socket = $port_or_socket;
1263 | }
1264 | }
1265 |
1266 | if ( $persistent )
1267 | $db_host = "p:{$db_host}";
1268 |
1269 | $retval = mysqli_real_connect( $dbh, $db_host, $db_user, $db_password, null, $port, $socket );
1270 |
1271 | if ( ! $retval || $dbh->connect_errno )
1272 | return false;
1273 |
1274 | return $dbh;
1275 | }
1276 |
1277 | function ex_mysql_select_db( $db_name, $dbh ) {
1278 | if ( ! $this->use_mysqli )
1279 | return @mysql_select_db( $db_name, $dbh );
1280 |
1281 | return @mysqli_select_db( $dbh, $db_name );
1282 | }
1283 |
1284 | function ex_mysql_close( $dbh ) {
1285 | if ( ! $this->use_mysqli )
1286 | return mysql_close( $dbh );
1287 |
1288 | return mysqli_close( $dbh );
1289 | }
1290 |
1291 | function ex_mysql_set_charset( $charset, $dbh ) {
1292 | if ( ! $this->use_mysqli )
1293 | return mysql_set_charset( $charset, $dbh );
1294 |
1295 | return mysqli_set_charset( $dbh, $charset );
1296 | }
1297 |
1298 | function ex_mysql_errno( $dbh = null ) {
1299 | if ( ! $this->use_mysqli )
1300 | return is_resource( $dbh ) ? mysql_errno( $dbh ) : mysql_errno();
1301 |
1302 | if ( is_null( $dbh ) )
1303 | return mysqli_connect_errno();
1304 |
1305 | return mysqli_errno( $dbh );
1306 | }
1307 |
1308 | function ex_mysql_error( $dbh = null ) {
1309 | if ( ! $this->use_mysqli )
1310 | return is_resource( $dbh ) ? mysql_error( $dbh ) : mysql_error();
1311 |
1312 | if ( is_null( $dbh ) )
1313 | return mysqli_connect_error();
1314 |
1315 | if ( ! $this->is_mysql_connection( $dbh ) )
1316 | return false;
1317 |
1318 | return mysqli_error( $dbh );
1319 | }
1320 |
1321 | function ex_mysql_ping( $dbh ) {
1322 | if ( ! $this->use_mysqli )
1323 | return @mysql_ping( $dbh );
1324 |
1325 | return @mysqli_ping( $dbh );
1326 | }
1327 |
1328 | function ex_mysql_affected_rows( $dbh ) {
1329 | if ( ! $this->use_mysqli )
1330 | return mysql_affected_rows( $dbh );
1331 |
1332 | return mysqli_affected_rows( $dbh );
1333 | }
1334 |
1335 | function ex_mysql_insert_id( $dbh ) {
1336 | if ( ! $this->use_mysqli )
1337 | return mysql_insert_id( $dbh );
1338 |
1339 | return mysqli_insert_id( $dbh );
1340 | }
1341 |
1342 | function ex_mysql_num_fields( $result ) {
1343 | if ( ! $this->use_mysqli )
1344 | return @mysql_num_fields( $result );
1345 |
1346 | return @mysqli_num_fields( $result );
1347 | }
1348 |
1349 | function ex_mysql_fetch_field( $result ) {
1350 | if ( ! $this->use_mysqli )
1351 | return @mysql_fetch_field( $result );
1352 |
1353 | return @mysqli_fetch_field( $result );
1354 | }
1355 |
1356 | function ex_mysql_fetch_assoc( $result ) {
1357 | if ( ! $this->use_mysqli )
1358 | return mysql_fetch_assoc( $result );
1359 |
1360 | if ( ! $this->is_mysql_result( $result ) )
1361 | return false;
1362 |
1363 | $object = mysqli_fetch_assoc( $result );
1364 |
1365 | return ! is_null( $object ) ? $object : false;
1366 | }
1367 |
1368 | function ex_mysql_fetch_object( $result ) {
1369 | if ( ! $this->use_mysqli )
1370 | return @mysql_fetch_object( $result );
1371 |
1372 | if ( ! $this->is_mysql_result( $result ) )
1373 | return false;
1374 |
1375 | $object = @mysqli_fetch_object( $result );
1376 |
1377 | return ! is_null( $object ) ? $object : false;
1378 | }
1379 |
1380 | function ex_mysql_fetch_row( $result ) {
1381 | if ( ! $this->use_mysqli )
1382 | return mysql_fetch_row( $result );
1383 |
1384 | if ( ! $this->is_mysql_result( $result ) )
1385 | return false;
1386 |
1387 | $row = mysqli_fetch_row( $result );
1388 |
1389 | return ! is_null( $row ) ? $row : false;
1390 |
1391 | }
1392 |
1393 | function ex_mysql_num_rows( $result ) {
1394 | if ( ! $this->use_mysqli )
1395 | return mysql_num_rows( $result );
1396 |
1397 | return mysqli_num_rows( $result );
1398 | }
1399 |
1400 | function ex_mysql_free_result( $result ) {
1401 | if ( ! $this->use_mysqli )
1402 | return @mysql_free_result( $result );
1403 |
1404 | return @mysqli_free_result( $result );
1405 | }
1406 |
1407 | function ex_mysql_get_server_info( $dbh ) {
1408 | if ( ! $this->use_mysqli )
1409 | return mysql_get_server_info( $dbh );
1410 |
1411 | return mysqli_get_server_info( $dbh );
1412 | }
1413 |
1414 | function ex_mysql_connect_timeout( $timeout = null ) {
1415 | if ( is_null( $timeout ) ) {
1416 | if ( ! $this->use_mysqli )
1417 | return ini_get( 'mysql.connect_timeout' );
1418 |
1419 | return ini_get( 'default_socket_timeout' );
1420 | }
1421 |
1422 | if ( ! $this->use_mysqli )
1423 | return ini_set( 'mysql.connect_timeout', $timeout );
1424 |
1425 | return ini_set( 'default_socket_timeout', $timeout );
1426 | }
1427 | // Helper functions for configuration
1428 |
1429 | } // class hyperdb
1430 |
1431 | $wpdb = new hyperdb();
1432 |
1433 | require( DB_CONFIG_FILE );
1434 |
--------------------------------------------------------------------------------
/ansible-playbooks/roles/web/files/default.conf:
--------------------------------------------------------------------------------
1 | upstream index_php_upstream {
2 | server 127.0.0.1:8090; # NGINX Unit backend address for index.php with
3 | # 'script' parameter
4 | }
5 |
6 | upstream direct_php_upstream {
7 | server 127.0.0.1:8091; # NGINX Unit backend address for generic PHP file handling
8 | }
9 |
10 | server {
11 | listen 80;
12 | server_name localhost 127.0.0.1;
13 | root /var/www/wordpress/;
14 |
15 |
16 | location /nginx_status {
17 | stub_status on;
18 | access_log on;
19 | allow all;
20 | }
21 |
22 | location / {
23 | try_files $uri @index_php;
24 | }
25 |
26 | location @index_php {
27 | proxy_pass http://index_php_upstream;
28 | proxy_set_header Host $host;
29 | }
30 |
31 | location /wp-admin {
32 | index index.php;
33 | }
34 |
35 | location ~* .php$ {
36 | try_files $uri =404;
37 | proxy_pass http://direct_php_upstream;
38 | proxy_set_header Host $host;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/ansible-playbooks/roles/web/files/wordpress.config:
--------------------------------------------------------------------------------
1 | {
2 | "listeners": {
3 | "127.0.0.1:8090": {
4 | "application": "script_index_php"
5 | },
6 | "127.0.0.1:8091": {
7 | "application": "direct_php"
8 | }
9 | },
10 |
11 | "applications": {
12 | "script_index_php": {
13 | "type": "php",
14 | "processes": {
15 | "max": 20,
16 | "spare": 5
17 | },
18 | "user": "www-data",
19 | "group": "www-data",
20 | "root": "/var/www/wordpress",
21 | "script": "index.php"
22 | },
23 | "direct_php": {
24 | "type": "php",
25 | "processes": {
26 | "max": 5,
27 | "spare": 0
28 | },
29 | "user": "www-data",
30 | "group": "www-data",
31 | "root": "/var/www/wordpress",
32 | "index": "index.php"
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/ansible-playbooks/roles/web/files/wp-config.php:
--------------------------------------------------------------------------------
1 | s$>n-8xh| 7ZKYU{9IPXo66v7jkYT|]J!}L#6|4wGaRx3');
51 | define('SECURE_AUTH_KEY', '+-eWffQEX|}nzS.o_Sqk.[Rz#0-/e|>SUVfl@D*1~Jf[6&u>Pw -,_N]MWy)+Gb}');
52 | define('LOGGED_IN_KEY', 'qV[Hq*-O?j`R:ohy^6-4chXnI RM($HVy$z<9+q?bQm!,6]7+p5x@)stPJSAd)8~');
53 | define('NONCE_KEY', 'K-`BdN^2} Gvcw*p&fcMcp.2t/x+qx (s,fkjdZPKa|>+W?J!+uwlz0o7ZjZk{Q_');
54 | define('AUTH_SALT', 'D!P{C`D _@+mKh13WHyRj=B}2HZKzRna]b.PVK+gP9os20.uIrcl/T`s?.g6MV7t');
55 | define('SECURE_AUTH_SALT', 'Ak#nF(@dzS:+#~#D/1AsyNQ2tmmqWcWShB-`;X8t$E^A+uPGMrj)iio$)OV5TZq2-x+m!mvBUiz6rV>5q$lB-o|kRG:?l:g^F>P0g[)t');
57 | define('NONCE_SALT', '/%8l|Dis^$|:}m6C4NR(DD]h@!~@r4_2p{&Fc9d;@z||8&KX|0r^sUuVmrr[ y`-');
58 |
59 | /**#@-*/
60 |
61 | /**
62 | * WordPress Database Table prefix.
63 | *
64 | * You can have multiple installations in one database if you give each
65 | * a unique prefix. Only numbers, letters, and underscores please!
66 | */
67 | $table_prefix = 'wp_';
68 |
69 | /**
70 | * For developers: WordPress debugging mode.
71 | *
72 | * Change this to true to enable the display of notices during development.
73 | * It is strongly recommended that plugin and theme developers use WP_DEBUG
74 | * in their development environments.
75 | *
76 | * For information on other constants that can be used for debugging,
77 | * visit the Codex.
78 | *
79 | * @link https://codex.wordpress.org/Debugging_in_WordPress
80 | */
81 | define( 'WP_DEBUG', false );
82 |
83 | /* That's all, stop editing! Happy publishing. */
84 |
85 | /** Absolute path to the WordPress directory. */
86 | if ( ! defined( 'ABSPATH' ) ) {
87 | define( 'ABSPATH', dirname( __FILE__ ) . '/' );
88 | }
89 |
90 | /** Sets up WordPress vars and included files. */
91 | require_once( ABSPATH . 'wp-settings.php' );
92 |
--------------------------------------------------------------------------------
/ansible-playbooks/roles/web/tasks/configure-nginx.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # Configure nginx stuff
3 |
4 | - name: Configure NGINX and NGINX Unit to auto start and reload
5 | shell: |
6 | systemctl enable unit
7 | systemctl enable nginx
8 | become: yes
9 |
10 | - name: start unit app server
11 | shell: systemctl start unit
12 |
13 | - name: Update Nginx Unit Config
14 | shell: curl -X PUT --data-binary @/usr/share/doc/unit-php/examples/unit.config --unix-socket /run/control.unit.sock http://localhost/config
15 | become: yes
16 |
17 | - name: Copy nginx default.conf to server
18 | copy:
19 | src: default.conf
20 | dest: /etc/nginx/conf.d
21 | owner: root
22 | group: root
23 | mode: 0755
24 |
25 | - name: Add ip as nginx server_name to default.conf
26 | lineinfile:
27 | dest: /etc/nginx/conf.d/default.conf
28 | regexp: '^(.*)server_name localhost 127.0.0.1;(.*)$'
29 | line: server_name localhost {{ app_name }} {{ ansible_host }};
30 | backrefs: yes
31 |
32 | - name: update nginx with wordpress config
33 | shell: curl -X PUT --data-binary @/var/www/wordpress/wordpress.config --unix-socket /run/control.unit.sock http://localhost/config
34 | become: yes
35 |
36 | - name: Restart nginx server
37 | shell: service nginx restart
38 |
39 |
40 |
--------------------------------------------------------------------------------
/ansible-playbooks/roles/web/tasks/configure-wordpress.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # Configure wordpress stuff
3 | - name: Create Wordpress Directory
4 | file:
5 | path: /var/www/
6 | state: directory
7 | owner: www-data
8 | group: www-data
9 | mode: 0775
10 | recurse: yes
11 |
12 | - name: Download and untar Wordpress distro
13 | unarchive:
14 | src: http://wordpress.org/latest.tar.gz
15 | dest: /var/www
16 | remote_src: yes
17 |
18 | - name: Copy wp-config.php to server
19 | copy:
20 | src: wp-config.php
21 | dest: /var/www/wordpress/wp-config.php
22 | owner: www-data
23 | group: www-data
24 | mode: 0755
25 |
26 | - name: Copy wordpress.config to server
27 | copy:
28 | src: wordpress.config
29 | dest: /var/www/wordpress/wordpress.config
30 | owner: root
31 | group: root
32 | mode: 0755
33 |
34 | - name: Copy db-config
35 | copy:
36 | src: db-config.php
37 | dest: /var/www/wordpress/db-config.php
38 | owner: www-data
39 | group: www-data
40 | mode: 0755
41 |
42 | - name: Copy db-php
43 | copy:
44 | src: db.php
45 | dest: /var/www/wordpress/wp-content/
46 | owner: www-data
47 | group: www-data
48 | mode: 0755
49 |
50 | - name: Modify wp-config for master db
51 | lineinfile:
52 | dest: /var/www/wordpress/wp-config.php
53 | regexp: ^(.*)define\( 'DB_HOST', '\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b'\);(.*)$
54 | line: "define( 'DB_HOST', '{{ master_db }}');"
55 |
56 | - name: Add slave_db_host to wp-config
57 | lineinfile:
58 | dest: /var/www/wordpress/wp-config.php
59 | regexp: ^(.*)define\( 'SLAVE_DB_HOST', '\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b'\);(.*)$
60 | line: "define( 'SLAVE_DB_HOST', '{{ slave_db }}');"
61 |
62 | - name: Set database password in wp-config
63 | lineinfile:
64 | dest: /var/www/wordpress/wp-config.php
65 | regex: ^(.*)define\( 'DB_PASSWORD', 'dbpassword' \);(.*)$
66 | line: "define( 'DB_PASSWORD', '{{ dbpassword }}' );"
67 |
68 | - name: Set wordpress file permissions
69 | shell: |
70 | chown -R www-data:www-data /var/www/wordpress
71 | find /var/www/wordpress -type d -exec chmod g+s {} \;
72 | chown -R www-data:www-data /var/www/wordpress
73 | chmod g+w /var/www/wordpress/wp-content
74 | chmod -R g+w /var/www/wordpress/wp-content/themes
75 | chmod -R g+w /var/www/wordpress/wp-content/plugins
76 | become: yes
77 |
78 | - name: Add LogDNA tag for webapp
79 | shell: logdna-agent -t webapp
80 | become: yes
81 |
82 | - name: restart LogDNA
83 | shell: service logdna-agent restart
84 | become: yes
85 |
86 | - name: Install & Configure sysdig on Webapp server
87 | shell: |
88 | curl -sL https://ibm.biz/install-sysdig-agent | sudo bash -s -- --access_key {{ sysdig_key }} -c ingest.us-south.monitoring.cloud.ibm.com --collector_port 6443 --secure true -ac "sysdig_capture_enabled: false" --tags role:webapp,location:{{ zone }}
89 | become: yes
--------------------------------------------------------------------------------
/ansible-playbooks/roles/web/tasks/main.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # Install & Configure NGINX & Wordpress
3 |
4 | - include: configure-wordpress.yaml
5 | - include: configure-nginx.yaml
--------------------------------------------------------------------------------
/ansible-playbooks/site.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # This playbook deploys the whole application stack in this site.
3 |
4 | - name: Apply common configuration to all nodes in inventory
5 | hosts: all
6 | remote_user: root
7 | gather_facts: no
8 |
9 | roles:
10 | - common
11 |
12 | - name: Configure and deploy the web and application code to webapptier
13 | hosts: webapptier
14 | remote_user: root
15 | gather_facts: no
16 |
17 | roles:
18 | - web
19 |
20 | - name: Configure Mysql Servers in dbtier
21 | hosts: dbtier
22 | remote_user: root
23 | gather_facts: no
24 |
25 | roles:
26 | - db
27 |
28 |
--------------------------------------------------------------------------------
/cis.tf:
--------------------------------------------------------------------------------
1 | # IBM Cloud Resource Group the CIS instance will be created under
2 | # Retrieve CIS resource Group and Cloud Internet Services (CIS) instance data
3 | data "ibm_resource_group" "resource" {
4 | name = "${var.cis_resource_group}"
5 | }
6 |
7 | data "ibm_cis" "cis_instance" {
8 | name = "${var.cis_instance_name}"
9 | resource_group_id = "${data.ibm_resource_group.resource.id}"
10 | }
11 |
12 | data "ibm_cis_domain" "cis_instance_domain" {
13 | domain = "${var.domain}"
14 | cis_id = "${data.ibm_cis.cis_instance.id}"
15 | }
16 |
17 | #setup healthcheck for nginx
18 | resource "ibm_cis_healthcheck" "root" {
19 | cis_id = "${data.ibm_cis.cis_instance.id}"
20 | description = "Websiteroot"
21 | expected_body = ""
22 | expected_codes = "200"
23 | path = "/nginx_status"
24 | }
25 |
26 | # Create Pool (of one) with VPC LBaaS instances using URL
27 | resource "ibm_cis_origin_pool" "vpc-lbaas" {
28 | cis_id = "${data.ibm_cis.cis_instance.id}"
29 | name = "${var.vpc-name}-webtier-lb"
30 | check_regions = ["NAF"]
31 |
32 | monitor = "${ibm_cis_healthcheck.root.id}"
33 |
34 | origins = {
35 | name = "${var.vpc-name}-webtier-lbaas-1"
36 | address = "${ibm_is_lb.webapptier-lb.hostname}"
37 | enabled = true
38 | }
39 |
40 | description = "${var.vpc-name}-webtier-lb"
41 | enabled = true
42 | }
43 |
44 | # GLB name - name advertised by DNS for the website: prefix + domain. Enable DDOS proxy
45 | resource "ibm_cis_global_load_balancer" "glb" {
46 | cis_id = "${data.ibm_cis.cis_instance.id}"
47 | domain_id = "${data.ibm_cis_domain.cis_instance_domain.id}"
48 | name = "${var.dns_name}${var.domain}"
49 | fallback_pool_id = "${ibm_cis_origin_pool.vpc-lbaas.id}"
50 | default_pool_ids = ["${ibm_cis_origin_pool.vpc-lbaas.id}"]
51 | session_affinity = "cookie"
52 | description = "Global Loadbalancer for webappdemo"
53 | proxied = true
54 | }
55 |
--------------------------------------------------------------------------------
/cloud-init-dbtier.tf:
--------------------------------------------------------------------------------
1 | data "template_cloudinit_config" "cloud-init-dbtier" {
2 | base64_encode = false
3 | gzip = false
4 |
5 | part {
6 | content = <