├── 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 | ![3tier Web App - Infrastructure](/docs/images/infrastructure-architecture.png) 41 | 42 | ### Application Architecture 43 | ![3tuer Web App - Application](docs/images/application-data-flow.png) 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 = <