Skip to content

Commit

Permalink
Strict mode (mesosphere#237)
Browse files Browse the repository at this point in the history
* Add sdk_dcos to get DCOS metadata

* Add strict mode deployment

Add the ability to deploy Jenkins on a strict mode cluster. This
handles detecting the security mode of the cluster, creating the
service account and secrets, and properly configuring the mesos
credentials in Jenkins.

* Add test for change_mesos_creds
  • Loading branch information
colin-msphere authored Jun 4, 2018
1 parent 310f419 commit 08e19bd
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 6 deletions.
19 changes: 18 additions & 1 deletion testing/jenkins.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@
log = logging.getLogger(__name__)


def install(service_name, role=None, mom=None, external_volume=None):
def install(service_name, role=None, mom=None, external_volume=None,
strict_settings=None, service_user=None):
"""Install a Jenkins instance and set the service name to
`service_name`. This does not wait for deployment to finish.
Args:
service_name: Unique service name
role: The role for the service to use (default is no role)
mom: Marathon on Marathon instance name
strict_settings: Dictionary that contains the secret name and
mesos principal to use in strict mode.
service_user: user
"""
options = {
"service": {
Expand All @@ -42,6 +46,15 @@ def install(service_name, role=None, mom=None, external_volume=None):
"local-persistent-volume-size": 1024
}

if strict_settings:
options["security"] = {
"secret-name": strict_settings['secret_name'],
"strict-mode": True
}

if service_user:
options['service']['user'] = service_user

if mom:
# get jenkins marathon app json with desired config.
# this will register at `/service/<service_name>`
Expand All @@ -60,6 +73,10 @@ def install(service_name, role=None, mom=None, external_volume=None):
additional_options=options,
wait_for_deployment=False)

if strict_settings:
jenkins_remote_access.change_mesos_creds(
strict_settings['mesos_principal'], service_name)


def uninstall(service_name, package_name='jenkins', role=None, mom=None):
"""Uninstall a Jenkins instance. This does not wait for deployment
Expand Down
45 changes: 45 additions & 0 deletions testing/jenkins_remote_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,41 @@ def failedRuns = activeJobs.findAll{job -> job.lastBuild != null && !(job.lastBu
}
"""

CREDENTIAL_CHANGE = """
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl
MesosCloud cloud = MesosCloud.get();
def changePassword = { new_username, new_password ->
def c = cloud.credentials
if ( c ) {
def credentials_store = Jenkins.instance.getExtensionList(
'com.cloudbees.plugins.credentials.SystemCredentialsProvider'
)[0].getStore()
def result = credentials_store.updateCredentials(
com.cloudbees.plugins.credentials.domains.Domain.global(),
c,
new UsernamePasswordCredentialsImpl(c.scope, c.id, c.description, new_username, new_password)
)
if (result) {
println "changed jenkins creds"
} else {
println "failed to change jenkins creds"
}
} else {
println "could not find credential for jenkins"
}
}
changePassword('$userName', 'abcdefg')
cloud.restartMesos()
"""


def add_slave_info(
labelString,
service_name,
Expand Down Expand Up @@ -171,6 +206,16 @@ def get_job_failures(service_name):
return make_post(JENKINS_JOB_FAILURES, service_name)


def change_mesos_creds(mesos_username, service_name):
return make_post(
Template(CREDENTIAL_CHANGE).substitute(
{
'userName': mesos_username,
}
),
service_name)


def make_post(
post_body,
service_name,
Expand Down
28 changes: 28 additions & 0 deletions testing/sdk_dcos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'''Utilities relating to getting information about DC/OS itself
************************************************************************
FOR THE TIME BEING WHATEVER MODIFICATIONS ARE APPLIED TO THIS FILE
SHOULD ALSO BE APPLIED TO sdk_dcos IN ANY OTHER PARTNER REPOS
************************************************************************
'''
from enum import Enum

import sdk_cmd


class DCOS_SECURITY(Enum):
disabled = 1
permissive = 2
strict = 3


def get_metadata():
return sdk_cmd.cluster_request('GET',
'dcos-metadata/bootstrap-config.json',
retry=False)


def get_security_mode() -> DCOS_SECURITY:
r = get_metadata().json()
mode = r['security']
return DCOS_SECURITY[mode]
38 changes: 33 additions & 5 deletions tests/scale/test_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,31 @@

import logging
import time
from threading import Thread
from threading import Thread, Lock
from typing import List, Set
from xml.etree import ElementTree

import config
import jenkins
import pytest
import sdk_dcos
import sdk_marathon
import sdk_quota
import sdk_security
import sdk_utils
import shakedown

from sdk_dcos import DCOS_SECURITY

log = logging.getLogger(__name__)

SHARED_ROLE = "jenkins-role"
# initial timeout waiting on deployments
DEPLOY_TIMEOUT = 15 * 60 # 15 mins
JOB_RUN_TIMEOUT = 10 * 60 # 10 mins

lock = Lock()


class ResultThread(Thread):
"""A thread that stores the result of the run command."""
Expand Down Expand Up @@ -102,6 +108,8 @@ def test_scaling_load(master_count,
mom: Marathon on Marathon instance name
external_volume: External volume on rexray (true) or local volume (false)
"""
security_mode = sdk_dcos.get_security_mode()

with shakedown.marathon_on_marathon(mom):
if cpu_quota is not 0.0:
_setup_quota(SHARED_ROLE, cpu_quota)
Expand All @@ -113,6 +121,7 @@ def test_scaling_load(master_count,
_install_jenkins,
external_volume=external_volume,
mom=mom,
security=security_mode,
daemon=True)
thread_failures = _wait_and_get_failures(install_threads,
timeout=DEPLOY_TIMEOUT)
Expand Down Expand Up @@ -205,18 +214,37 @@ def _spawn_threads(names, target, daemon=False, **kwargs) -> List[ResultThread]:
return thread_list


def _install_jenkins(service_name, mom=None, external_volume=None):
def _install_jenkins(service_name, security=None, **kwargs):
"""Install Jenkins service.
Args:
service_name: Service Name or Marathon ID (same thing)
mom: Marathon on Marathon instance name
external_volume: Enable external volumes
"""
log.info("Installing jenkins '{}'".format(service_name))
try:
jenkins.install(service_name, role=SHARED_ROLE, mom=mom,
external_volume=external_volume)
if security == DCOS_SECURITY.strict:
with lock:
log.info("Creating service accounts for '{}'"
.format(service_name))
sa_name = "{}-principal".format(service_name)
sa_secret = "jenkins-{}-secret".format(service_name)
sdk_security.create_service_account(
sa_name, sa_secret)

sdk_security.grant_permissions(
'nobody', '*', sa_name)

sdk_security.grant_permissions(
'nobody', SHARED_ROLE, sa_name)
kwargs['strict_settings'] = {
'secret_name': sa_secret,
'mesos_principal': sa_name,
}
kwargs['service_user'] = 'nobody'

log.info("Installing jenkins '{}'".format(service_name))
jenkins.install(service_name, role=SHARED_ROLE, **kwargs)
except Exception as e:
log.warning("Error encountered while installing Jenkins: {}".format(e))
raise e
Expand Down
9 changes: 9 additions & 0 deletions tests/scale/test_scale_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,21 @@ def test_install_custom_name():
finally:
sdk_install.uninstall(config.PACKAGE_NAME, svc_name)


@pytest.mark.sanity
def test_get_job_failures():
r = jenkins_remote_access.get_job_failures(config.SERVICE_NAME)
assert r.status_code == 200


@pytest.mark.sanity
def test_change_mesos_creds():
r = jenkins_remote_access.change_mesos_creds('myusername',
config.SERVICE_NAME)
assert r.status_code == 200
assert "changed jenkins creds" in r.text


def get_test_job_name():
return 'test-job-{}'.format(uuid.uuid4())

Expand Down

0 comments on commit 08e19bd

Please sign in to comment.