Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion dbaas/tsuru/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
from django.conf.urls import url, patterns
from views import (ListPlans, GetServiceStatus, GetServiceInfo, ServiceAdd,
ServiceAppBind, ServiceUnitBind, ServiceRemove)
ServiceAppBind, ServiceUnitBind, ServiceRemove,
ServiceJobBind)

urlpatterns = patterns(
'tsuru.views',
Expand All @@ -17,4 +18,6 @@
ServiceUnitBind.as_view()),
url(r'^resources/(?P<database_name>\w+)/bind-app$',
ServiceAppBind.as_view(), name='service-app-bind'),
url(r'^resources/(?P<database_name>\w+)/bind-job$',
ServiceJobBind.as_view(), name='service-job-bind'),
)
1 change: 1 addition & 0 deletions dbaas/tsuru/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
from serviceRemove import ServiceRemove
from serviceUnitBind import ServiceUnitBind
from serviceAppBind import ServiceAppBind
from serviceJobBind import ServiceJobBind
139 changes: 139 additions & 0 deletions dbaas/tsuru/views/serviceJobBind.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
from rest_framework.views import APIView
from rest_framework.renderers import JSONRenderer, JSONPRenderer
from rest_framework.response import Response
from logical.models import Database
from workflow.steps.util.base import ACLFromHellClient
from ..utils import (log_and_response, get_url_env, LOG, check_database_status)
from rest_framework import status


class AclFromHellNotAllowerForEnvException(Exception):
pass


class ServiceJobBind(APIView):
renderer_classes = (JSONRenderer, JSONPRenderer)
model = Database

def add_acl_for_hosts(self, database, job_name):

infra = database.infra
hosts = infra.hosts

acl_from_hell_client = ACLFromHellClient(database.environment)
if not acl_from_hell_client.aclfromhell_allowed:
raise AclFromHellNotAllowerForEnvException(
"ACL from hell credential not found for env {}".format(
database.environment
)
)

for host in hosts:
acl_from_hell_client.add_job_acl(database, job_name, host.hostname)
acl_from_hell_client.add_job_acl_for_vip_if_needed(database, job_name)

@staticmethod
def _handle_job_name(job_name):
return job_name[0] if isinstance(job_name, list) else job_name

def post(self, request, database_name, format=None):
"""This method binds a App to a database through tsuru."""
env = get_url_env(request)
data = request.DATA
LOG.debug("Tsuru Bind App POST Request DATA {}".format(data))

response = check_database_status(database_name, env)
if not isinstance(response, self.model):
return response

database = response
try:
self.add_acl_for_hosts(
database,
self._handle_job_name(data['job-name'])
)
except Exception as e:
return log_and_response(str(e))

hosts, ports = database.infra.get_driver().get_dns_port()
ports = str(ports)
if database.databaseinfra.engine.name == 'redis':
redis_password = database.databaseinfra.password
endpoint = database.get_endpoint_dns().replace(
'<password>', redis_password
)

env_vars = {
"DBAAS_REDIS_PASSWORD": redis_password,
"DBAAS_REDIS_ENDPOINT": endpoint,
"DBAAS_REDIS_HOST": hosts,
"DBAAS_REDIS_PORT": ports
}

if 'redis_sentinel' in database.infra.get_driver().topology_name():
env_vars = {
"DBAAS_SENTINEL_PASSWORD": redis_password,
"DBAAS_SENTINEL_ENDPOINT": endpoint,
"DBAAS_SENTINEL_ENDPOINT_SIMPLE": database.get_endpoint_dns_simple(), # noqa
"DBAAS_SENTINEL_SERVICE_NAME": database.databaseinfra.name,
"DBAAS_SENTINEL_HOSTS": hosts,
"DBAAS_SENTINEL_PORT": ports
}

else:
try:
credential = (
database.credentials.filter(privileges='Owner')
or database.credentials.all()
)[0]
except IndexError as e:
msg = ("Database {} in env {} does not have "
"credentials.").format(
database_name, env
)

return log_and_response(
msg=msg, e=e,
http_status=status.HTTP_500_INTERNAL_SERVER_ERROR
)

endpoint = database.get_endpoint_dns().replace(
'<user>:<password>', "{}:{}".format(
credential.user, credential.password
)
)

kind = ''
if endpoint.startswith('mysql'):
kind = 'MYSQL_'
if endpoint.startswith('mongodb'):
kind = 'MONGODB_'

env_vars = {
"DBAAS_{}USER".format(kind): credential.user,
"DBAAS_{}PASSWORD".format(kind): credential.password,
"DBAAS_{}ENDPOINT".format(kind): endpoint,
"DBAAS_{}HOSTS".format(kind): hosts,
"DBAAS_{}PORT".format(kind): ports
}

return Response(env_vars, status.HTTP_201_CREATED)

def delete(self, request, database_name, format=None):
"""This method unbinds a Job to a database through tsuru."""
env = get_url_env(request)
data = request.DATA
LOG.debug("Tsuru Unbind Job DELETE Request DATA {}".format(data))

response = check_database_status(database_name, env)
if not isinstance(response, Database):
return response

database = response

acl_from_hell_client = ACLFromHellClient(database.environment)
acl_from_hell_client.remove_job_acl(
database,
self._handle_job_name(data['job-name'])
)
return Response(status=status.HTTP_204_NO_CONTENT)
129 changes: 129 additions & 0 deletions dbaas/workflow/steps/util/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,14 @@ def add_acl(self, database, app_name, hostname):
hostname, resp.status_code
))

def add_job_acl_for_vip_if_needed(self, database, job_name):
databaseinfra = database.databaseinfra
if not databaseinfra.vips.exists():
return

vip_dns = self._get_vip_dns(databaseinfra)
self.add_job_acl(database, job_name, vip_dns)

def remove_acl(self, database, app_name):
rules = self.get_enabled_rules(database, app_name)

Expand All @@ -621,3 +629,124 @@ def remove_acl(self, database, app_name):
rule_id, host)
LOG.error(msg)
return None

def add_job_acl(self, database, job_name, hostname):
rules = self.get_job_enabled_rules(
database,
job_name,
extra_params={'destination.externaldns.name': hostname}
)
if rules:
msg = "Rule already registered. Database: {}, \
Job Name: {}, Hostname: {} - Rules: {}".format(
database, job_name, hostname, rules
)
LOG.info(msg)
return

infra = database.infra
driver = infra.get_driver()

payload = {
"source": {
"tsurujob": {
"jobname": job_name
}
},
"destination": {
"externaldns": {
"name": hostname,
"ports": map(
lambda p: {
'protocol': 'tcp',
'port': p
},
driver.ports
)
}
},
"target": "accept",
"metadata": {
'owner': 'dbaas',
"service-name": self.credential.project,
"instance-name": database.name
}
}

LOG.info("Tsuru Add ACL For Job: payload for host {}:{}".format(
hostname, payload))
resp = self._request(
requests.post,
self.credential.endpoint,
json=payload,
)
if not resp.ok:
error = "Cant set acl for job {}:{}-{}. Error: {}".format(
job_name, database, hostname, resp.content
)
LOG.error(msg)
raise CantSetACLError(error)

LOG.info("Tsuru Add ACL Status for host {}: {}".format(
hostname, resp.status_code
))

def remove_job_acl(self, database, job_name):
rules = self.get_job_enabled_rules(database, job_name)

if not rules:
msg = "Rule not found for {}.".format(
database.name)
LOG.debug(msg)

for rule in rules:
rule_id = rule.get('RuleID')
host = (rule.get('Destination', {})
.get('ExternalDNS', {})
.get('Name'))
if rule_id:
LOG.info('Tsuru Unbind App removing rule for job{}:{}-{}'.format(
job_name, database, host))
resp = self._request(
requests.delete,
'{}/{}'.format(self.credential.endpoint, rule_id)
)
if not resp.ok:
msg = "Error on delete rule {} for {}.".format(
rule_id, host)
LOG.error(msg)
return None

def get_job_enabled_rules(self, database, job_name, extra_params=None):
enabled_rules = []
params = {
'metadata.owner': 'dbaas',
'metadata.service-name': self.credential.project,
'metadata.instance-name': database.name,
'source.tsurujob.jobname': job_name,
}

if extra_params:
params.update(extra_params)

LOG.debug("Tsuru get rule for {} params:{}".format(
database.name, params))
resp = self._request(
requests.get,
self.credential.endpoint,
params=params,
)

if not resp.ok:
LOG.debug("Tsuru Status on Get Rules for database {}: {}".format(
database, resp.status_code))
return enabled_rules

all_rules = resp.json()
if all_rules:
for rule in all_rules:
if rule.get('Removed'):
continue
enabled_rules.append(rule)

return enabled_rules