Merge "Fix for cvp-ha for mcp with OC4"
diff --git a/.gitreview b/.gitreview
index 3a1eac3..b0ed746 100644
--- a/.gitreview
+++ b/.gitreview
@@ -1,4 +1,4 @@
[gerrit]
-host=gerrit.mcp.mirantis.net
+host=gerrit.mcp.mirantis.com
port=29418
project=mcp-ci/pipeline-library.git
diff --git a/src/com/mirantis/mcp/Validate.groovy b/src/com/mirantis/mcp/Validate.groovy
index c044d8d..3eb0034 100644
--- a/src/com/mirantis/mcp/Validate.groovy
+++ b/src/com/mirantis/mcp/Validate.groovy
@@ -8,11 +8,13 @@
/**
* Run docker container with basic (keystone) parameters
+ * For backward compatibility. Deprecated.
+ * Will be removed soon.
*
* @param target Host to run container
* @param dockerImageLink Docker image link. May be custom or default rally image
*/
-def runBasicContainer(master, target, dockerImageLink="xrally/xrally-openstack:0.9.1"){
+def runBasicContainer(master, target, dockerImageLink="xrally/xrally-openstack:0.10.1"){
def salt = new com.mirantis.mk.Salt()
def common = new com.mirantis.mk.Common()
def _pillar = salt.getPillar(master, 'I@keystone:server', 'keystone:server')
@@ -27,6 +29,37 @@
"-e OS_REGION_NAME=${keystone.region} -e OS_ENDPOINT_TYPE=admin --entrypoint /bin/bash ${dockerImageLink}")
}
+
+/**
+ * Run docker container with parameters
+ *
+ * @param target Host to run container
+ * @param dockerImageLink Docker image link. May be custom or default rally image
+ * @param name Name for container
+ * @param env_var Environment variables to set in container
+ * @param entrypoint Set entrypoint to /bin/bash or leave default
+**/
+
+
+def runContainer(master, target, dockerImageLink, name='cvp', env_var=[], entrypoint=true){
+ def salt = new com.mirantis.mk.Salt()
+ def common = new com.mirantis.mk.Common()
+ def variables = ''
+ def entry_point = ''
+ if ( salt.cmdRun(master, target, "docker ps -f name=${name} -q", false, null, false)['return'][0].values()[0] ) {
+ salt.cmdRun(master, target, "docker rm -f ${name}")
+ }
+ if (env_var.size() > 0) {
+ variables = ' -e ' + env_var.join(' -e ')
+ }
+ if (entrypoint) {
+ entry_point = '--entrypoint /bin/bash'
+ }
+ salt.cmdRun(master, target, "docker run -tid --net=host --name=${name} " +
+ "-u root ${entry_point} ${variables} ${dockerImageLink}")
+}
+
+
/**
* Get file content (encoded). The content encoded by Base64.
*
@@ -136,6 +169,7 @@
/**
* Execute mcp sanity tests
+ * Deprecated. Will be removed soon
*
* @param salt_url Salt master url
* @param salt_credentials Salt credentials
@@ -170,6 +204,47 @@
* @param env_vars Additional environment variables for cvp-sanity-checks
* @param output_dir Directory for results
*/
+def runPyTests(salt_url, salt_credentials, test_set="", env_vars="", name='cvp', container_node="", remote_dir='/root/qa_results/', artifacts_dir='validation_artifacts/') {
+ def xml_file = "${name}_report.xml"
+ def common = new com.mirantis.mk.Common()
+ def salt = new com.mirantis.mk.Salt()
+ def creds = common.getCredentials(salt_credentials)
+ def username = creds.username
+ def password = creds.password
+ if (container_node != "") {
+ def saltMaster
+ saltMaster = salt.connection(salt_url, salt_credentials)
+ def script = "pytest --junitxml ${xml_file} --tb=short -sv ${test_set}"
+ env_vars.addAll("SALT_USERNAME=${username}", "SALT_PASSWORD=${password}",
+ "SALT_URL=${salt_url}")
+ variables = ' -e ' + env_vars.join(' -e ')
+ salt.cmdRun(saltMaster, container_node, "docker exec ${variables} ${name} bash -c '${script}'", false)
+ salt.cmdRun(saltMaster, container_node, "docker cp ${name}:/var/lib/${xml_file} ${remote_dir}${xml_file}")
+ addFiles(saltMaster, container_node, remote_dir+xml_file, artifacts_dir)
+ }
+ else {
+ if (env_vars.size() > 0) {
+ variables = 'export ' + env_vars.join(';export ')
+ }
+ def script = ". ${env.WORKSPACE}/venv/bin/activate; ${variables}; " +
+ "pytest --junitxml ${artifacts_dir}${xml_file} --tb=short -sv ${env.WORKSPACE}/${test_set}"
+ withEnv(["SALT_USERNAME=${username}", "SALT_PASSWORD=${password}", "SALT_URL=${salt_url}"]) {
+ def statusCode = sh script:script, returnStatus:true
+ }
+ }
+}
+
+/**
+ * Execute pytest framework tests
+ * For backward compatibility
+ * Will be removed soon
+ *
+ * @param salt_url Salt master url
+ * @param salt_credentials Salt credentials
+ * @param test_set Test set to run
+ * @param env_vars Additional environment variables for cvp-sanity-checks
+ * @param output_dir Directory for results
+ */
def runTests(salt_url, salt_credentials, test_set="", output_dir="validation_artifacts/", env_vars="") {
def common = new com.mirantis.mk.Common()
def creds = common.getCredentials(salt_credentials)
@@ -241,34 +316,71 @@
}
/**
+ * Make all-in-one scenario cmd for rally tests
+ *
+ * @param scenarios_path Path to scenarios folder/file
+ * @param skip_scenarios Comma-delimited list of scenarios names to skip
+ * @param bundle_file Bundle name to create
+*/
+def bundle_up_scenarios(scenarios_path, skip_scenarios, bundle_file) {
+ def skip_names = ''
+ def skip_dirs = ''
+ def result = ''
+ if (skip_scenarios != ''){
+ for ( scen in skip_scenarios.split(',') ) {
+ if ( scen.contains('yaml')) {
+ skip_names += "! -name ${scen} "
+ }
+ else {
+ skip_dirs += "-path ${scenarios_path}/${scen} -prune -o "
+ }
+ }
+ }
+ result = "if [ -f ${scenarios_path} ]; then cp ${scenarios_path} ${bundle_file}; " +
+ "else " +
+ "find -L ${scenarios_path} " + skip_dirs +
+ " -name '*.yaml' " + skip_names +
+ "-exec cat {} >> ${bundle_file} \\; ; " +
+ "sed -i '/---/d' ${bundle_file}; fi; "
+
+ return result
+}
+
+/**
* Execute rally tests
*
* @param target Host to run tests
* @param dockerImageLink Docker image link
* @param platform What do we have underneath (openstack/k8s)
* @param output_dir Directory for results
- * @param repository Git repository with files for Rally
- * @param branch Git branch which will be used during the checkout
+ * @param config_repo Git repository with with files for Rally
+ * @param config_branch Git config repo branch which will be used during the checkout
+ * @param plugins_repo Git repository with Rally plugins
+ * @param plugins_branch Git plugins repo branch which will be used during the checkout
* @param scenarios Directory inside repo with specific scenarios
+ * @param sl_scenarios Directory inside repo with specific scenarios for stacklight
* @param tasks_args_file Argument file that is used for throttling settings
* @param ext_variables The list of external variables
* @param results The reports directory
*/
-def runRallyTests(master, target, dockerImageLink, platform, output_dir, repository, branch, scenarios = '', tasks_args_file = '', ext_variables = [], results = '/root/qa_results') {
+def runRallyTests(master, target, dockerImageLink, platform, output_dir, config_repo, config_branch, plugins_repo, plugins_branch, scenarios, sl_scenarios = '', tasks_args_file = '', ext_variables = [], results = '/root/qa_results', skip_list = '') {
def salt = new com.mirantis.mk.Salt()
def output_file = 'docker-rally.log'
def dest_folder = '/home/rally/qa_results'
def env_vars = []
def rally_extra_args = ''
+ def cmd_rally_plugins =
+ "git clone -b ${plugins_branch ?: 'master'} ${plugins_repo} /tmp/plugins; " +
+ "sudo pip install --upgrade /tmp/plugins; "
def cmd_rally_init = ''
- def cmd_rally_checkout = ''
+ def cmd_rally_checkout = "git clone -b ${config_branch ?: 'master'} ${config_repo} test_config; "
def cmd_rally_start = ''
def cmd_rally_task_args = ''
- def cmd_report = "rally task export --type junit-xml --to ${dest_folder}/report-rally.xml; " +
- "rally task report --out ${dest_folder}/report-rally.html"
+ def cmd_rally_stacklight = ''
+ def cmd_rally_report = ''
salt.runSaltProcessStep(master, target, 'file.remove', ["${results}"])
salt.runSaltProcessStep(master, target, 'file.mkdir', ["${results}", "mode=777"])
- if (platform == 'openstack') {
+ if (platform['type'] == 'openstack') {
def _pillar = salt.getPillar(master, 'I@keystone:server', 'keystone:server')
def keystone = _pillar['return'][0].values()[0]
env_vars = ( ['tempest_version=15.0.0',
@@ -278,31 +390,16 @@
"OS_AUTH_URL=http://${keystone.bind.private_address}:${keystone.bind.private_port}/v2.0",
"OS_REGION_NAME=${keystone.region}",
'OS_ENDPOINT_TYPE=admin'] + ext_variables ).join(' -e ')
- if (repository == '' ) {
- cmd_rally_init = ''
- cmd_rally_start = '/opt/devops-qa-tools/deployment/configure.sh; ' +
- "rally $rally_extra_args task start combined_scenario.yaml " +
- '--task-args-file /opt/devops-qa-tools/rally-scenarios/task_arguments.yaml; '
- cmd_rally_checkout = ''
- } else {
- cmd_rally_init = 'rally db create; ' +
- 'rally deployment create --fromenv --name=existing; ' +
- 'rally deployment config; '
- cmd_rally_checkout = "git clone -b ${branch ?: 'master'} ${repository} test_config; "
- if (scenarios == '') {
- cmd_rally_start = "rally $rally_extra_args task start test_config/rally/scenario.yaml "
- } else {
- cmd_rally_start = "rally $rally_extra_args task start scenarios.yaml "
- cmd_rally_checkout += "if [ -f ${scenarios} ]; then cp ${scenarios} scenarios.yaml; " +
- "else " +
- "find -L ${scenarios} -name '*.yaml' -exec cat {} >> scenarios.yaml \\; ; " +
- "sed -i '/---/d' scenarios.yaml; fi; "
- }
+ cmd_rally_init = 'rally db create; ' +
+ 'rally deployment create --fromenv --name=existing; ' +
+ 'rally deployment config; '
+ if (platform['stacklight_enabled'] == true) {
+ cmd_rally_stacklight = bundle_up_scenarios(sl_scenarios, skip_list, "scenarios_${platform.type}_stacklight.yaml")
+ cmd_rally_stacklight += "rally $rally_extra_args task start scenarios_${platform.type}_stacklight.yaml " +
+ "--task-args-file test_config/job-params-stacklight.yaml; "
}
- } else if (platform == 'k8s') {
+ } else if (platform['type'] == 'k8s') {
rally_extra_args = "--debug --log-file ${dest_folder}/task.log"
- def plugins_repo = ext_variables.plugins_repo
- def plugins_branch = ext_variables.plugins_branch
def _pillar = salt.getPillar(master, 'I@kubernetes:master and *01*', 'kubernetes:master')
def kubernetes = _pillar['return'][0].values()[0]
env_vars = [
@@ -327,31 +424,15 @@
writeFile file: "${tmp_dir}/k8s-client.crt", text: k8s_client_crt
salt.cmdRun(master, target, "mv ${tmp_dir}/* ${results}/")
salt.runSaltProcessStep(master, target, 'file.rmdir', ["${tmp_dir}"])
- cmd_rally_init = 'set -e ; set -x; if [ ! -w ~/.rally ]; then sudo chown rally:rally ~/.rally ; fi; cd /tmp/; ' +
- "git clone -b ${plugins_branch ?: 'master'} ${plugins_repo} plugins; " +
- "sudo pip install --upgrade ./plugins; " +
+ cmd_rally_init = "rally db recreate; " +
"rally env create --name k8s --from-sysenv; " +
"rally env check k8s; "
- if (repository == '' ) {
- cmd_rally_start = "rally $rally_extra_args task start " +
- "./plugins/samples/scenarios/kubernetes/create-and-delete-pod.yaml; "
- cmd_rally_checkout = ''
- } else {
- cmd_rally_checkout = "git clone -b ${branch ?: 'master'} ${repository} test_config; "
- if (scenarios == '') {
- cmd_rally_start = "rally $rally_extra_args task start test_config/rally-k8s/create-and-delete-pod.yaml "
- } else {
- cmd_rally_start = "rally $rally_extra_args task start scenarios.yaml "
- cmd_rally_checkout += "if [ -f ${scenarios} ]; then cp ${scenarios} scenarios.yaml; " +
- "else " +
- "find -L ${scenarios} -name '*.yaml' -exec cat {} >> scenarios.yaml \\; ; " +
- "sed -i '/---/d' scenarios.yaml; fi; "
- }
- }
} else {
throw new Exception("Platform ${platform} is not supported yet")
}
- if (repository != '' ) {
+ cmd_rally_checkout += bundle_up_scenarios(scenarios, skip_list, "scenarios_${platform.type}.yaml")
+ cmd_rally_start = "rally $rally_extra_args task start scenarios_${platform.type}.yaml "
+ if (config_repo != '' ) {
switch(tasks_args_file) {
case 'none':
cmd_rally_task_args = '; '
@@ -364,7 +445,13 @@
break
}
}
- full_cmd = cmd_rally_init + cmd_rally_checkout + cmd_rally_start + cmd_rally_task_args + cmd_report
+ cmd_rally_report= "rally task export --type junit-xml --to ${dest_folder}/report-rally.xml; " +
+ "rally task report --out ${dest_folder}/report-rally.html"
+ full_cmd = 'set -xe; ' + cmd_rally_plugins +
+ cmd_rally_init + cmd_rally_checkout +
+ 'set +e; ' + cmd_rally_start +
+ cmd_rally_task_args + cmd_rally_stacklight +
+ cmd_rally_report
salt.runSaltProcessStep(master, target, 'file.touch', ["${results}/rally.db"])
salt.cmdRun(master, target, "chmod 666 ${results}/rally.db")
salt.cmdRun(master, target, "docker run -w /home/rally -i --rm --net=host -e ${env_vars} " +
@@ -472,12 +559,12 @@
* @param testing_tools_repo Repo with testing tools: configuration script, skip-list, etc.
* @param tempest_repo Tempest repo to clone. Can be upstream tempest (default, recommended), your customized tempest in local/remote repo or path inside container. If not specified, tempest will not be configured.
* @param tempest_endpoint_type internalURL or adminURL or publicURL to use in tests
- * @param tempest_version Version of tempest to use
+ * @param tempest_version Version of tempest to use. This value will be just passed to configure.sh script (cvp-configuration repo).
* @param conf_script_path Path to configuration script.
* @param ext_variables Some custom extra variables to add into container
*/
def configureContainer(master, target, proxy, testing_tools_repo, tempest_repo,
- tempest_endpoint_type="internalURL", tempest_version="15.0.0",
+ tempest_endpoint_type="internalURL", tempest_version="",
conf_script_path="", ext_variables = []) {
def salt = new com.mirantis.mk.Salt()
if (testing_tools_repo != "" ) {
@@ -510,19 +597,17 @@
def salt = new com.mirantis.mk.Salt()
def xml_file = "${output_filename}.xml"
def html_file = "${output_filename}.html"
- def log_file = "${output_filename}.log"
skip_list_cmd = ''
if (skip_list != '') {
skip_list_cmd = "--skip-list ${skip_list}"
}
- salt.cmdRun(master, target, "docker exec cvp rally verify start --pattern ${test_pattern} ${skip_list_cmd} " +
- "--detailed > ${log_file}", false)
- salt.cmdRun(master, target, "cat ${log_file}")
+ salt.cmdRun(master, target, "docker exec cvp rally verify start --pattern ${test_pattern} ${skip_list_cmd} --detailed")
salt.cmdRun(master, target, "docker exec cvp rally verify report --type junit-xml --to /home/rally/${xml_file}")
salt.cmdRun(master, target, "docker exec cvp rally verify report --type html --to /home/rally/${html_file}")
salt.cmdRun(master, target, "docker cp cvp:/home/rally/${xml_file} ${output_dir}")
salt.cmdRun(master, target, "docker cp cvp:/home/rally/${html_file} ${output_dir}")
- return salt.cmdRun(master, target, "docker exec cvp rally verify show | head -5 | tail -1 | awk '{print \$4}'")['return'][0].values()[0].split()[0]
+ return salt.cmdRun(master, target, "docker exec cvp rally verify show | head -5 | tail -1 | " +
+ "awk '{print \$4}'")['return'][0].values()[0].split()[0]
}
/**
@@ -536,10 +621,8 @@
def runCVPrally(master, target, scenarios_path, output_dir, output_filename="docker-rally") {
def salt = new com.mirantis.mk.Salt()
def xml_file = "${output_filename}.xml"
- def log_file = "${output_filename}.log"
def html_file = "${output_filename}.html"
- salt.cmdRun(master, target, "docker exec cvp rally task start ${scenarios_path} > ${log_file}", false)
- salt.cmdRun(master, target, "cat ${log_file}")
+ salt.cmdRun(master, target, "docker exec cvp rally task start ${scenarios_path}")
salt.cmdRun(master, target, "docker exec cvp rally task report --out ${html_file}")
salt.cmdRun(master, target, "docker exec cvp rally task report --junit --out ${xml_file}")
salt.cmdRun(master, target, "docker cp cvp:/home/rally/${xml_file} ${output_dir}")
@@ -661,14 +744,12 @@
* Cleanup
*
* @param target Host to run commands
+ * @param name Name of container to remove
*/
-def runCleanup(master, target) {
+def runCleanup(master, target, name='cvp') {
def salt = new com.mirantis.mk.Salt()
- if ( salt.cmdRun(master, target, "docker ps -f name=qa_tools -q", false, null, false)['return'][0].values()[0] ) {
- salt.cmdRun(master, target, "docker rm -f qa_tools")
- }
- if ( salt.cmdRun(master, target, "docker ps -f name=cvp -q", false, null, false)['return'][0].values()[0] ) {
- salt.cmdRun(master, target, "docker rm -f cvp")
+ if ( salt.cmdRun(master, target, "docker ps -f name=${name} -q", false, null, false)['return'][0].values()[0] ) {
+ salt.cmdRun(master, target, "docker rm -f ${name}")
}
}
/**
diff --git a/src/com/mirantis/mk/Common.groovy b/src/com/mirantis/mk/Common.groovy
index 7ad1834..87b1696 100644
--- a/src/com/mirantis/mk/Common.groovy
+++ b/src/com/mirantis/mk/Common.groovy
@@ -5,6 +5,7 @@
import com.cloudbees.groovy.cps.NonCPS
import groovy.json.JsonSlurperClassic
+
/**
*
* Common functions
@@ -14,9 +15,9 @@
/**
* Generate current timestamp
*
- * @param format Defaults to yyyyMMddHHmmss
+ * @param format Defaults to yyyyMMddHHmmss
*/
-def getDatetime(format="yyyyMMddHHmmss") {
+def getDatetime(format = "yyyyMMddHHmmss") {
def now = new Date();
return now.format(format, TimeZone.getTimeZone('UTC'));
}
@@ -26,14 +27,14 @@
* Currently implemented by calling pwd so it won't return relevant result in
* dir context
*/
-def getWorkspace(includeBuildNum=false) {
+def getWorkspace(includeBuildNum = false) {
def workspace = sh script: 'pwd', returnStdout: true
workspace = workspace.trim()
- if(includeBuildNum){
- if(!workspace.endsWith("/")){
- workspace += "/"
- }
- workspace += env.BUILD_NUMBER
+ if (includeBuildNum) {
+ if (!workspace.endsWith("/")) {
+ workspace += "/"
+ }
+ workspace += env.BUILD_NUMBER
}
return workspace
}
@@ -43,7 +44,7 @@
* Must be run from context of node
*/
def getJenkinsUid() {
- return sh (
+ return sh(
script: 'id -u',
returnStdout: true
).trim()
@@ -54,7 +55,7 @@
* Must be run from context of node
*/
def getJenkinsGid() {
- return sh (
+ return sh(
script: 'id -g',
returnStdout: true
).trim()
@@ -64,7 +65,7 @@
* Returns Jenkins user uid and gid in one list (in that order)
* Must be run from context of node
*/
-def getJenkinsUserIds(){
+def getJenkinsUserIds() {
return sh(script: "id -u && id -g", returnStdout: true).tokenize("\n")
}
@@ -72,37 +73,37 @@
*
* Find credentials by ID
*
- * @param credsId Credentials ID
- * @param credsType Credentials type (optional)
+ * @param credsId Credentials ID
+ * @param credsType Credentials type (optional)
*
*/
def getCredentialsById(String credsId, String credsType = 'any') {
def credClasses = [ // ordered by class name
- sshKey: com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey.class,
- cert: com.cloudbees.plugins.credentials.common.CertificateCredentials.class,
- password: com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials.class,
- any: com.cloudbees.plugins.credentials.impl.BaseStandardCredentials.class,
- dockerCert: org.jenkinsci.plugins.docker.commons.credentials.DockerServerCredentials.class,
- file: org.jenkinsci.plugins.plaincredentials.FileCredentials.class,
- string: org.jenkinsci.plugins.plaincredentials.StringCredentials.class,
+ sshKey : com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey.class,
+ cert : com.cloudbees.plugins.credentials.common.CertificateCredentials.class,
+ password : com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials.class,
+ any : com.cloudbees.plugins.credentials.impl.BaseStandardCredentials.class,
+ dockerCert: org.jenkinsci.plugins.docker.commons.credentials.DockerServerCredentials.class,
+ file : org.jenkinsci.plugins.plaincredentials.FileCredentials.class,
+ string : org.jenkinsci.plugins.plaincredentials.StringCredentials.class,
]
return com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
credClasses[credsType],
jenkins.model.Jenkins.instance
- ).findAll {cred -> cred.id == credsId}[0]
+ ).findAll { cred -> cred.id == credsId }[0]
}
/**
* Get credentials from store
*
- * @param id Credentials name
+ * @param id Credentials name
*/
def getCredentials(id, cred_type = "username_password") {
warningMsg('You are using obsolete function. Please switch to use `getCredentialsById()`')
type_map = [
username_password: 'password',
- key: 'sshKey',
+ key : 'sshKey',
]
return getCredentialsById(id, type_map[cred_type])
@@ -122,7 +123,7 @@
* Print pretty-printed string representation of given item
* @param item item to be pretty-printed (list, map, whatever)
*/
-def prettyPrint(item){
+def prettyPrint(item) {
println prettify(item)
}
@@ -131,7 +132,7 @@
* @param item item to be pretty-printed (list, map, whatever)
* @return pretty-printed string
*/
-def prettify(item){
+def prettify(item) {
return groovy.json.JsonOutput.prettyPrint(toJson(item)).replace('\\n', System.getProperty('line.separator'))
}
@@ -180,22 +181,15 @@
* @param msg
* @param color Colorful output or not
*/
-def debugMsg(msg, color = true){
+def debugMsg(msg, color = true) {
// if debug property exists on env, debug is enabled
- if(env.getEnvironment().containsKey('DEBUG') && env['DEBUG'] == "true"){
+ if (env.getEnvironment().containsKey('DEBUG') && env['DEBUG'] == "true") {
printMsg("[DEBUG] ${msg}", "red")
}
}
-/**
- * Print message
- *
- * @param msg Message to be printed
- * @param level Level of message (default INFO)
- * @param color Color to use for output or false (default)
- */
-def printMsg(msg, color = false) {
- colors = [
+def getColorizedString(msg, color) {
+ def colorMap = [
'red' : '\u001B[31m',
'black' : '\u001B[30m',
'green' : '\u001B[32m',
@@ -206,11 +200,18 @@
'white' : '\u001B[37m',
'reset' : '\u001B[0m'
]
- if (color != false) {
- print "${colors[color]}${msg}${colors.reset}"
- } else {
- print "[${level}] ${msg}"
- }
+
+ return "${colorMap[color]}${msg}${colorMap.reset}"
+}
+
+/**
+ * Print message
+ *
+ * @param msg Message to be printed
+ * @param color Color to use for output
+ */
+def printMsg(msg, color) {
+ print getColorizedString(msg, color)
}
/**
@@ -220,7 +221,7 @@
* @param type Type of files to search (groovy.io.FileType.FILES)
*/
@NonCPS
-def getFiles(path, type=groovy.io.FileType.FILES) {
+def getFiles(path, type = groovy.io.FileType.FILES) {
files = []
new File(path).eachFile(type) {
files[] = it
@@ -236,7 +237,7 @@
*/
@NonCPS
def entries(m) {
- m.collect {k, v -> [k, v]}
+ m.collect { k, v -> [k, v] }
}
/**
@@ -246,20 +247,20 @@
*/
def serial(steps) {
stepsArray = entries(steps)
- for (i=0; i < stepsArray.size; i++) {
+ for (i = 0; i < stepsArray.size; i++) {
def step = stepsArray[i]
def dummySteps = [:]
def stepKey
- if(step[1] instanceof List || step[1] instanceof Map){
- for(j=0;j < step[1].size(); j++){
- if(step[1] instanceof List){
+ if (step[1] instanceof List || step[1] instanceof Map) {
+ for (j = 0; j < step[1].size(); j++) {
+ if (step[1] instanceof List) {
stepKey = j
- }else if(step[1] instanceof Map){
+ } else if (step[1] instanceof Map) {
stepKey = step[1].keySet()[j]
}
- dummySteps.put("step-${step[0]}-${stepKey}",step[1][stepKey])
+ dummySteps.put("step-${step[0]}-${stepKey}", step[1][stepKey])
}
- }else{
+ } else {
dummySteps.put(step[0], step[1])
}
parallel dummySteps
@@ -271,18 +272,18 @@
* @param inputList input list
* @param partitionSize (partition size, optional, default 5)
*/
-def partitionList(inputList, partitionSize=5){
- List<List<String>> partitions = new ArrayList<>();
- for (int i=0; i<inputList.size(); i += partitionSize) {
- partitions.add(new ArrayList<String>(inputList.subList(i, Math.min(i + partitionSize, inputList.size()))));
- }
- return partitions
+def partitionList(inputList, partitionSize = 5) {
+ List<List<String>> partitions = new ArrayList<>();
+ for (int i = 0; i < inputList.size(); i += partitionSize) {
+ partitions.add(new ArrayList<String>(inputList.subList(i, Math.min(i + partitionSize, inputList.size()))));
+ }
+ return partitions
}
/**
* Get password credentials from store
*
- * @param id Credentials name
+ * @param id Credentials name
*/
def getPasswordCredentials(id) {
return getCredentialsById(id, 'password')
@@ -291,7 +292,7 @@
/**
* Get SSH credentials from store
*
- * @param id Credentials name
+ * @param id Credentials name
*/
def getSshCredentials(id) {
return getCredentialsById(id, 'sshKey')
@@ -303,28 +304,28 @@
* @return boolean result
*/
@NonCPS
-def jenkinsHasPlugin(pluginName){
- return Jenkins.instance.pluginManager.plugins.collect{p -> p.shortName}.contains(pluginName)
+def jenkinsHasPlugin(pluginName) {
+ return Jenkins.instance.pluginManager.plugins.collect { p -> p.shortName }.contains(pluginName)
}
@NonCPS
def _needNotification(notificatedTypes, buildStatus, jobName) {
- if(notificatedTypes && notificatedTypes.contains("onchange")){
- if(jobName){
+ if (notificatedTypes && notificatedTypes.contains("onchange")) {
+ if (jobName) {
def job = Jenkins.instance.getItem(jobName)
def numbuilds = job.builds.size()
- if (numbuilds > 0){
+ if (numbuilds > 0) {
//actual build is first for some reasons, so last finished build is second
def lastBuild = job.builds[1]
- if(lastBuild){
- if(lastBuild.result.toString().toLowerCase().equals(buildStatus)){
+ if (lastBuild) {
+ if (lastBuild.result.toString().toLowerCase().equals(buildStatus)) {
println("Build status didn't changed since last build, not sending notifications")
return false;
}
}
}
}
- }else if(!notificatedTypes.contains(buildStatus)){
+ } else if (!notificatedTypes.contains(buildStatus)) {
return false;
}
return true;
@@ -343,7 +344,7 @@
* @param mailFrom mail FROM param, if empty "jenkins" will be used, it's mandatory for sending email notifications
* @param mailTo mail TO param, it's mandatory for sending email notifications, this option enable mail notification
*/
-def sendNotification(buildStatus, msgText="", enabledNotifications = [], notificatedTypes=["onchange"], jobName=null, buildNumber=null, buildUrl=null, mailFrom="jenkins", mailTo=null){
+def sendNotification(buildStatus, msgText = "", enabledNotifications = [], notificatedTypes = ["onchange"], jobName = null, buildNumber = null, buildUrl = null, mailFrom = "jenkins", mailTo = null) {
// Default values
def colorName = 'blue'
def colorCode = '#0000FF'
@@ -354,40 +355,40 @@
def subject = "${buildStatusParam}: Job '${jobNameParam} [${buildNumberParam}]'"
def summary = "${subject} (${buildUrlParam})"
- if(msgText != null && msgText != ""){
- summary+="\n${msgText}"
+ if (msgText != null && msgText != "") {
+ summary += "\n${msgText}"
}
- if(buildStatusParam.toLowerCase().equals("success")){
+ if (buildStatusParam.toLowerCase().equals("success")) {
colorCode = "#00FF00"
colorName = "green"
- }else if(buildStatusParam.toLowerCase().equals("unstable")){
+ } else if (buildStatusParam.toLowerCase().equals("unstable")) {
colorCode = "#FFFF00"
colorName = "yellow"
- }else if(buildStatusParam.toLowerCase().equals("failure")){
+ } else if (buildStatusParam.toLowerCase().equals("failure")) {
colorCode = "#FF0000"
colorName = "red"
}
- if(_needNotification(notificatedTypes, buildStatusParam.toLowerCase(), jobNameParam)){
- if(enabledNotifications.contains("slack") && jenkinsHasPlugin("slack")){
- try{
+ if (_needNotification(notificatedTypes, buildStatusParam.toLowerCase(), jobNameParam)) {
+ if (enabledNotifications.contains("slack") && jenkinsHasPlugin("slack")) {
+ try {
slackSend color: colorCode, message: summary
- }catch(Exception e){
+ } catch (Exception e) {
println("Calling slack plugin failed")
e.printStackTrace()
}
}
- if(enabledNotifications.contains("hipchat") && jenkinsHasPlugin("hipchat")){
- try{
+ if (enabledNotifications.contains("hipchat") && jenkinsHasPlugin("hipchat")) {
+ try {
hipchatSend color: colorName.toUpperCase(), message: summary
- }catch(Exception e){
+ } catch (Exception e) {
println("Calling hipchat plugin failed")
e.printStackTrace()
}
}
- if(enabledNotifications.contains("email") && mailTo != null && mailTo != "" && mailFrom != null && mailFrom != ""){
- try{
+ if (enabledNotifications.contains("email") && mailTo != null && mailTo != "" && mailFrom != null && mailFrom != "") {
+ try {
mail body: summary, from: mailFrom, subject: subject, to: mailTo
- }catch(Exception e){
+ } catch (Exception e) {
println("Sending mail plugin failed")
e.printStackTrace()
}
@@ -402,16 +403,15 @@
* @return index-th element
*/
-def cutOrDie(cmd, index)
-{
+def cutOrDie(cmd, index) {
def common = new com.mirantis.mk.Common()
def output
try {
- output = sh(script: cmd, returnStdout: true)
- def result = output.tokenize(" ")[index]
- return result;
+ output = sh(script: cmd, returnStdout: true)
+ def result = output.tokenize(" ")[index]
+ return result;
} catch (Exception e) {
- common.errorMsg("Failed to execute cmd: ${cmd}\n output: ${output}")
+ common.errorMsg("Failed to execute cmd: ${cmd}\n output: ${output}")
}
}
@@ -423,7 +423,7 @@
*/
def checkContains(variable, keyword) {
- if(env.getEnvironment().containsKey(variable)){
+ if (env.getEnvironment().containsKey(variable)) {
return env[variable] && env[variable].toLowerCase().contains(keyword.toLowerCase())
} else {
return false
@@ -435,19 +435,22 @@
* @param jsonString input JSON string
* @return created hashmap
*/
-def parseJSON(jsonString){
- def m = [:]
- def lazyMap = new JsonSlurperClassic().parseText(jsonString)
- m.putAll(lazyMap)
- return m
+def parseJSON(jsonString) {
+ def m = [:]
+ def lazyMap = new JsonSlurperClassic().parseText(jsonString)
+ m.putAll(lazyMap)
+ return m
}
/**
* Test pipeline input parameter existence and validity (not null and not empty string)
* @param paramName input parameter name (usually uppercase)
- */
-def validInputParam(paramName){
- return env.getEnvironment().containsKey(paramName) && env[paramName] != null && env[paramName] != ""
+ */
+def validInputParam(paramName) {
+ if (paramName instanceof java.lang.String) {
+ return env.getEnvironment().containsKey(paramName) && env[paramName] != null && env[paramName] != ""
+ }
+ return false
}
/**
@@ -460,7 +463,7 @@
@NonCPS
def countHashMapEquals(lm, param, eq) {
- return lm.stream().filter{i -> i[param].equals(eq)}.collect(java.util.stream.Collectors.counting())
+ return lm.stream().filter { i -> i[param].equals(eq) }.collect(java.util.stream.Collectors.counting())
}
/**
@@ -476,7 +479,7 @@
def stdout = sh(script: 'mktemp', returnStdout: true).trim()
try {
- def status = sh(script:"${cmd} 1>${stdout} 2>${stderr}", returnStatus: true)
+ def status = sh(script: "${cmd} 1>${stdout} 2>${stderr}", returnStatus: true)
res['stderr'] = sh(script: "cat ${stderr}", returnStdout: true)
res['stdout'] = sh(script: "cat ${stdout}", returnStdout: true)
res['status'] = status
@@ -488,7 +491,6 @@
return res
}
-
/**
* Retry commands passed to body
*
@@ -496,17 +498,16 @@
* @param delay Delay between retries (in seconds)
* @param body Commands to be in retry block
* @return calling commands in body
- * @example retry(3,5){ function body }
- * retry{ function body }
+ * @example retry ( 3 , 5 ) { function body }* retry{ function body }
*/
def retry(int times = 5, int delay = 0, Closure body) {
int retries = 0
def exceptions = []
- while(retries++ < times) {
+ while (retries++ < times) {
try {
return body.call()
- } catch(e) {
+ } catch (e) {
sleep(delay)
}
}
@@ -514,30 +515,29 @@
throw new Exception("Failed after $times retries")
}
-
/**
* Wait for user input with timeout
*
* @param timeoutInSeconds Timeout
* @param options Options for input widget
*/
-def waitForInputThenPass(timeoutInSeconds, options=[message: 'Ready to go?']) {
- def userInput = true
- try {
- timeout(time: timeoutInSeconds, unit: 'SECONDS') {
- userInput = input options
+def waitForInputThenPass(timeoutInSeconds, options = [message: 'Ready to go?']) {
+ def userInput = true
+ try {
+ timeout(time: timeoutInSeconds, unit: 'SECONDS') {
+ userInput = input options
+ }
+ } catch (err) { // timeout reached or input false
+ def user = err.getCauses()[0].getUser()
+ if ('SYSTEM' == user.toString()) { // SYSTEM means timeout.
+ println("Timeout, proceeding")
+ } else {
+ userInput = false
+ println("Aborted by: [${user}]")
+ throw err
+ }
}
- } catch(err) { // timeout reached or input false
- def user = err.getCauses()[0].getUser()
- if('SYSTEM' == user.toString()) { // SYSTEM means timeout.
- println("Timeout, proceeding")
- } else {
- userInput = false
- println("Aborted by: [${user}]")
- throw err
- }
- }
- return userInput
+ return userInput
}
/**
@@ -547,6 +547,381 @@
*/
@NonCPS
def SortMapByValueAsc(_map) {
- def sortedMap = _map.sort {it.value}
+ def sortedMap = _map.sort { it.value }
return sortedMap
}
+
+/**
+ * Compare 'old' and 'new' dir's recursively
+ * @param diffData =' Only in new/XXX/infra: secrets.yml
+ Files old/XXX/init.yml and new/XXX/init.yml differ
+ Only in old/XXX/infra: secrets11.yml '
+ *
+ * @return
+ * - new:
+ - XXX/secrets.yml
+ - diff:
+ - XXX/init.yml
+ - removed:
+ - XXX/secrets11.yml
+
+ */
+def diffCheckMultidir(diffData) {
+ common = new com.mirantis.mk.Common()
+ // Some global constants. Don't change\move them!
+ keyNew = 'new'
+ keyRemoved = 'removed'
+ keyDiff = 'diff'
+ def output = [
+ new : [],
+ removed: [],
+ diff : [],
+ ]
+ String pathSep = '/'
+ diffData.each { line ->
+ def job_file = ''
+ def job_type = ''
+ if (line.startsWith('Files old/')) {
+ job_file = new File(line.replace('Files old/', '').tokenize()[0])
+ job_type = keyDiff
+ } else if (line.startsWith('Only in new/')) {
+ // get clean normalized filepath, under new/
+ job_file = new File(line.replace('Only in new/', '').replace(': ', pathSep)).toString()
+ job_type = keyNew
+ } else if (line.startsWith('Only in old/')) {
+ // get clean normalized filepath, under old/
+ job_file = new File(line.replace('Only in old/', '').replace(': ', pathSep)).toString()
+ job_type = keyRemoved
+ } else {
+ common.warningMsg("Not parsed diff line: ${line}!")
+ }
+ if (job_file != '') {
+ output[job_type].push(job_file)
+ }
+ }
+ return output
+}
+
+/**
+ * Compare 2 folder, file by file
+ * Structure should be:
+ * ${compRoot}/
+ └── diff - diff results will be save here
+ ├── new - input folder with data
+ ├── old - input folder with data
+ ├── pillar.diff - globall diff will be saved here
+ * b_url - usual env.BUILD_URL, to be add into description
+ * grepOpts - General grep cmdline; Could be used to pass some magic
+ * regexp into after-diff listing file(pillar.diff)
+ * Example: '-Ev infra/secrets.yml'
+ * return - html-based string
+ * TODO: allow to specify subdir for results?
+ **/
+
+def comparePillars(compRoot, b_url, grepOpts) {
+
+ // Some global constants. Don't change\move them!
+ keyNew = 'new'
+ keyRemoved = 'removed'
+ keyDiff = 'diff'
+ def diff_status = 0
+ // FIXME
+ httpWS = b_url + '/artifact/'
+ dir(compRoot) {
+ // If diff empty - exit 0
+ diff_status = sh(script: 'diff -q -r old/ new/ > pillar.diff',
+ returnStatus: true,
+ )
+ }
+ // Unfortunately, diff not able to work with dir-based regexp
+ if (diff_status == 1 && grepOpts) {
+ dir(compRoot) {
+ grep_status = sh(script: """
+ cp -v pillar.diff pillar_orig.diff
+ grep ${grepOpts} pillar_orig.diff > pillar.diff
+ """,
+ returnStatus: true
+ )
+ if (grep_status == 1) {
+ warningMsg("Grep regexp ${grepOpts} removed all diff!")
+ diff_status = 0
+ }
+ }
+ }
+ // Set job description
+ description = ''
+ if (diff_status == 1) {
+ // Analyse output file and prepare array with results
+ String data_ = readFile file: "${compRoot}/pillar.diff"
+ def diff_list = diffCheckMultidir(data_.split("\\r?\\n"))
+ infoMsg(diff_list)
+ dir(compRoot) {
+ if (diff_list[keyDiff].size() > 0) {
+ if (!fileExists('diff')) {
+ sh('mkdir -p diff')
+ }
+ description += '<b>CHANGED</b><ul>'
+ infoMsg('Changed items:')
+ def stepsForParallel = [:]
+ stepsForParallel.failFast = true
+ diff_list[keyDiff].each {
+ stepsForParallel.put("Differ for:${it}",
+ {
+ // We don't want to handle sub-dirs structure. So, simply make diff 'flat'
+ def item_f = it.toString().replace('/', '_')
+ description += "<li><a href=\"${httpWS}/diff/${item_f}/*view*/\">${it}</a></li>"
+ // Generate diff file
+ def diff_exit_code = sh([
+ script : "diff -U 50 old/${it} new/${it} > diff/${item_f}",
+ returnStdout: false,
+ returnStatus: true,
+ ])
+ // catch normal errors, diff should always return 1
+ if (diff_exit_code != 1) {
+ error 'Error with diff file generation'
+ }
+ })
+ }
+
+ parallel stepsForParallel
+ }
+ if (diff_list[keyNew].size() > 0) {
+ description += '<b>ADDED</b><ul>'
+ for (item in diff_list[keyNew]) {
+ description += "<li><a href=\"${httpWS}/new/${item}/*view*/\">${item}</a></li>"
+ }
+ }
+ if (diff_list[keyRemoved].size() > 0) {
+ description += '<b>DELETED</b><ul>'
+ for (item in diff_list[keyRemoved]) {
+ description += "<li><a href=\"${httpWS}/old/${item}/*view*/\">${item}</a></li>"
+ }
+ }
+
+ }
+ }
+
+ if (description != '') {
+ dir(compRoot) {
+ archiveArtifacts([
+ artifacts : '**',
+ allowEmptyArchive: true,
+ ])
+ }
+ return description.toString()
+ } else {
+ return '<b>No job changes</b>'
+ }
+}
+
+/**
+ * Simple function, to get basename from string.
+ * line - path-string
+ * remove_ext - string, optionl. Drop file extenstion.
+ **/
+def GetBaseName(line, remove_ext) {
+ filename = line.toString().split('/').last()
+ if (remove_ext && filename.endsWith(remove_ext.toString())) {
+ filename = filename.take(filename.lastIndexOf(remove_ext.toString()))
+ }
+ return filename
+}
+
+/**
+ * Return colored string of specific stage in stageMap
+ *
+ * @param stageMap LinkedHashMap object.
+ * @param stageName The name of current stage we are going to execute.
+ * @param color Text color
+ * */
+def getColoredStageView(stageMap, stageName, color) {
+ def stage = stageMap[stageName]
+ def banner = []
+ def currentStageIndex = new ArrayList<String>(stageMap.keySet()).indexOf(stageName)
+ def numberOfStages = stageMap.keySet().size() - 1
+
+ banner.add(getColorizedString(
+ "=========== Stage ${currentStageIndex}/${numberOfStages}: ${stageName} ===========", color))
+ for (stage_item in stage.keySet()) {
+ banner.add(getColorizedString(
+ "${stage_item}: ${stage[stage_item]}", color))
+ }
+ banner.add('\n')
+
+ return banner
+}
+
+/**
+ * Pring stageMap to console with specified color
+ *
+ * @param stageMap LinkedHashMap object with stages information.
+ * @param currentStage The name of current stage we are going to execute.
+ *
+ * */
+def printCurrentStage(stageMap, currentStage) {
+ print getColoredStageView(stageMap, currentStage, "cyan").join('\n')
+}
+
+/**
+ * Pring stageMap to console with specified color
+ *
+ * @param stageMap LinkedHashMap object.
+ * @param baseColor Text color (default white)
+ * */
+def printStageMap(stageMap, baseColor = "white") {
+ def banner = []
+ def index = 0
+ for (stage_name in stageMap.keySet()) {
+ banner.addAll(getColoredStageView(stageMap, stage_name, baseColor))
+ }
+ print banner.join('\n')
+}
+
+/**
+ * Wrap provided code in stage, and do interactive retires if needed.
+ *
+ * @param stageMap LinkedHashMap object with stages information.
+ * @param currentStage The name of current stage we are going to execute.
+ * @param target Target host to execute stage on.
+ * @param interactive Boolean flag to specify if interaction with user is enabled.
+ * @param body Command to be in stage block.
+ * */
+def stageWrapper(stageMap, currentStage, target, interactive = true, Closure body) {
+ def common = new com.mirantis.mk.Common()
+ def banner = []
+
+ printCurrentStage(stageMap, currentStage)
+
+ stage(currentStage) {
+ if (interactive){
+ input message: getColorizedString("We are going to execute stage \'${currentStage}\' on the following target ${target}.\nPlease review stage information above.", "yellow")
+ }
+ try {
+ return body.call()
+ stageMap[currentStage]['Status'] = "SUCCESS"
+ } catch (Exception err) {
+ def msg = "Stage ${currentStage} failed with the following exception:\n${err}"
+ print getColorizedString(msg, "yellow")
+ common.errorMsg(err)
+ if (interactive) {
+ input message: getColorizedString("Please make sure problem is fixed to proceed with retry. Ready to proceed?", "yellow")
+ stageMap[currentStage]['Status'] = "RETRYING"
+ stageWrapper(stageMap, currentStage, target, interactive, body)
+ } else {
+ error(msg)
+ }
+ }
+ }
+}
+
+/**
+ * Ugly transition solution for internal tests.
+ * 1) Check input => transform to static result, based on runtime and input
+ * 2) Check remote-binary repo for exact resource
+ */
+
+def checkRemoteBinary(LinkedHashMap config, List extraScmExtensions = []) {
+ def common = new com.mirantis.mk.Common()
+ res = [:]
+ res['MirrorRoot'] = config.get('globalMirrorRoot', env["BIN_MIRROR_ROOT"] ? env["BIN_MIRROR_ROOT"] : "http://mirror.mirantis.com/")
+ // Reclass-like format's. To make life eazy!
+ res['apt_mk_version'] = config.get('apt_mk_version', env["BIN_APT_MK_VERSION"] ? env["BIN_APT_MK_VERSION"] : 'nightly')
+ res['linux_system_repo_url'] = config.get('linux_system_repo_url', env["BIN_linux_system_repo_url"] ? env["BIN_linux_system_repo_url"] : "${res['MirrorRoot']}/${res['apt_mk_version']}/")
+
+ if (config.get('verify', true)) {
+ MirrorRootStatus = sh(script: "wget --auth-no-challenge --spider ${res['linux_system_repo_url']} 2>/dev/null", returnStatus: true)
+ if (MirrorRootStatus != 0) {
+ common.warningMsg("Resource: ${res['linux_system_repo_url']} not exist")
+ res['linux_system_repo_url'] = false
+ }
+ }
+ return res
+}
+
+/**
+ * Workaround to update env properties, like GERRIT_* vars,
+ * which should be passed from upstream job to downstream.
+ * Will not fail entire job in case any issues.
+ * @param envVar - EnvActionImpl env job
+ * @param extraVars - Multiline YAML text with extra vars
+ */
+def mergeEnv(envVar, extraVars) {
+ def common = new com.mirantis.mk.Common()
+ try {
+ def extraParams = readYaml text: extraVars
+ for(String key in extraParams.keySet()) {
+ envVar[key] = extraParams[key]
+ common.warningMsg("Parameter ${key} is updated from EXTRA vars.")
+ }
+ } catch (Exception e) {
+ common.errorMsg("Can't update env parameteres, because: ${e.toString()}")
+ }
+}
+
+/**
+ * Wrapper around parallel pipeline function
+ * with ability to restrict number of parallel threads
+ * running simultaneously
+ *
+ * @param branches - Map with Clousers to be executed
+ * @param maxParallelJob - Integer number of parallel threads allowed
+ * to run simultaneously
+ */
+def runParallel(branches, maxParallelJob = 10) {
+ def runningSteps = 0
+ branches.each { branchName, branchBody ->
+ if (branchBody instanceof Closure) {
+ branches[branchName] = {
+ while (!(runningSteps < maxParallelJob)) {
+ continue
+ }
+ runningSteps += 1
+ branchBody.call()
+ runningSteps -= 1
+ }
+ }
+ }
+ if (branches) {
+ parallel branches
+ }
+}
+
+/**
+ * Ugly processing basic funcs with /etc/apt
+ * @param configYaml
+ * Example :
+ configYaml = '''
+ ---
+ distrib_revision: 'nightly'
+ aprConfD: |-
+ APT::Get::AllowUnauthenticated 'true';
+ repo:
+ mcp_saltstack:
+ source: "deb [arch=amd64] http://mirror.mirantis.com/SUB_DISTRIB_REVISION/saltstack-2017.7/xenial xenial main"
+ pinning: |-
+ Package: libsodium18
+ Pin: release o=SaltStack
+ Pin-Priority: 50
+ '''
+ *
+ */
+
+def debianExtraRepos(configYaml) {
+ def config = readYaml text: configYaml
+ def distribRevision = config.get('distrib_revision', 'nightly')
+ if (config.get('repo', false)) {
+ for (String repo in config['repo'].keySet()) {
+ source = config['repo'][repo]['source'].replace('SUB_DISTRIB_REVISION', distribRevision)
+ warningMsg("Write ${source} > /etc/apt/sources.list.d/${repo}.list")
+ sh("echo '${source}' > /etc/apt/sources.list.d/${repo}.list")
+ // TODO implement pining
+ }
+ }
+ if (config.get('aprConfD', false)) {
+ for (String pref in config['aprConfD'].tokenize('\n')) {
+ warningMsg("Adding ${pref} => /etc/apt/apt.conf.d/99setupAndTestNode")
+ sh("echo '${pref}' >> /etc/apt/apt.conf.d/99setupAndTestNode")
+ }
+ sh('cat /etc/apt/apt.conf.d/99setupAndTestNode')
+ }
+}
diff --git a/src/com/mirantis/mk/Debian.groovy b/src/com/mirantis/mk/Debian.groovy
index 4d7b1ee..d6c82db 100644
--- a/src/com/mirantis/mk/Debian.groovy
+++ b/src/com/mirantis/mk/Debian.groovy
@@ -251,3 +251,89 @@
sh("export GNUPGHOME=${workspace}/.gnupg; dput -f \"ppa:${ppaRepo}\" *_source.changes")
}
}
+
+/**
+* Upgrade packages on given target.
+*
+* @param env Salt Connection object or env Salt command map
+* @param target Salt target to upgrade packages on.
+*/
+def osUpgrade(env, target){
+ def common = new com.mirantis.mk.Common()
+ def salt = new com.mirantis.mk.Salt()
+
+ common.infoMsg("Running upgrade on ${target}")
+
+ salt.runSaltProcessStep(env, target, 'pkg.refresh_db', [], null, true)
+ def cmd = 'export DEBIAN_FRONTEND=noninteractive; apt-get -y -q --allow-downgrades -o Dpkg::Options::=\"--force-confdef\" -o Dpkg::Options::=\"--force-confold\" upgrade;'
+ salt.runSaltProcessStep(env, target, 'cmd.run', [cmd])
+}
+
+/**
+* Running dist-upgrade on given target.
+*
+* @param env Salt Connection object or env Salt command map
+* @param target Salt target to upgrade packages on.
+*/
+def osDistUpgrade(env, target){
+ def salt = new com.mirantis.mk.Salt()
+ def common = new com.mirantis.mk.Common()
+
+ common.infoMsg("Running dist-upgrade on ${target}")
+ salt.runSaltProcessStep(env, target, 'pkg.refresh_db', [], null, true)
+ def cmd = 'export DEBIAN_FRONTEND=noninteractive; apt-get -y -q --allow-downgrades -o Dpkg::Options::=\"--force-confdef\" -o Dpkg::Options::=\"--force-confold\" dist-upgrade;'
+ salt.runSaltProcessStep(env, target, 'cmd.run', [cmd])
+}
+
+/**
+* Reboot specified target, and wait when minion is UP.
+*
+* @param env Salt Connection object or env Salt command map
+* @param target Salt target to upgrade packages on.
+* @param timeout Sleep timeout when doing retries.
+* @param attempts Number of attemps to wait for.
+*/
+def osReboot(env, target, timeout=30, attempts=10){
+ def salt = new com.mirantis.mk.Salt()
+ def common = new com.mirantis.mk.Common()
+
+ salt.runSaltProcessStep(env, target, 'cmd.run', ["touch /tmp/rebooting"])
+ salt.runSaltProcessStep(env, target, 'system.reboot', [], null, true, 5)
+
+ common.retry(timeout, attempts){
+ if (salt.runSaltProcessStep(env, target, 'file.file_exists', ['/tmp/rebooting'], null, true, 5)['return'][0].values()[0].toBoolean()){
+ error("The system is still rebooting...")
+ }
+ }
+}
+
+/**
+* Upgrade OS on given node, wait when minion become reachable.
+*
+* @param env Salt Connection object or env Salt command map
+* @param target Salt target to upgrade packages on.
+* @param mode 'upgrade' or 'dist-upgrade'
+* @param postponeReboot Boolean flag to specify if reboot have to be postponed.
+* @param timeout Sleep timeout when doing retries.
+* @param attempts Number of attemps to wait for.
+*/
+def osUpgradeNode(env, target, mode, postponeReboot=false, timeout=30, attempts=10){
+ def common = new com.mirantis.mk.Common()
+ def salt = new com.mirantis.mk.Salt()
+
+ def rebootRequired = false
+ if (mode == 'dist-upgrade'){
+ osDistUpgrade(env, target)
+ } else if (mode == 'upgrade'){
+ osUpgrade(env, target)
+ }
+ rebootRequired = salt.runSaltProcessStep(env, target, 'file.file_exists', ['/var/run/reboot-required'], null, true, 5)['return'][0].values()[0].toBoolean()
+ if (rebootRequired) {
+ if (!postponeReboot){
+ common.infoMsg("Reboot is required after upgrade on ${target} Rebooting...")
+ osReboot(env, target, timeout, attempts)
+ } else {
+ common.infoMsg("Postponing reboot on node ${target}")
+ }
+ }
+}
diff --git a/src/com/mirantis/mk/Gerrit.groovy b/src/com/mirantis/mk/Gerrit.groovy
index 99c91b7..3761789 100644
--- a/src/com/mirantis/mk/Gerrit.groovy
+++ b/src/com/mirantis/mk/Gerrit.groovy
@@ -16,6 +16,8 @@
* - withMerge, merge master before build
* - withLocalBranch, prevent detached mode in repo
* - withWipeOut, wipe repository and force clone
+ * - GerritTriggerBuildChooser - use magic GerritTriggerBuildChooser class from gerrit-trigger-plugin.
+ * By default,enabled.
* Gerrit properties like GERRIT_SCHEMA can be passed in config as gerritSchema or will be obtained from env
* @param extraScmExtensions list of extra scm extensions which will be used for checkout (optional)
* @return boolean result
@@ -48,13 +50,13 @@
def path = config.get('path', "")
def depth = config.get('depth', 0)
def timeout = config.get('timeout', 20)
+ def GerritTriggerBuildChooser = config.get('useGerritTriggerBuildChooser', true)
def invalidParams = _getInvalidGerritParams(config)
if (invalidParams.isEmpty()) {
// default parameters
def scmExtensions = [
[$class: 'CleanCheckout'],
- [$class: 'BuildChooserSetting', buildChooser: [$class: 'GerritTriggerBuildChooser']],
[$class: 'CheckoutOption', timeout: timeout],
[$class: 'CloneOption', depth: depth, noTags: false, reference: '', shallow: depth > 0, timeout: timeout]
]
@@ -74,9 +76,14 @@
scmUserRemoteConfigs.put('credentialsId',credentials)
}
+ // Usefull, if we only need to clone branch. W\o any refspec magic
+ if (GerritTriggerBuildChooser) {
+ scmExtensions.add([$class: 'BuildChooserSetting', buildChooser: [$class: 'GerritTriggerBuildChooser']],)
+ }
+
// if we need to "merge" code from patchset to GERRIT_BRANCH branch
if (merge) {
- scmExtensions.add([$class: 'PreBuildMerge', options: [fastForwardMode: 'FF', mergeRemote: 'gerrit', mergeStrategy: 'default', mergeTarget: gerritBranch]])
+ scmExtensions.add([$class: 'PreBuildMerge', options: [fastForwardMode: 'FF', mergeRemote: 'gerrit', mergeStrategy: 'DEFAULT', mergeTarget: gerritBranch]])
}
// we need wipe workspace before checkout
if (wipe) {
@@ -246,4 +253,4 @@
def missedParams = requiredParams - config.keySet()
def badParams = config.subMap(requiredParams).findAll{it.value in [null, '']}.keySet()
return badParams + missedParams
-}
\ No newline at end of file
+}
diff --git a/src/com/mirantis/mk/Http.groovy b/src/com/mirantis/mk/Http.groovy
index 987a998..b752b42 100644
--- a/src/com/mirantis/mk/Http.groovy
+++ b/src/com/mirantis/mk/Http.groovy
@@ -112,25 +112,25 @@
}
/**
- * Make generic call using Salt REST API and return parsed JSON
+ * Make generic call using REST API and return parsed JSON
*
- * @param master Salt connection object
- * @param uri URI which will be appended to Salt server base URL
- * @param method HTTP method to use (default GET)
- * @param data JSON data to POST or PUT
- * @param headers Map of additional request headers
+ * @param base connection object, map with 'url' and optional 'authToken' keys
+ * @param uri URI which will be appended to connection base URL
+ * @param method HTTP method to use (default GET)
+ * @param data JSON data to POST, PUT or PATCH
+ * @param headers Map of additional request headers
*/
-def restCall(master, uri, method = 'GET', data = null, headers = [:]) {
- def connection = new URL("${master.url}${uri}").openConnection()
+def restCall(base, uri, method = 'GET', data = null, headers = [:]) {
+ def connection = new URL("${base.url}${uri}").openConnection()
if (method != 'GET') {
connection.setRequestMethod(method)
}
connection.setRequestProperty('User-Agent', 'jenkins-groovy')
connection.setRequestProperty('Accept', 'application/json')
- if (master.authToken) {
- // XXX: removeme
- connection.setRequestProperty('X-Auth-Token', master.authToken)
+ if (base.authToken) {
+ // XXX: removeme, explicitly use headers instead
+ connection.setRequestProperty('X-Auth-Token', base.authToken)
}
for (header in headers) {
@@ -163,34 +163,56 @@
}
/**
- * Make GET request using Salt REST API and return parsed JSON
+ * Make GET request using REST API and return parsed JSON
*
- * @param master Salt connection object
- * @param uri URI which will be appended to Salt server base URL
+ * @param base connection object, map with 'url' and optional 'authToken' keys
+ * @param uri URI which will be appended to server base URL
*/
-def restGet(master, uri, data = null) {
- return restCall(master, uri, 'GET', data)
+def restGet(base, uri, data = null, headers = [:]) {
+ return restCall(base, uri, 'GET', data, headers)
}
/**
- * Make POST request using Salt REST API and return parsed JSON
+ * Make POST request using REST API and return parsed JSON
*
- * @param master Salt connection object
- * @param uri URI which will be appended to Docker server base URL
+ * @param base connection object, map with 'url' and optional 'authToken' keys
+ * @param uri URI which will be appended to server base URL
+ * @param data JSON Data to POST
+ */
+def restPost(base, uri, data = null, headers = ['Accept': '*/*']) {
+ return restCall(base, uri, 'POST', data, headers)
+}
+
+/**
+ * Make PUT request using REST API and return parsed JSON
+ *
+ * @param base connection object, map with 'url' and optional 'authToken' keys
+ * @param uri URI which will be appended to server base URL
* @param data JSON Data to PUT
*/
-def restPost(master, uri, data = null) {
- return restCall(master, uri, 'POST', data, ['Accept': '*/*'])
+def restPut(base, uri, data = null, headers = ['Accept': '*/*']) {
+ return restCall(base, uri, 'PUT', data, headers)
}
/**
- * Make DELETE request using Salt REST API and return parsed JSON
+ * Make PATCH request using REST API and return parsed JSON
*
- * @param master Salt connection object
- * @param uri URI which will be appended to Salt server base URL
+ * @param base connection object, map with 'url' and optional 'authToken' keys
+ * @param uri URI which will be appended to server base URL
+ * @param data JSON Data to PUT
*/
-def restDelete(master, uri, data = null) {
- return restCall(master, uri, 'DELETE', data)
+def restPatch(base, uri, data = null, headers = ['Accept': '*/*']) {
+ return restCall(base, uri, 'PATCH', data, headers)
+}
+
+/**
+ * Make DELETE request using REST API and return parsed JSON
+ *
+ * @param base connection object, map with 'url' and optional 'authToken' keys
+ * @param uri URI which will be appended to server base URL
+ */
+def restDelete(base, uri, data = null, headers = [:]) {
+ return restCall(base, uri, 'DELETE', data, headers)
}
/**
diff --git a/src/com/mirantis/mk/Openscap.groovy b/src/com/mirantis/mk/Openscap.groovy
new file mode 100644
index 0000000..1841f16
--- /dev/null
+++ b/src/com/mirantis/mk/Openscap.groovy
@@ -0,0 +1,52 @@
+package com.mirantis.mk
+
+/**
+ * Run salt oscap.eval xccdf
+ *
+ * @param target the target where the benchmark will be evaluated
+ * @param evaltype what to evaluate (xccdf or oval)
+ * @param benchmark the benchmark which will be evaluated by openscap
+ * @param resultsDir the directory where artifacts will be moved
+ * @param profile the XCCDF profile name
+ * @param xccdfVersion XCCDF benchmark version (default 1.2)
+ * @param tailoringId The id of your tailoring data (from the corresponding pillar)
+ */
+def openscapEval(master, target, evaltype, benchmark, resultsDir, profile = 'default', xccdfVersion = '1.2', tailoringId = 'None') {
+ def salt = new com.mirantis.mk.Salt()
+ def common = new com.mirantis.mk.Common()
+ salt.runSaltProcessStep(master, target, 'oscap.eval', [evaltype, benchmark, results_dir = resultsDir, profile = profile, xccdf_version = xccdfVersion, tailoring_id= tailoringId])
+}
+
+/**
+ * Upload results to the security dashboard
+ *
+ * @param apiUrl the security dashboard url
+ * @param file the file to upload
+ * @param cloud_name the cloud_name
+ * @param nodename the scanned node name
+ */
+def uploadScanResultsToDashboard(apiUrl, results, cloud_name, nodename) {
+ def common = new com.mirantis.mk.Common()
+ def http = new com.mirantis.mk.Http()
+ def data = [:]
+
+ // Skip authorization until there is no authorization in the worp
+
+ // Get cloud_id
+ data['name'] = cloud_name
+ def cloudId = common.parseJSON(http.sendHttpPostRequest(apiUrl+'/environment', data))['id']
+ // Get report_id
+ data['env_uuid'] = cloudId
+ def reportId = common.parseJSON(http.sendHttpPostRequest(apiUrl+'/reports/openscap/', data))['id']
+
+ // Create node
+ def nodes = []
+ nodes.add[nodename]
+ data['nodes'] = nodes
+ http.sendHttpPostRequest(apiUrl+'/environment/'+cloudId+'/nodes', data)
+
+ // Upload results
+ data['results'] = results
+ data['node'] = nodename
+ http.sendHttpPostRequest(apiUrl+'/reports/openscap/'+reportId, data)
+}
diff --git a/src/com/mirantis/mk/Openstack.groovy b/src/com/mirantis/mk/Openstack.groovy
index f4a42ae..37fc73e 100644
--- a/src/com/mirantis/mk/Openstack.groovy
+++ b/src/com/mirantis/mk/Openstack.groovy
@@ -426,7 +426,7 @@
def salt = new com.mirantis.mk.Salt()
def common = new com.mirantis.mk.Common()
for (s in services) {
- def outputServicesStr = salt.getReturnValues(salt.cmdRun(env, "${probe}*", "service --status-all | grep ${s} | awk \'{print \$4}\'"))
+ def outputServicesStr = salt.getReturnValues(salt.cmdRun(env, probe, "service --status-all | grep ${s} | awk \'{print \$4}\'"))
def servicesList = outputServicesStr.tokenize("\n").init()
if (confirm) {
if (servicesList) {
@@ -434,7 +434,7 @@
input message: "Click PROCEED to stop ${servicesList}. Otherwise click ABORT to skip stopping them."
for (name in servicesList) {
if (!name.contains('Salt command')) {
- salt.runSaltProcessStep(env, "${target}*", 'service.stop', ["${name}"])
+ salt.runSaltProcessStep(env, target, 'service.stop', ["${name}"])
}
}
} catch (Exception er) {
@@ -445,7 +445,7 @@
if (servicesList) {
for (name in servicesList) {
if (!name.contains('Salt command')) {
- salt.runSaltProcessStep(env, "${target}*", 'service.stop', ["${name}"])
+ salt.runSaltProcessStep(env, target, 'service.stop', ["${name}"])
}
}
}
@@ -454,6 +454,79 @@
}
/**
+ * Return intersection of globally installed services and those are
+ * defined on specific target according to theirs priorities.
+ *
+ * @param env Salt Connection object or env
+ * @param target The target node to get list of apps for.
+**/
+def getOpenStackUpgradeServices(env, target){
+ def salt = new com.mirantis.mk.Salt()
+ def common = new com.mirantis.mk.Common()
+
+ def global_apps = salt.getConfig(env, 'I@salt:master:enabled:true', 'orchestration.upgrade.applications')
+ def node_apps = salt.getPillar(env, target, '__reclass__:applications')['return'][0].values()[0]
+ def node_sorted_apps = []
+ if ( !global_apps['return'][0].values()[0].isEmpty() ) {
+ Map<String,Integer> _sorted_apps = [:]
+ for (k in global_apps['return'][0].values()[0].keySet()) {
+ if (k in node_apps) {
+ _sorted_apps[k] = global_apps['return'][0].values()[0][k].values()[0].toInteger()
+ }
+ }
+ node_sorted_apps = common.SortMapByValueAsc(_sorted_apps).keySet()
+ common.infoMsg("Applications are placed in following order:"+node_sorted_apps)
+ } else {
+ common.errorMsg("No applications found.")
+ }
+
+ return node_sorted_apps
+}
+
+
+/**
+ * Run specified upgrade phase for all services on given node.
+ *
+ * @param env Salt Connection object or env
+ * @param target The target node to run states on.
+ * @param phase The phase name to run.
+**/
+def runOpenStackUpgradePhase(env, target, phase){
+ def salt = new com.mirantis.mk.Salt()
+ def common = new com.mirantis.mk.Common()
+
+ services = getOpenStackUpgradeServices(env, target)
+ def st
+
+ for (service in services){
+ st = "${service}.upgrade.${phase}".trim()
+ common.infoMsg("Running ${phase} for service ${st} on ${target}")
+ salt.enforceState(env, target, st)
+ }
+}
+
+
+/**
+ * Run OpenStack states on specified node.
+ *
+ * @param env Salt Connection object or env
+ * @param target The target node to run states on.
+**/
+def applyOpenstackAppsStates(env, target){
+ def salt = new com.mirantis.mk.Salt()
+ def common = new com.mirantis.mk.Common()
+
+ services = getOpenStackUpgradeServices(env, target)
+ def st
+
+ for (service in services){
+ st = "${service}".trim()
+ common.infoMsg("Running ${st} on ${target}")
+ salt.enforceState(env, target, st)
+ }
+}
+
+/**
* Restores Galera database
* @param env Salt Connection object or pepperEnv
* @return output of salt commands
diff --git a/src/com/mirantis/mk/Orchestrate.groovy b/src/com/mirantis/mk/Orchestrate.groovy
index d513822..265cfa0 100644
--- a/src/com/mirantis/mk/Orchestrate.groovy
+++ b/src/com/mirantis/mk/Orchestrate.groovy
@@ -61,19 +61,16 @@
salt.enforceState(master, "* ${extra_tgt}", ['linux.network.host'])
// Install and configure iptables
- if (salt.testTarget(master, "I@iptables:service ${extra_tgt}")) {
- salt.enforceState(master, "I@iptables:service ${extra_tgt}", 'iptables')
- }
+ salt.enforceStateWithTest(master, "I@iptables:service ${extra_tgt}", 'iptables')
// Install and configure logrotate
- if (salt.testTarget(master, "I@logrotate:server ${extra_tgt}")) {
- salt.enforceState(master, "I@logrotate:server ${extra_tgt}", 'logrotate')
- }
+ salt.enforceStateWithTest(master, "I@logrotate:server ${extra_tgt}", 'logrotate')
// Install and configure auditd
- if (salt.testTarget(master, "I@auditd:service ${extra_tgt}")) {
- salt.enforceState(master, "I@auditd:service ${extra_tgt}", 'auditd')
- }
+ salt.enforceStateWithTest(master, "I@auditd:service ${extra_tgt}", 'auditd')
+
+ // Install and configure openscap
+ salt.enforceStateWithTest(master, "I@openscap:service ${extra_tgt}", 'openscap')
}
def installFoundationInfraOnTarget(master, target, staticMgmtNet=false, extra_tgt = '') {
@@ -169,20 +166,19 @@
def installInfra(master, extra_tgt = '') {
def common = new com.mirantis.mk.Common()
def salt = new com.mirantis.mk.Salt()
+ def first_target
// Install glusterfs
if (salt.testTarget(master, "I@glusterfs:server ${extra_tgt}")) {
salt.enforceState(master, "I@glusterfs:server ${extra_tgt}", 'glusterfs.server.service')
- salt.enforceState(master, "I@glusterfs:server and *01* ${extra_tgt}", 'glusterfs.server.setup', true, true, null, false, -1, 5)
+ salt.enforceState(master, "I@glusterfs:server:role:primary ${extra_tgt}", 'glusterfs.server.setup', true, true, null, false, -1, 5)
sleep(10)
salt.cmdRun(master, "I@glusterfs:server ${extra_tgt}", "gluster peer status; gluster volume status")
}
// Ensure glusterfs clusters is ready
- if (salt.testTarget(master, "I@glusterfs:client ${extra_tgt}")) {
- salt.enforceState(master, "I@glusterfs:client ${extra_tgt}", 'glusterfs.client', true, true, null, false, -1, 2)
- }
+ salt.enforceStateWithTest(master, "I@glusterfs:client ${extra_tgt}", 'glusterfs.client', "", true, true, null, false, -1, 2)
// Install galera
if (salt.testTarget(master, "I@galera:master ${extra_tgt}") || salt.testTarget(master, "I@galera:slave ${extra_tgt}")) {
@@ -193,23 +189,22 @@
salt.runSaltProcessStep(master, "I@galera:master ${extra_tgt}", 'mysql.status')
salt.runSaltProcessStep(master, "I@galera:slave ${extra_tgt}", 'mysql.status')
// If galera is not enabled check if we need to install mysql:server
- } else if (salt.testTarget(master, "I@mysql:server ${extra_tgt}")){
- salt.enforceState(master, "I@mysql:server ${extra_tgt}", 'mysql.server')
- if (salt.testTarget(master, "I@mysql:client ${extra_tgt}")){
- salt.enforceState(master, "I@mysql:client ${extra_tgt}", 'mysql.client')
- }
+ } else {
+ salt.enforceStateWithTest(master, "I@mysql:server ${extra_tgt}", 'mysql.server')
+ salt.enforceStateWithTest(master, "I@mysql:client ${extra_tgt}", 'mysql.client')
}
installBackup(master, 'mysql', extra_tgt)
// Install docker
if (salt.testTarget(master, "I@docker:host ${extra_tgt}")) {
salt.enforceState(master, "I@docker:host ${extra_tgt}", 'docker.host', true, true, null, false, -1, 3)
- salt.cmdRun(master, "I@docker:host:enabled:true ${extra_tgt}", 'docker ps')
+ salt.cmdRun(master, "I@docker:host and I@docker:host:enabled:true ${extra_tgt}", 'docker ps')
}
// Install keepalived
if (salt.testTarget(master, "I@keepalived:cluster ${extra_tgt}")) {
- salt.enforceState(master, "I@keepalived:cluster and *01* ${extra_tgt}", 'keepalived')
+ first_target = salt.getFirstMinion(master, "I@keepalived:cluster ${extra_tgt}")
+ salt.enforceState(master, "${first_target} ${extra_tgt}", 'keepalived')
salt.enforceState(master, "I@keepalived:cluster ${extra_tgt}", 'keepalived')
}
@@ -231,9 +226,7 @@
}
// Install memcached
- if (salt.testTarget(master, "I@memcached:server ${extra_tgt}")) {
- salt.enforceState(master, "I@memcached:server ${extra_tgt}", 'memcached')
- }
+ salt.enforceStateWithTest(master, "I@memcached:server ${extra_tgt}", 'memcached')
// Install etcd
if (salt.testTarget(master, "I@etcd:server ${extra_tgt}")) {
@@ -245,11 +238,18 @@
// Install redis
if (salt.testTarget(master, "I@redis:server ${extra_tgt}")) {
- if (salt.testTarget(master, "I@redis:cluster:role:master ${extra_tgt}")) {
- salt.enforceState(master, "I@redis:cluster:role:master ${extra_tgt}", 'redis')
- }
+ salt.enforceStateWithTest(master, "I@redis:cluster:role:master ${extra_tgt}", 'redis')
salt.enforceState(master, "I@redis:server ${extra_tgt}", 'redis')
}
+
+ // Install DNS services
+ if (salt.testTarget(master, "I@bind:server ${extra_tgt}")) {
+ salt.enforceState(master, "I@bind:server ${extra_tgt}", 'bind.server')
+ }
+ if (salt.testTarget(master, "I@powerdns:server ${extra_tgt}")) {
+ salt.enforceState(master, "I@powerdns:server ${extra_tgt}", 'powerdns.server')
+ }
+
installBackup(master, 'common', extra_tgt)
}
@@ -263,23 +263,18 @@
def installOpenstackControl(master, extra_tgt = '') {
def salt = new com.mirantis.mk.Salt()
def common = new com.mirantis.mk.Common()
+ def first_target
// Install horizon dashboard
- if (salt.testTarget(master, "I@horizon:server ${extra_tgt}")) {
- salt.enforceState(master, "I@horizon:server ${extra_tgt}", 'horizon')
- }
+ salt.enforceStateWithTest(master, "I@horizon:server ${extra_tgt}", 'horizon')
// Install sphinx server
- if (salt.testTarget(master, "I@sphinx:server ${extra_tgt}")) {
- salt.enforceState(master, "I@sphinx:server ${extra_tgt}", 'sphinx')
- }
- if (salt.testTarget(master, "I@nginx:server ${extra_tgt}")) {
- salt.enforceState(master, "I@nginx:server ${extra_tgt}", 'salt.minion')
- salt.enforceState(master, "I@nginx:server ${extra_tgt}", 'nginx')
- }
+ salt.enforceStateWithTest(master, "I@sphinx:server ${extra_tgt}", 'sphinx')
+ salt.enforceStateWithTest(master, "I@nginx:server ${extra_tgt}", 'salt.minion')
+ salt.enforceStateWithTest(master, "I@nginx:server ${extra_tgt}", 'nginx')
// setup keystone service
if (salt.testTarget(master, "I@keystone:server ${extra_tgt}")) {
- salt.enforceState(master, "I@keystone:server and *01* ${extra_tgt}", 'keystone.server')
+ salt.enforceState(master, "I@keystone:server:role:primary ${extra_tgt}", 'keystone.server')
salt.enforceState(master, "I@keystone:server ${extra_tgt}", 'keystone.server')
// populate keystone services/tenants/roles/users
@@ -289,7 +284,8 @@
sleep(30)
}
if (salt.testTarget(master, "I@keystone:client ${extra_tgt}")) {
- salt.enforceState(master, "I@keystone:client and *01* ${extra_tgt}", 'keystone.client')
+ first_target = salt.getFirstMinion(master, "I@keystone:client ${extra_tgt}")
+ salt.enforceState(master, "${first_target} ${extra_tgt}", 'keystone.client')
salt.enforceState(master, "I@keystone:client ${extra_tgt}", 'keystone.client')
}
if (salt.testTarget(master, "I@keystone:server ${extra_tgt}")) {
@@ -299,11 +295,8 @@
}
// Install glance
- if (salt.testTarget(master, "I@glance:server ${extra_tgt}")) {
- //runSaltProcessStep(master, 'I@glance:server', 'state.sls', ['glance.server'], 1)
- salt.enforceState(master, "I@glance:server and *01* ${extra_tgt}", 'glance.server')
- salt.enforceState(master, "I@glance:server ${extra_tgt}", 'glance.server')
- }
+ salt.enforceStateWithTest(master, "I@glance:server:role:primary ${extra_tgt}", 'glance.server', "I@glance:server ${extra_tgt}")
+ salt.enforceStateWithTest(master, "I@glance:server ${extra_tgt}", 'glance.server')
// Check glance service
if (salt.testTarget(master, "I@glance:server ${extra_tgt}")) {
@@ -313,60 +306,48 @@
}
// Create glance resources
- if (salt.testTarget(master, "I@glance:client ${extra_tgt}")) {
- salt.enforceState(master, "I@glance:client ${extra_tgt}", 'glance.client')
- }
+ salt.enforceStateWithTest(master, "I@glance:client ${extra_tgt}", 'glance.client')
// Install and check nova service
- if (salt.testTarget(master, "I@nova:controller ${extra_tgt}")) {
- // run on first node first
- salt.enforceState(master, "I@nova:controller and *01* ${extra_tgt}", 'nova.controller')
- salt.enforceState(master, "I@nova:controller ${extra_tgt}", 'nova.controller')
- if (salt.testTarget(master, "I@keystone:server ${extra_tgt}")) {
- common.retry(3,5){
- salt.cmdRun(master, "I@keystone:server ${extra_tgt}", '. /root/keystonercv3; nova service-list')
- }
+ // run on first node first
+ salt.enforceStateWithTest(master, "I@nova:controller:role:primary ${extra_tgt}", 'nova.controller', "I@nova:controller ${extra_tgt}")
+ salt.enforceStateWithTest(master, "I@nova:controller ${extra_tgt}", 'nova.controller')
+ if (salt.testTarget(master, "I@keystone:server and I@nova:controller ${extra_tgt}")) {
+ common.retry(3,5){
+ salt.cmdRun(master, "I@keystone:server ${extra_tgt}", '. /root/keystonercv3; nova service-list')
}
}
+
// Create nova resources
- if (salt.testTarget(master, "I@nova:client ${extra_tgt}")) {
- salt.enforceState(master, "I@nova:client ${extra_tgt}", 'nova.client')
- }
+ salt.enforceStateWithTest(master, "I@nova:client ${extra_tgt}", 'nova.client')
// Install and check cinder service
- if (salt.testTarget(master, "I@cinder:controller ${extra_tgt}")) {
- // run on first node first
- salt.enforceState(master, "I@cinder:controller and *01* ${extra_tgt}", 'cinder')
- salt.enforceState(master, "I@cinder:controller ${extra_tgt}", 'cinder')
- if (salt.testTarget(master, "I@keystone:server ${extra_tgt}")) {
- common.retry(3,5){
- salt.cmdRun(master, "I@keystone:server ${extra_tgt}", '. /root/keystonercv3; cinder list')
- }
+ // run on first node first
+ salt.enforceStateWithTest(master, "I@cinder:controller:role:primary ${extra_tgt}", 'cinder', "I@cinder:controller ${extra_tgt}")
+ salt.enforceStateWithTest(master, "I@cinder:controller ${extra_tgt}", 'cinder')
+ if (salt.testTarget(master, "I@keystone:server and I@cinder:controller ${extra_tgt}")) {
+ common.retry(3,5){
+ salt.cmdRun(master, "I@keystone:server ${extra_tgt}", '. /root/keystonercv3; cinder list')
}
}
// Install neutron service
- if (salt.testTarget(master, "I@neutron:server ${extra_tgt}")) {
- // run on first node first
- salt.enforceState(master, "I@neutron:server and *01* ${extra_tgt}", 'neutron.server')
- salt.enforceState(master, "I@neutron:server ${extra_tgt}", 'neutron.server')
- if (salt.testTarget(master, "I@keystone:server ${extra_tgt}")) {
- common.retry(3,5){
- salt.cmdRun(master, "I@keystone:server ${extra_tgt}",'. /root/keystonercv3; neutron agent-list')
- }
+ // run on first node first
+ salt.enforceStateWithTest(master, "I@neutron:server:role:primary ${extra_tgt}", 'neutron.server', "I@neutron:server ${extra_tgt}")
+ salt.enforceStateWithTest(master, "I@neutron:server ${extra_tgt}", 'neutron.server')
+ if (salt.testTarget(master, "I@keystone:server and I@neutron:server ${extra_tgt}")) {
+ common.retry(3,5){
+ salt.cmdRun(master, "I@keystone:server ${extra_tgt}",'. /root/keystonercv3; neutron agent-list')
}
}
// Install heat service
- if (salt.testTarget(master, "I@heat:server ${extra_tgt}")) {
- // run on first node first
- salt.enforceState(master, "I@heat:server and *01* ${extra_tgt}", 'heat')
- salt.enforceState(master, "I@heat:server ${extra_tgt}", 'heat')
- if (salt.testTarget(master, "I@keystone:server ${extra_tgt}")) {
- common.retry(3,5){
- salt.cmdRun(master, "I@keystone:server ${extra_tgt}", '. /root/keystonercv3; heat resource-type-list')
- }
+ salt.enforceStateWithTest(master, "I@heat:server:role:primary ${extra_tgt}", 'heat', "I@heat:server ${extra_tgt}")
+ salt.enforceStateWithTest(master, "I@heat:server ${extra_tgt}", 'heat')
+ if (salt.testTarget(master, "I@keystone:server and I@heat:server ${extra_tgt}")) {
+ common.retry(3,5){
+ salt.cmdRun(master, "I@keystone:server ${extra_tgt}", '. /root/keystonercv3; openstack orchestration resource type list')
}
}
@@ -376,88 +357,69 @@
}
// Install ironic service
- if (salt.testTarget(master, "I@ironic:api ${extra_tgt}")) {
- salt.enforceState(master, "I@ironic:api and *01* ${extra_tgt}", 'ironic.api')
- salt.enforceState(master, "I@ironic:api ${extra_tgt}", 'ironic.api')
- }
+ salt.enforceStateWithTest(master, "I@ironic:api:role:primary ${extra_tgt}", 'ironic.api', "I@ironic:api ${extra_tgt}")
+ salt.enforceStateWithTest(master, "I@ironic:api ${extra_tgt}", 'ironic.api')
// Install manila service
- if (salt.testTarget(master, "I@manila:api ${extra_tgt}")) {
- salt.enforceState(master, "I@manila:api and *01* ${extra_tgt}", 'manila.api')
- salt.enforceState(master, "I@manila:api ${extra_tgt}", 'manila.api')
- }
- if (salt.testTarget(master, "I@manila:scheduler ${extra_tgt}")) {
- salt.enforceState(master, "I@manila:scheduler ${extra_tgt}", 'manila.scheduler')
- }
+ salt.enforceStateWithTest(master, "I@manila:api:role:primary ${extra_tgt}", 'manila.api', "I@manila:api ${extra_tgt}")
+ salt.enforceStateWithTest(master, "I@manila:api ${extra_tgt}", 'manila.api')
+ salt.enforceStateWithTest(master, "I@manila:scheduler ${extra_tgt}", 'manila.scheduler')
// Install designate services
if (salt.testTarget(master, "I@designate:server:enabled ${extra_tgt}")) {
- if (salt.testTarget(master, "I@designate:server:backend:bind9 ${extra_tgt}")) {
- salt.enforceState(master, "I@bind:server ${extra_tgt}", 'bind.server')
- }
- if (salt.testTarget(master, "I@designate:server:backend:pdns4 ${extra_tgt}")) {
- salt.enforceState(master, "I@powerdns:server ${extra_tgt}", 'powerdns.server')
- }
- salt.enforceState(master, "I@designate:server and *01* ${extra_tgt}", 'designate.server')
+ salt.enforceState(master, "I@designate:server:role:primary ${extra_tgt}", 'designate.server')
salt.enforceState(master, "I@designate:server ${extra_tgt}", 'designate')
}
// Install octavia api service
- if (salt.testTarget(master, "I@octavia:api ${extra_tgt}")) {
- salt.enforceState(master, "I@octavia:api and *01* ${extra_tgt}", 'octavia')
- salt.enforceState(master, "I@octavia:api ${extra_tgt}", 'octavia')
- }
+ salt.enforceStateWithTest(master, "I@octavia:api:role:primary ${extra_tgt}", 'octavia.api', "I@octavia:api ${extra_tgt}")
+ salt.enforceStateWithTest(master, "I@octavia:api ${extra_tgt}", 'octavia.api')
// Install DogTag server service
- if (salt.testTarget(master, "I@dogtag:server ${extra_tgt}")) {
- salt.enforceState(master, "I@dogtag:server and *01* ${extra_tgt}", 'dogtag.server')
- salt.enforceState(master, "I@dogtag:server ${extra_tgt}", 'dogtag.server')
- }
+ salt.enforceStateWithTest(master, "I@dogtag:server:role:master ${extra_tgt}", 'dogtag.server', "I@dogtag:server ${extra_tgt}")
+ salt.enforceStateWithTest(master, "I@dogtag:server ${extra_tgt}", 'dogtag.server')
// Install barbican server service
- if (salt.testTarget(master, "I@barbican:server ${extra_tgt}")) {
- salt.enforceState(master, "I@barbican:server and *01* ${extra_tgt}", 'barbican.server')
- salt.enforceState(master, "I@barbican:server ${extra_tgt}", 'barbican.server')
- }
+ salt.enforceStateWithTest(master, "I@barbican:server:role:primary ${extra_tgt}", 'barbican.server', "I@barbican:server ${extra_tgt}")
+ salt.enforceStateWithTest(master, "I@barbican:server ${extra_tgt}", 'barbican.server')
+
// Install barbican client
- if (salt.testTarget(master, "I@barbican:client ${extra_tgt}")) {
- salt.enforceState(master, "I@barbican:client ${extra_tgt}", 'barbican.client')
- }
+ salt.enforceStateWithTest(master, "I@barbican:client ${extra_tgt}", 'barbican.client')
// Install gnocchi server
- if (salt.testTarget(master, "I@gnocchi:server ${extra_tgt}")) {
- salt.enforceState(master, "I@gnocchi:server and *01* ${extra_tgt}", 'gnocchi.server')
- salt.enforceState(master, "I@gnocchi:server ${extra_tgt}", 'gnocchi.server')
- }
+ salt.enforceStateWithTest(master, "I@gnocchi:server:role:primary ${extra_tgt}", 'gnocchi.server', "I@gnocchi:server ${extra_tgt}")
+ salt.enforceStateWithTest(master, "I@gnocchi:server ${extra_tgt}", 'gnocchi.server')
// Apply gnocchi client state to create gnocchi archive policies, due to possible
// races, apply on the first node initially
if (salt.testTarget(master, "I@gnocchi:client ${extra_tgt}")) {
- salt.enforceState(master, "I@gnocchi:client and *01* ${extra_tgt}", 'gnocchi.client')
+ first_target = salt.getFirstMinion(master, "I@gnocchi:client ${extra_tgt}")
+ salt.enforceState(master, "${first_target} ${extra_tgt}", 'gnocchi.client')
salt.enforceState(master, "I@gnocchi:client ${extra_tgt}", 'gnocchi.client')
}
// Install gnocchi statsd
if (salt.testTarget(master, "I@gnocchi:statsd ${extra_tgt}")) {
- salt.enforceState(master, "I@gnocchi:statsd and *01* ${extra_tgt}", 'gnocchi.statsd')
+ first_target = salt.getFirstMinion(master, "I@gnocchi:statsd ${extra_tgt}")
+ salt.enforceState(master, "${first_target} ${extra_tgt}", 'gnocchi.statsd')
salt.enforceState(master, "I@gnocchi:statsd ${extra_tgt}", 'gnocchi.statsd')
}
// Install panko server
if (salt.testTarget(master, "I@panko:server ${extra_tgt}")) {
- salt.enforceState(master, "I@panko:server and *01* ${extra_tgt}", 'panko')
+ first_target = salt.getFirstMinion(master, "I@panko:server ${extra_tgt}")
+ salt.enforceState(master, "${first_target} ${extra_tgt}", 'panko')
salt.enforceState(master, "I@panko:server ${extra_tgt}", 'panko')
}
// Install ceilometer server
- if (salt.testTarget(master, "I@ceilometer:server ${extra_tgt}")) {
- salt.enforceState(master, "I@ceilometer:server and *01* ${extra_tgt}", 'ceilometer')
- salt.enforceState(master, "I@ceilometer:server ${extra_tgt}", 'ceilometer')
- }
+ salt.enforceStateWithTest(master, "I@ceilometer:server:role:primary ${extra_tgt}", 'ceilometer', "I@ceilometer:server ${extra_tgt}")
+ salt.enforceStateWithTest(master, "I@ceilometer:server ${extra_tgt}", 'ceilometer')
// Install aodh server
if (salt.testTarget(master, "I@aodh:server ${extra_tgt}")) {
- salt.enforceState(master, "I@aodh:server and *01* ${extra_tgt}", 'aodh')
+ first_target = salt.getFirstMinion(master, "I@aodh:server ${extra_tgt}")
+ salt.enforceState(master, "${first_target} ${extra_tgt}", 'aodh')
salt.enforceState(master, "I@aodh:server ${extra_tgt}", 'aodh')
}
}
@@ -466,39 +428,24 @@
def installIronicConductor(master, extra_tgt = ''){
def salt = new com.mirantis.mk.Salt()
- if (salt.testTarget(master, "I@ironic:conductor ${extra_tgt}")) {
- salt.enforceState(master, "I@ironic:conductor ${extra_tgt}", 'ironic.conductor')
- salt.enforceState(master, "I@ironic:conductor ${extra_tgt}", 'apache')
- }
- if (salt.testTarget(master, "I@tftpd_hpa:server ${extra_tgt}")) {
- salt.enforceState(master, "I@tftpd_hpa:server ${extra_tgt}", 'tftpd_hpa')
- }
+ salt.enforceStateWithTest(master, "I@ironic:conductor ${extra_tgt}", 'ironic.conductor')
+ salt.enforceStateWithTest(master, "I@ironic:conductor ${extra_tgt}", 'apache')
+ salt.enforceStateWithTest(master, "I@tftpd_hpa:server ${extra_tgt}", 'tftpd_hpa')
if (salt.testTarget(master, "I@nova:compute ${extra_tgt}")) {
salt.runSaltProcessStep(master, "I@nova:compute ${extra_tgt}", 'service.restart', ['nova-compute'])
}
- if (salt.testTarget(master, "I@baremetal_simulator:enabled ${extra_tgt}")) {
- salt.enforceState(master, "I@baremetal_simulator:enabled ${extra_tgt}", 'baremetal_simulator')
- }
- if (salt.testTarget(master, "I@ironic:client ${extra_tgt}")) {
- salt.enforceState(master, "I@ironic:client ${extra_tgt}", 'ironic.client')
- }
+ salt.enforceStateWithTest(master, "I@baremetal_simulator:enabled ${extra_tgt}", 'baremetal_simulator')
+ salt.enforceStateWithTest(master, "I@ironic:client ${extra_tgt}", 'ironic.client')
}
def installManilaShare(master, extra_tgt = ''){
def salt = new com.mirantis.mk.Salt()
- if (salt.testTarget(master, "I@manila:share ${extra_tgt}")) {
- salt.enforceState(master, "I@manila:share ${extra_tgt}", 'manila.share')
- }
- if (salt.testTarget(master, "I@manila:data ${extra_tgt}")) {
- salt.enforceState(master, "I@manila:data ${extra_tgt}", 'manila.data')
- }
-
- if (salt.testTarget(master, "I@manila:client ${extra_tgt}")) {
- salt.enforceState(master, "I@manila:client ${extra_tgt}", 'manila.client')
- }
+ salt.enforceStateWithTest(master, "I@manila:share ${extra_tgt}", 'manila.share')
+ salt.enforceStateWithTest(master, "I@manila:data ${extra_tgt}", 'manila.data')
+ salt.enforceStateWithTest(master, "I@manila:client ${extra_tgt}", 'manila.client')
}
@@ -508,23 +455,19 @@
//neutron agents in addition to neutron server. Once neutron agents
//are up neutron resources can be created without hitting the situation when neutron resources are created
//prior to neutron agents which results in creating ports in non-usable state
- if (salt.testTarget(master, "I@neutron:gateway ${extra_tgt}")) {
- salt.enforceState(master, "I@neutron:gateway ${extra_tgt}", 'neutron')
- }
+ salt.enforceStateWithTest(master, "I@neutron:gateway ${extra_tgt}", 'neutron')
// Create neutron resources - this step was moved here to ensure that
//neutron resources are created after neutron agens are up. In this case neutron ports will be in
//usable state. More information: https://bugs.launchpad.net/neutron/+bug/1399249
- if (salt.testTarget(master, "I@neutron:client ${extra_tgt}")) {
- salt.enforceState(master, "I@neutron:client ${extra_tgt}", 'neutron.client')
- }
+ salt.enforceStateWithTest(master, "I@neutron:client ${extra_tgt}", 'neutron.client')
salt.enforceHighstate(master, "I@neutron:gateway ${extra_tgt}")
// install octavia manager services
if (salt.testTarget(master, "I@octavia:manager ${extra_tgt}")) {
salt.runSaltProcessStep(master, "I@salt:master ${extra_tgt}", 'mine.update', ['*'])
- salt.enforceState(master, "I@octavia:manager ${extra_tgt}", 'octavia')
+ salt.enforceState(master, "I@octavia:manager ${extra_tgt}", 'octavia.manager')
salt.enforceState(master, "I@octavia:manager ${extra_tgt}", 'salt.minion.ca')
salt.enforceState(master, "I@octavia:manager ${extra_tgt}", 'salt.minion.cert')
}
@@ -544,7 +487,7 @@
def hightstateTarget = "${compute_compound} and not ${gluster_compound} and not ${salt_ca_compound}"
if (salt.testTarget(master, hightstateTarget)) {
retry(2) {
- salt.enforceHighstateWithExclude(master, hightstateTarget, 'opencontrail.client')
+ salt.enforceHighstate(master, hightstateTarget)
}
} else {
common.infoMsg("No minions matching highstate target found for target ${hightstateTarget}")
@@ -557,7 +500,7 @@
if ( target == cmp_target ) {
// Enforce highstate one by one on salt ca servers which are compute nodes
retry(2) {
- salt.enforceHighstateWithExclude(master, target, 'opencontrail.client')
+ salt.enforceHighstate(master, target)
}
}
}
@@ -570,7 +513,7 @@
if ( target == cmp_target ) {
// Enforce highstate one by one on glusterfs servers which are compute nodes
retry(2) {
- salt.enforceHighstateWithExclude(master, target, 'opencontrail.client')
+ salt.enforceHighstate(master, target)
}
}
}
@@ -578,30 +521,29 @@
}
// Run nova:controller to map cmp with cells
- if (salt.testTarget(master, "I@nova:controller ${extra_tgt}")) {
- salt.enforceState(master, "I@nova:controller and *01* ${extra_tgt}", 'nova.controller')
- }
+ salt.enforceState(master, "I@nova:controller:role:primary ${extra_tgt}", 'nova.controller', "I@nova:controller ${extra_tgt}")
}
def installContrailNetwork(master, extra_tgt = '') {
def common = new com.mirantis.mk.Common()
def salt = new com.mirantis.mk.Salt()
-
+ def first_target
// Install opencontrail database services
- salt.enforceState(master, "I@opencontrail:database and *01* ${extra_tgt}", 'opencontrail.database')
+ first_target = salt.getFirstMinion(master, "I@opencontrail:database ${extra_tgt}")
+ salt.enforceState(master, "${first_target} ${extra_tgt}", 'opencontrail.database')
salt.enforceState(master, "I@opencontrail:database ${extra_tgt}", 'opencontrail.database')
// Install opencontrail control services
- salt.enforceStateWithExclude(master, "I@opencontrail:control and *01* ${extra_tgt}", "opencontrail", "opencontrail.client")
+ first_target = salt.getFirstMinion(master, "I@opencontrail:control ${extra_tgt}")
+ salt.enforceStateWithExclude(master, "${first_target} ${extra_tgt}", "opencontrail", "opencontrail.client")
salt.enforceStateWithExclude(master, "I@opencontrail:control ${extra_tgt}", "opencontrail", "opencontrail.client")
- salt.enforceStateWithExclude(master, "I@opencontrail:collector and *01* ${extra_tgt}", "opencontrail", "opencontrail.client")
+ first_target = salt.getFirstMinion(master, "I@opencontrail:collector ${extra_tgt}")
+ salt.enforceStateWithExclude(master, "${first_target} ${extra_tgt}", "opencontrail", "opencontrail.client")
salt.enforceStateWithExclude(master, "I@opencontrail:collector ${extra_tgt}", "opencontrail", "opencontrail.client")
- if (salt.testTarget(master, "I@docker:client and I@opencontrail:control ${extra_tgt}")) {
- salt.enforceState(master, "I@opencontrail:control or I@opencontrail:collector ${extra_tgt}", 'docker.client')
- }
+ salt.enforceStateWithTest(master, "( I@opencontrail:control or I@opencontrail:collector ) ${extra_tgt}", 'docker.client', "I@docker:client and I@opencontrail:control ${extra_tgt}")
installBackup(master, 'contrail', extra_tgt)
}
@@ -624,10 +566,8 @@
}
sleep(300)
- if (salt.testTarget(master, "I@opencontrail:compute ${extra_tgt}")) {
- salt.enforceState(master, "I@opencontrail:compute ${extra_tgt}", 'opencontrail.client')
- salt.enforceState(master, "I@opencontrail:compute ${extra_tgt}", 'opencontrail')
- }
+ salt.enforceStateWithTest(master, "I@opencontrail:compute ${extra_tgt}", 'opencontrail.client')
+ salt.enforceStateWithTest(master, "I@opencontrail:compute ${extra_tgt}", 'opencontrail')
}
@@ -640,27 +580,56 @@
def installKubernetesControl(master, extra_tgt = '') {
def salt = new com.mirantis.mk.Salt()
+ def first_target
+ salt.fullRefresh(master, "* ${extra_tgt}")
+
+ // Bootstrap all nodes
+ salt.enforceState(master, "I@kubernetes:master ${extra_tgt}", 'linux')
+ salt.enforceState(master, "I@kubernetes:master ${extra_tgt}", 'salt.minion')
+ salt.enforceState(master, "I@kubernetes:master ${extra_tgt}", ['openssh', 'ntp'])
+
+ // Create and distribute SSL certificates for services using salt state
+ salt.enforceState(master, "I@kubernetes:master ${extra_tgt}", 'salt.minion.cert')
+
+ // Install docker
+ salt.enforceState(master, "I@docker:host ${extra_tgt}", 'docker.host')
+
+ // If network engine is not opencontrail, run addons state for kubernetes
+ if (!salt.getPillar(master, "I@kubernetes:master ${extra_tgt}", 'kubernetes:master:network:opencontrail:enabled')) {
+ salt.enforceState(master, "I@kubernetes:master ${extra_tgt}", 'kubernetes.master.kube-addons')
+ }
// Install Kubernetes pool and Calico
- salt.enforceState(master, "I@kubernetes:master ${extra_tgt}", 'kubernetes.master.kube-addons')
- salt.enforceState(master, "I@kubernetes:pool ${extra_tgt}", 'kubernetes.pool')
+ salt.enforceState(master, "I@kubernetes:master ${extra_tgt}", 'kubernetes.pool')
if (salt.testTarget(master, "I@etcd:server:setup ${extra_tgt}")) {
// Setup etcd server
- salt.enforceState(master, "I@kubernetes:master and *01* ${extra_tgt}", 'etcd.server.setup')
+ first_target = salt.getFirstMinion(master, "I@kubernetes:master ${extra_tgt}")
+ salt.enforceState(master, "${first_target} ${extra_tgt}", 'etcd.server.setup')
}
// Run k8s master at *01* to simplify namespaces creation
- salt.enforceStateWithExclude(master, "I@kubernetes:master and *01* ${extra_tgt}", "kubernetes.master", "kubernetes.master.setup")
+ first_target = salt.getFirstMinion(master, "I@kubernetes:master ${extra_tgt}")
- // Run k8s without master.setup
- salt.enforceStateWithExclude(master, "I@kubernetes:master ${extra_tgt}", "kubernetes", "kubernetes.master.setup")
+ // If network engine is opencontrail, run master state for kubernetes without kube-addons
+ // The kube-addons state will be called later only in case of opencontrail
+ if (salt.getPillar(master, "I@kubernetes:master ${extra_tgt}", 'kubernetes:master:network:opencontrail:enabled')) {
+ // Run k8s on first node without master.setup and master.kube-addons
+ salt.enforceStateWithExclude(master, "${first_target} ${extra_tgt}", "kubernetes.master", "kubernetes.master.setup,kubernetes.master.kube-addons")
+ // Run k8s without master.setup and master.kube-addons
+ salt.enforceStateWithExclude(master, "I@kubernetes:master ${extra_tgt}", "kubernetes", "kubernetes.master.setup,kubernetes.master.kube-addons")
+ } else {
+ // Run k8s on first node without master.setup and master.kube-addons
+ salt.enforceStateWithExclude(master, "${first_target} ${extra_tgt}", "kubernetes.master", "kubernetes.master.setup")
+ // Run k8s without master.setup
+ salt.enforceStateWithExclude(master, "I@kubernetes:master ${extra_tgt}", "kubernetes", "kubernetes.master.setup")
+ }
// Run k8s master setup
- salt.enforceState(master, "I@kubernetes:master and *01* ${extra_tgt}", 'kubernetes.master.setup')
+ salt.enforceState(master, "I@kubernetes:master ${extra_tgt}", 'kubernetes.master.setup')
// Restart kubelet
- salt.runSaltProcessStep(master, "I@kubernetes:pool ${extra_tgt}", 'service.restart', ['kubelet'])
+ salt.runSaltProcessStep(master, "I@kubernetes:master ${extra_tgt}", 'service.restart', ['kubelet'])
}
@@ -669,23 +638,22 @@
salt.fullRefresh(master, "*")
// Bootstrap all nodes
- salt.enforceState(master, "I@kubernetes:pool ${extra_tgt}", 'linux')
- salt.enforceState(master, "I@kubernetes:pool ${extra_tgt}", 'salt.minion')
- salt.enforceState(master, "I@kubernetes:pool ${extra_tgt}", ['openssh', 'ntp'])
+ salt.enforceState(master, "I@kubernetes:pool and not I@kubernetes:master ${extra_tgt}", 'linux')
+ salt.enforceState(master, "I@kubernetes:pool and not I@kubernetes:master ${extra_tgt}", 'salt.minion')
+ salt.enforceState(master, "I@kubernetes:pool and not I@kubernetes:master ${extra_tgt}", ['openssh', 'ntp'])
// Create and distribute SSL certificates for services using salt state
- salt.enforceState(master, "I@kubernetes:pool ${extra_tgt}", 'salt.minion.cert')
+ salt.enforceState(master, "I@kubernetes:pool and not I@kubernetes:master ${extra_tgt}", 'salt.minion.cert')
// Install docker
salt.enforceState(master, "I@docker:host ${extra_tgt}", 'docker.host')
// Install Kubernetes and Calico
- salt.enforceState(master, "I@kubernetes:pool ${extra_tgt}", 'kubernetes.pool')
+ salt.enforceState(master, "I@kubernetes:pool and not I@kubernetes:master ${extra_tgt}", 'kubernetes.pool')
// Install Tiller and all configured releases
- if (salt.testTarget(master, "I@helm:client ${extra_tgt}")) {
- salt.enforceState(master, "I@helm:client ${extra_tgt}", 'helm')
- }
+ salt.enforceStateWithTest(master, "I@helm:client ${extra_tgt}", 'helm')
+ salt.runSaltProcessStep(master, "I@kubernetes:pool and not I@kubernetes:master ${extra_tgt}", 'service.restart', ['kubelet'])
}
@@ -693,20 +661,30 @@
def salt = new com.mirantis.mk.Salt()
//Install and Configure Docker
- salt.enforceState(master, "I@docker:swarm ${extra_tgt}", 'docker.host')
- salt.enforceState(master, "I@docker:swarm:role:master ${extra_tgt}", 'docker.swarm')
- salt.enforceState(master, "I@docker:swarm ${extra_tgt}", 'salt.minion.grains')
- salt.runSaltProcessStep(master, "I@docker:swarm ${extra_tgt}", 'mine.update')
- salt.runSaltProcessStep(master, "I@docker:swarm ${extra_tgt}", 'saltutil.refresh_modules')
- sleep(5)
- salt.enforceState(master, "I@docker:swarm:role:master ${extra_tgt}", 'docker.swarm')
- if (salt.testTarget(master, "I@docker:swarm:role:manager ${extra_tgt}")){
- salt.enforceState(master, "I@docker:swarm:role:manager ${extra_tgt}", 'docker.swarm')
+ if (salt.testTarget(master, "I@docker:swarm ${extra_tgt}")) {
+ salt.enforceState(master, "I@docker:swarm ${extra_tgt}", 'docker.host')
+ salt.enforceState(master, "I@docker:swarm:role:master ${extra_tgt}", 'docker.swarm')
+ salt.enforceState(master, "I@docker:swarm ${extra_tgt}", 'salt.minion.grains')
+ salt.runSaltProcessStep(master, "I@docker:swarm ${extra_tgt}", 'mine.update')
+ salt.runSaltProcessStep(master, "I@docker:swarm ${extra_tgt}", 'saltutil.refresh_modules')
+ sleep(5)
+ salt.enforceState(master, "I@docker:swarm:role:master ${extra_tgt}", 'docker.swarm')
+ salt.enforceStateWithTest(master, "I@docker:swarm:role:manager ${extra_tgt}", 'docker.swarm')
+ sleep(10)
+ salt.cmdRun(master, "I@docker:swarm:role:master ${extra_tgt}", 'docker node ls')
}
- sleep(10)
- salt.cmdRun(master, "I@docker:swarm:role:master ${extra_tgt}", 'docker node ls')
}
+// Setup addons for kubernetes - For OpenContrail network engine
+// Use after compute nodes are ready, because K8s addons like DNS should be placed on cmp nodes
+def setupKubeAddonForContrail(master, extra_tgt = '') {
+ def salt = new com.mirantis.mk.Salt()
+
+ if (salt.getPillar(master, "I@kubernetes:master ${extra_tgt}", 'kubernetes:master:network:opencontrail:enabled')){
+ // Setup Addons for Kubernetes only in case of OpenContrail is used as neteork engine
+ salt.enforceState(master, "I@kubernetes:master ${extra_tgt}", 'kubernetes.master.kube-addons')
+ }
+}
def installCicd(master, extra_tgt = '') {
def salt = new com.mirantis.mk.Salt()
@@ -770,13 +748,9 @@
salt.cmdRun(master, jenkins_compound, 'timeout ' + (wait_timeout*60+3) + ' /bin/sh -c -- ' + '"' + check_jenkins_cmd + '"')
}
- if (salt.testTarget(master, "I@openldap:client ${extra_tgt}")) {
- salt.enforceState(master, "I@openldap:client ${extra_tgt}", 'openldap', true, true, null, false, -1, 2)
- }
+ salt.enforceStateWithTest(master, "I@openldap:client ${extra_tgt}", 'openldap', "", true, true, null, false, -1, 2)
- if (salt.testTarget(master, "I@python:environment ${extra_tgt}")) {
- salt.enforceState(master, "I@python:environment ${extra_tgt}", 'python')
- }
+ salt.enforceStateWithTest(master, "I@python:environment ${extra_tgt}", 'python')
withEnv(['ASK_ON_ERROR=false']){
retry(2){
@@ -804,6 +778,7 @@
def salt = new com.mirantis.mk.Salt()
def retries_wait = 20
def retries = 15
+ def first_target
// Install core services for K8S environments:
// HAProxy, Nginx and lusterFS clients
@@ -812,13 +787,9 @@
salt.enforceState(master, "I@haproxy:proxy ${extra_tgt}", 'haproxy')
salt.runSaltProcessStep(master, "I@haproxy:proxy ${extra_tgt}", 'service.status', ['haproxy'])
- if (salt.testTarget(master, "I@nginx:server ${extra_tgt}")) {
- salt.enforceState(master, "I@nginx:server ${extra_tgt}", 'nginx')
- }
+ salt.enforceStateWithTest(master, "I@nginx:server ${extra_tgt}", 'nginx')
- if (salt.testTarget(master, "I@glusterfs:client ${extra_tgt}")) {
- salt.enforceState(master, "I@glusterfs:client ${extra_tgt}", 'glusterfs.client', true, true, null, false, -1, 2)
- }
+ salt.enforceStateWithTest(master, "I@glusterfs:client ${extra_tgt}", 'glusterfs.client', "", true, true, null, false, -1, 2)
}
// Install MongoDB for Alerta
@@ -832,18 +803,22 @@
}
//Install Telegraf
- salt.enforceState(master, "I@telegraf:agent or I@telegraf:remote_agent ${extra_tgt}", 'telegraf')
+ salt.enforceState(master, "( I@telegraf:agent or I@telegraf:remote_agent ) ${extra_tgt}", 'telegraf')
// Install Prometheus exporters
- if (salt.testTarget(master, "I@prometheus:exporters ${extra_tgt}")) {
- salt.enforceState(master, "I@prometheus:exporters ${extra_tgt}", 'prometheus')
- }
+ salt.enforceStateWithTest(master, "I@prometheus:exporters ${extra_tgt}", 'prometheus')
//Install Elasticsearch and Kibana
- salt.enforceState(master, "*01* and I@elasticsearch:server ${extra_tgt}", 'elasticsearch.server')
- salt.enforceState(master, "I@elasticsearch:server ${extra_tgt}", 'elasticsearch.server')
- salt.enforceState(master, "*01* and I@kibana:server ${extra_tgt}", 'kibana.server')
- salt.enforceState(master, "I@kibana:server ${extra_tgt}", 'kibana.server')
+ if (salt.testTarget(master, "I@elasticsearch:server:enabled:true ${extra_tgt}")) {
+ first_target = salt.getFirstMinion(master, "I@elasticsearch:server:enabled:true ${extra_tgt}")
+ salt.enforceState(master, "${first_target} ${extra_tgt}", 'elasticsearch.server')
+ }
+ salt.enforceStateWithTest(master, "I@elasticsearch:server:enabled:true ${extra_tgt}", 'elasticsearch.server')
+ if (salt.testTarget(master, "I@kibana:server:enabled:true ${extra_tgt}")) {
+ first_target = salt.getFirstMinion(master, "I@kibana:server:enabled:true ${extra_tgt}")
+ salt.enforceState(master, "${first_target} ${extra_tgt}", 'kibana.server')
+ }
+ salt.enforceStateWithTest(master, "I@kibana:server:enabled:true ${extra_tgt}", 'kibana.server')
// Check ES health cluster status
def pillar = salt.getPillar(master, "I@elasticsearch:client ${extra_tgt}", 'elasticsearch:client:server:host')
@@ -875,7 +850,8 @@
//Install InfluxDB
if (salt.testTarget(master, "I@influxdb:server ${extra_tgt}")) {
- salt.enforceState(master, "*01* and I@influxdb:server ${extra_tgt}", 'influxdb')
+ first_target = salt.getFirstMinion(master, "I@influxdb:server ${extra_tgt}")
+ salt.enforceState(master, "${first_target} ${extra_tgt}", 'influxdb')
salt.enforceState(master, "I@influxdb:server ${extra_tgt}", 'influxdb')
}
@@ -921,14 +897,10 @@
salt.runSaltProcessStep(master, "I@docker:swarm and I@prometheus:server ${extra_tgt}", 'dockerng.ps')
//Install Prometheus LTS
- if (salt.testTarget(master, "I@prometheus:relay ${extra_tgt}")) {
- salt.enforceState(master, "I@prometheus:relay ${extra_tgt}", 'prometheus')
- }
+ salt.enforceStateWithTest(master, "I@prometheus:relay ${extra_tgt}", 'prometheus')
// Install sphinx server
- if (salt.testTarget(master, "I@sphinx:server ${extra_tgt}")) {
- salt.enforceState(master, "I@sphinx:server ${extra_tgt}", 'sphinx')
- }
+ salt.enforceStateWithTest(master, "I@sphinx:server ${extra_tgt}", 'sphinx')
//Configure Grafana
pillar = salt.getPillar(master, "ctl01* ${extra_tgt}", '_param:stacklight_monitor_address')
@@ -1013,7 +985,7 @@
sleep(5)
// Update Heka
- salt.enforceState(master, "I@heka:aggregator:enabled:True or I@heka:remote_collector:enabled:True ${extra_tgt}", 'heka')
+ salt.enforceState(master, "( I@heka:aggregator:enabled:True or I@heka:remote_collector:enabled:True ) ${extra_tgt}", 'heka')
// Update collectd
salt.enforceState(master, "I@collectd:remote_client:enabled:True ${extra_tgt}", 'collectd')
@@ -1074,10 +1046,8 @@
salt.runSaltProcessStep(master, "I@backupninja:client ${extra_tgt}", 'mine.update')
salt.enforceState(master, "I@backupninja:client ${extra_tgt}", 'backupninja')
}
- if (salt.testTarget(master, "I@backupninja:server ${extra_tgt}")) {
- salt.enforceState(master, "I@backupninja:server ${extra_tgt}", 'salt.minion.grains')
- salt.enforceState(master, "I@backupninja:server ${extra_tgt}", 'backupninja')
- }
+ salt.enforceStateWithTest(master, "I@backupninja:server ${extra_tgt}", 'salt.minion.grains')
+ salt.enforceStateWithTest(master, "I@backupninja:server ${extra_tgt}", 'backupninja')
} else if (component == 'mysql') {
// Install Xtrabackup
if (salt.testTarget(master, "I@xtrabackup:client ${extra_tgt}")) {
@@ -1087,9 +1057,7 @@
salt.runSaltProcessStep(master, "I@xtrabackup:client ${extra_tgt}", 'mine.update')
salt.enforceState(master, "I@xtrabackup:client ${extra_tgt}", 'xtrabackup')
}
- if (salt.testTarget(master, "I@xtrabackup:server ${extra_tgt}")) {
- salt.enforceState(master, "I@xtrabackup:server ${extra_tgt}", 'xtrabackup')
- }
+ salt.enforceStateWithTest(master, "I@xtrabackup:server ${extra_tgt}", 'xtrabackup')
} else if (component == 'contrail') {
// Install Cassandra backup
@@ -1100,9 +1068,7 @@
salt.runSaltProcessStep(master, "I@cassandra:backup:client ${extra_tgt}", 'mine.update')
salt.enforceState(master, "I@cassandra:backup:client ${extra_tgt}", 'cassandra.backup')
}
- if (salt.testTarget(master, "I@cassandra:backup:server ${extra_tgt}")) {
- salt.enforceState(master, "I@cassandra:backup:server ${extra_tgt}", 'cassandra.backup')
- }
+ salt.enforceStateWithTest(master, "I@cassandra:backup:server ${extra_tgt}", 'cassandra.backup')
// Install Zookeeper backup
if (salt.testTarget(master, "I@zookeeper:backup:client ${extra_tgt}")) {
salt.enforceState(master, "I@zookeeper:backup:client ${extra_tgt}", 'salt.minion.grains')
@@ -1111,9 +1077,7 @@
salt.runSaltProcessStep(master, "I@zookeeper:backup:client ${extra_tgt}", 'mine.update')
salt.enforceState(master, "I@zookeeper:backup:client ${extra_tgt}", 'zookeeper.backup')
}
- if (salt.testTarget(master, "I@zookeeper:backup:server ${extra_tgt}")) {
- salt.enforceState(master, "I@zookeeper:backup:server ${extra_tgt}", 'zookeeper.backup')
- }
+ salt.enforceStateWithTest(master, "I@zookeeper:backup:server ${extra_tgt}", 'zookeeper.backup')
} else if (component == 'ceph') {
// Install Ceph backup
if (salt.testTarget(master, "I@ceph:backup:client ${extra_tgt}")) {
@@ -1123,9 +1087,7 @@
salt.runSaltProcessStep(master, "I@ceph:backup:client ${extra_tgt}", 'mine.update')
salt.enforceState(master, "I@ceph:backup:client ${extra_tgt}", 'ceph.backup')
}
- if (salt.testTarget(master, "I@ceph:backup:server ${extra_tgt}")) {
- salt.enforceState(master, "I@ceph:backup:server ${extra_tgt}", 'ceph.backup')
- }
+ salt.enforceStateWithTest(master, "I@ceph:backup:server ${extra_tgt}", 'ceph.backup')
}
}
@@ -1140,17 +1102,15 @@
salt.enforceState(master, "I@ceph:common ${extra_tgt}", 'salt.minion.grains')
// generate keyrings
- if (salt.testTarget(master, "I@ceph:mon:keyring:mon ${extra_tgt} or I@ceph:common:keyring:admin ${extra_tgt}")) {
- salt.enforceState(master, "I@ceph:mon:keyring:mon ${extra_tgt} or I@ceph:common:keyring:admin ${extra_tgt}", 'ceph.mon')
+ if (salt.testTarget(master, "( I@ceph:mon:keyring:mon or I@ceph:common:keyring:admin ) ${extra_tgt}")) {
+ salt.enforceState(master, "( I@ceph:mon:keyring:mon or I@ceph:common:keyring:admin ) ${extra_tgt}", 'ceph.mon')
salt.runSaltProcessStep(master, "I@ceph:mon ${extra_tgt}", 'saltutil.sync_grains')
- salt.runSaltProcessStep(master, "I@ceph:mon:keyring:mon ${extra_tgt} or I@ceph:common:keyring:admin ${extra_tgt}", 'mine.update')
+ salt.runSaltProcessStep(master, "( I@ceph:mon:keyring:mon or I@ceph:common:keyring:admin ) ${extra_tgt}", 'mine.update')
sleep(5)
}
// install Ceph Mons
salt.enforceState(master, target, 'ceph.mon')
- if (salt.testTarget(master, "I@ceph:mgr ${extra_tgt}")) {
- salt.enforceState(master, "I@ceph:mgr ${extra_tgt}", 'ceph.mgr')
- }
+ salt.enforceStateWithTest(master, "I@ceph:mgr ${extra_tgt}", 'ceph.mgr')
}
def installCephOsd(master, target="I@ceph:osd", setup=true, extra_tgt = '') {
@@ -1175,45 +1135,56 @@
def salt = new com.mirantis.mk.Salt()
// install Ceph Radosgw
- if (salt.testTarget(master, "I@ceph:radosgw ${extra_tgt}")) {
+ if (salt.testTarget(master, "I@ceph:radosgw ${extra_tgt} and I@node_role.openstack-control")) {
salt.runSaltProcessStep(master, "I@ceph:radosgw ${extra_tgt}", 'saltutil.sync_grains')
salt.enforceState(master, "I@ceph:radosgw ${extra_tgt}", 'ceph.radosgw')
}
- // setup Keystone service and endpoints for swift or / and S3
- if (salt.testTarget(master, "I@keystone:client ${extra_tgt}")) {
- salt.enforceState(master, "I@keystone:client ${extra_tgt}", 'keystone.client')
+
+ // setup keyring for Openstack services
+ salt.enforceStateWithTest(master, "I@ceph:common and I@glance:server ${extra_tgt}", ['ceph.common', 'ceph.setup.keyring'])
+
+ salt.enforceStateWithTest(master, "I@ceph:common and I@cinder:controller ${extra_tgt}", ['ceph.common', 'ceph.setup.keyring'])
+
+ if (salt.testTarget(master, "I@ceph:common and I@nova:compute ${extra_tgt}")) {
+ salt.enforceState(master, "I@ceph:common and I@nova:compute ${extra_tgt}", ['ceph.common', 'ceph.setup.keyring'])
+ salt.runSaltProcessStep(master, "I@ceph:common and I@nova:compute ${extra_tgt}", 'saltutil.sync_grains')
}
+
+ salt.enforceStateWithTest(master, "I@ceph:common and I@gnocchi:server ${extra_tgt}", ['ceph.common', 'ceph.setup.keyring'])
}
def connectCeph(master, extra_tgt = '') {
def salt = new com.mirantis.mk.Salt()
+ // setup Keystone service and endpoints for swift or / and S3
+ salt.enforceStateWithTest(master, "I@keystone:client ${extra_tgt}", 'keystone.client')
+
// connect Ceph to the env
if (salt.testTarget(master, "I@ceph:common and I@glance:server ${extra_tgt}")) {
- salt.enforceState(master, "I@ceph:common and I@glance:server ${extra_tgt}", ['ceph.common', 'ceph.setup.keyring', 'glance'])
+ salt.enforceState(master, "I@ceph:common and I@glance:server ${extra_tgt}", ['glance'])
salt.runSaltProcessStep(master, "I@ceph:common and I@glance:server ${extra_tgt}", 'service.restart', ['glance-api'])
}
if (salt.testTarget(master, "I@ceph:common and I@cinder:controller ${extra_tgt}")) {
- salt.enforceState(master, "I@ceph:common and I@cinder:controller ${extra_tgt}", ['ceph.common', 'ceph.setup.keyring', 'cinder'])
+ salt.enforceState(master, "I@ceph:common and I@cinder:controller ${extra_tgt}", ['cinder'])
salt.runSaltProcessStep(master, "I@ceph:common and I@cinder:controller ${extra_tgt}", 'service.restart', ['cinder-volume'])
}
if (salt.testTarget(master, "I@ceph:common and I@nova:compute ${extra_tgt}")) {
- salt.enforceState(master, "I@ceph:common and I@nova:compute ${extra_tgt}", ['ceph.common', 'ceph.setup.keyring'])
- salt.runSaltProcessStep(master, "I@ceph:common and I@nova:compute ${extra_tgt}", 'saltutil.sync_grains')
salt.enforceState(master, "I@ceph:common and I@nova:compute ${extra_tgt}", ['nova'])
salt.runSaltProcessStep(master, "I@ceph:common and I@nova:compute ${extra_tgt}", 'service.restart', ['nova-compute'])
}
+ if (salt.testTarget(master, "I@ceph:common and I@gnocchi:server ${extra_tgt}")) {
+ salt.enforceState(master, "I@ceph:common and I@gnocchi:server:role:primary ${extra_tgt}", 'gnocchi.server')
+ salt.enforceState(master, "I@ceph:common and I@gnocchi:server ${extra_tgt}", 'gnocchi.server')
+ }
}
def installOssInfra(master, extra_tgt = '') {
def common = new com.mirantis.mk.Common()
def salt = new com.mirantis.mk.Salt()
- if (salt.testTarget(master, "I@devops_portal:config ${extra_tgt}")) {
- salt.enforceState(master, "I@devops_portal:config ${extra_tgt}", 'devops_portal.config')
- salt.enforceState(master, "I@rundeck:client ${extra_tgt}", ['linux.system.user', 'openssh'])
- salt.enforceState(master, "I@rundeck:server ${extra_tgt}", 'rundeck.server')
- }
+ salt.enforceStateWithTest(master, "I@devops_portal:config ${extra_tgt}", 'devops_portal.config', )
+ salt.enforceStateWithTest(master, "I@rundeck:client ${extra_tgt}", ['linux.system.user', 'openssh'], "I@devops_portal:config ${extra_tgt}")
+ salt.enforceStateWithTest(master, "I@rundeck:server ${extra_tgt}", 'rundeck.server', "I@devops_portal:config ${extra_tgt}")
}
def installOss(master, extra_tgt = '') {
@@ -1296,4 +1267,4 @@
else {
common.infoMsg("No applications found for orchestration")
}
-}
+}
\ No newline at end of file
diff --git a/src/com/mirantis/mk/Salt.groovy b/src/com/mirantis/mk/Salt.groovy
index 6530062..ca209be 100644
--- a/src/com/mirantis/mk/Salt.groovy
+++ b/src/com/mirantis/mk/Salt.groovy
@@ -164,6 +164,39 @@
return enforceState(saltId, target, state, output, failOnError, batch, optional, read_timeout, retries, queue, saltArgs)
}
+/**
+ * Allows to test the given target for reachability and if reachable enforces the state
+* @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method)
+ * @param target State enforcing target
+ * @param state Salt state
+ * @param testTargetMatcher Salt compound matcher to be tested (default is empty string). If empty string, param `target` will be used for tests
+ * @param output print output (optional, default true)
+ * @param failOnError throw exception on salt state result:false (optional, default true)
+ * @param batch salt batch parameter integer or string with percents (optional, default null - disable batch)
+ * @param optional Optional flag (if true pipeline will continue even if no minions for target found)
+ * @param read_timeout http session read timeout (optional, default -1 - disabled)
+ * @param retries Retry count for salt state. (optional, default -1 - no retries)
+ * @param queue salt queue parameter for state.sls calls (optional, default true) - CANNOT BE USED WITH BATCH
+ * @param saltArgs additional salt args eq. ["runas=aptly"]
+ * @return output of salt command
+ */
+def enforceStateWithTest(saltId, target, state, testTargetMatcher = "", output = true, failOnError = true, batch = null, optional = false, read_timeout=-1, retries=-1, queue=true, saltArgs=[]) {
+ def common = new com.mirantis.mk.Common()
+ if (!testTargetMatcher) {
+ testTargetMatcher = target
+ }
+ if (testTarget(saltId, testTargetMatcher)) {
+ return enforceState(saltId, target, state, output, failOnError, batch, false, read_timeout, retries, queue, saltArgs)
+ } else {
+ if (!optional) {
+ common.infoMsg("No Minions matched the target matcher: ${testTargetMatcher}, and 'optional' param was set to false. - This may signify missing pillar definition!!")
+// throw new Exception("No Minions matched the target matcher: ${testTargetMatcher}.") TODO: Change the infoMsg to Error once the methods are changed to Use named params and optional param will be set globally
+ } else {
+ common.infoMsg("No Minions matched the target matcher: ${testTargetMatcher}, but 'optional' param was set to true - Pipeline continues. ")
+ }
+ }
+}
+
/* Enforces state on given saltId and target
* @param saltId Salt Connection object or pepperEnv (the command will be sent using the selected method)
* @param target State enforcing target
@@ -248,12 +281,20 @@
def node = out["return"][i];
for(int j=0;j<node.size();j++){
def nodeKey = node.keySet()[j]
- if (!node[nodeKey].contains("Salt command execution success")) {
- throw new Exception("Execution of cmd ${originalCmd} failed. Server returns: ${node[nodeKey]}")
+ if (node[nodeKey] instanceof String) {
+ if (!node[nodeKey].contains("Salt command execution success")) {
+ throw new Exception("Execution of cmd ${originalCmd} failed. Server returns: ${node[nodeKey]}")
+ }
+ } else if (node[nodeKey] instanceof Boolean) {
+ if (!node[nodeKey]) {
+ throw new Exception("Execution of cmd ${originalCmd} failed. Server returns: ${node[nodeKey]}")
+ }
+ } else {
+ throw new Exception("Execution of cmd ${originalCmd} failed. Server returns unexpected data type: ${node[nodeKey]}")
}
}
}
- }else{
+ } else {
throw new Exception("Salt Api response doesn't have return param!")
}
}
@@ -559,7 +600,7 @@
*/
def getFirstMinion(saltId, target) {
def minionsSorted = getMinionsSorted(saltId, target)
- return minionsSorted[0].split("\\.")[0]
+ return minionsSorted[0]
}
/**
@@ -684,7 +725,22 @@
//Since the runSaltCommand uses "arg" (singular) for "runner" client this won`t work correctly on old salt 2016
//cause this version of salt used "args" (plural) for "runner" client, see following link for reference:
//https://github.com/saltstack/salt/pull/32938
- return runSaltCommand(saltId, 'runner', target, 'state.orchestrate', true, orchestrate, kwargs, 7200, 7200)
+ def common = new com.mirantis.mk.Common()
+ def result = runSaltCommand(saltId, 'runner', target, 'state.orchestrate', true, orchestrate, kwargs, 7200, 7200)
+ if(result != null){
+ if(result['return']){
+ def retcode = result['return'][0].get('retcode')
+ if (retcode != 0) {
+ throw new Exception("Orchestration state failed while running: "+orchestrate)
+ }else{
+ common.infoMsg("Orchestrate state "+orchestrate+" succeeded")
+ }
+ }else{
+ common.errorMsg("Salt result has no return attribute! Result: ${result}")
+ }
+ }else{
+ common.errorMsg("Cannot check salt result, given result is null")
+ }
}
/**
@@ -948,8 +1004,8 @@
* @param file File path to read (/etc/hosts for example)
*/
-def getFileContent(saltId, target, file) {
- result = cmdRun(saltId, target, "cat ${file}")
+def getFileContent(saltId, target, file, checkResponse = true, batch=null, output = true, saltArgs = []) {
+ result = cmdRun(saltId, target, "cat ${file}", checkResponse, batch, output, saltArgs)
return result['return'][0].values()[0].replaceAll('Salt command execution success','')
}
diff --git a/src/com/mirantis/mk/SaltModelTesting.groovy b/src/com/mirantis/mk/SaltModelTesting.groovy
index 665d0c6..2d1a888 100644
--- a/src/com/mirantis/mk/SaltModelTesting.groovy
+++ b/src/com/mirantis/mk/SaltModelTesting.groovy
@@ -1,62 +1,279 @@
package com.mirantis.mk
/**
- * setup and test salt-master
- *
- * @param masterName salt master's name
- * @param clusterName model cluster name
- * @param extraFormulas extraFormulas to install
- * @param formulasSource formulas source (git or pkg)
- * @param reclassVersion Version of used reclass (branch, tag, ...) (optional, default master)
- * @param testDir directory of model
- * @param formulasSource Salt formulas source type (optional, default pkg)
- * @param formulasRevision APT revision for formulas (optional default stable)
- * @param ignoreClassNotfound Ignore missing classes for reclass model
- * @param dockerMaxCpus max cpus passed to docker (default 0, disabled)
- * @param legacyTestingMode do you want to enable legacy testing mode (iterating through the nodes directory definitions instead of reading cluster models)
- * @param aptRepoUrl package repository with salt formulas
- * @param aptRepoGPG GPG key for apt repository with formulas
- * Return true | false
+ * Setup Docker to run some tests. Returns true/false based on
+ were tests successful or not.
+ * @param config - LinkedHashMap with configuration params:
+ * dockerHostname - (required) Hostname to use for Docker container.
+ * formulasRevision - (optional) Revision of packages to use (default proposed).
+ * runCommands - (optional) Dict with closure structure of body required tests. For example:
+ * [ '001_Test': { sh("./run-some-test") }, '002_Test': { sh("./run-another-test") } ]
+ * Before execution runCommands will be sorted by key names. Alpabetical order is preferred.
+ * runFinally - (optional) Dict with closure structure of body required commands, which should be
+ * executed in any case of test results. Same format as for runCommands
+ * updateRepo - (optional) Whether to run common repo update step.
+ * dockerContainerName - (optional) Docker container name.
+ * dockerImageName - (optional) Docker image name
+ * dockerMaxCpus - (optional) Number of CPUS to use in Docker.
+ * dockerExtraOpts - (optional) Array of Docker extra opts for container
+ * envOpts - (optional) Array of variables that should be passed as ENV vars to Docker container.
+ * Return true | false
*/
-def setupAndTestNode(masterName, clusterName, extraFormulas, testDir, formulasSource = 'pkg',
- formulasRevision = 'stable', reclassVersion = "master", dockerMaxCpus = 0,
- ignoreClassNotfound = false, legacyTestingMode = false, aptRepoUrl = '', aptRepoGPG = '', dockerContainerName = false) {
- // timeout for test execution (40min)
- def testTimeout = 40 * 60
- def TestMarkerResult = false
- def saltOpts = "--retcode-passthrough --force-color"
- def common = new com.mirantis.mk.Common()
- def workspace = common.getWorkspace()
- def img = docker.image("mirantis/salt:saltstack-ubuntu-xenial-salt-2017.7")
- img.pull()
+def setupDockerAndTest(LinkedHashMap config) {
+ def common = new com.mirantis.mk.Common()
+ def TestMarkerResult = false
+ // setup options
+ def defaultContainerName = 'test-' + UUID.randomUUID().toString()
+ def dockerHostname = config.get('dockerHostname', defaultContainerName)
+ def formulasRevision = config.get('formulasRevision', 'proposed')
+ def runCommands = config.get('runCommands', [:])
+ def runFinally = config.get('runFinally', [:])
+ def baseRepoPreConfig = config.get('baseRepoPreConfig', true)
+ def dockerContainerName = config.get('dockerContainerName', defaultContainerName)
+ def dockerImageName = config.get('image', "mirantis/salt:saltstack-ubuntu-xenial-salt-2017.7")
+ def dockerMaxCpus = config.get('dockerMaxCpus', 4)
+ def dockerExtraOpts = config.get('dockerExtraOpts', [])
+ def envOpts = config.get('envOpts', [])
+ envOpts.add("DISTRIB_REVISION=${formulasRevision}")
+ def dockerBaseOpts = [
+ '-u root:root',
+ "--hostname=${dockerHostname}",
+ '--ulimit nofile=4096:8192',
+ "--name=${dockerContainerName}",
+ "--cpus=${dockerMaxCpus}"
+ ]
- if (!extraFormulas || extraFormulas == "") {
- extraFormulas = "linux"
- }
- if (!dockerContainerName) {
- dockerContainerName = 'setupAndTestNode' + UUID.randomUUID().toString()
- }
- def dockerMaxCpusOpt = "--cpus=4"
- if (dockerMaxCpus > 0) {
- dockerMaxCpusOpt = "--cpus=${dockerMaxCpus}"
- }
- try {
- img.inside("-u root:root --hostname=${masterName} --ulimit nofile=4096:8192 ${dockerMaxCpusOpt} --name=${dockerContainerName}") {
- withEnv(["FORMULAS_SOURCE=${formulasSource}", "EXTRA_FORMULAS=${extraFormulas}", "DISTRIB_REVISION=${formulasRevision}",
- "DEBUG=1", "MASTER_HOSTNAME=${masterName}", "CLUSTER_NAME=${clusterName}", "MINION_ID=${masterName}",
- "RECLASS_VERSION=${reclassVersion}", "RECLASS_IGNORE_CLASS_NOTFOUND=${ignoreClassNotfound}", "APT_REPOSITORY=${aptRepoUrl}",
- "APT_REPOSITORY_GPG=${aptRepoGPG}", "SALT_STOPSTART_WAIT=10"]) {
- sh(script: "git clone https://github.com/salt-formulas/salt-formulas-scripts /srv/salt/scripts", returnStdout: true)
- sh("""rsync -ah ${testDir}/* /srv/salt/reclass && echo '127.0.1.2 salt' >> /etc/hosts
+ def dockerOptsFinal = (dockerBaseOpts + dockerExtraOpts).join(' ')
+ def defaultExtraReposYaml = '''
+---
+distrib_revision: 'nightly'
+aprConfD: |-
+ APT::Get::AllowUnauthenticated 'true';
+ APT::Get::Install-Suggests 'false';
+ APT::Get::Install-Recommends 'false';
+repo:
+ mcp_saltstack:
+ source: "deb [arch=amd64] http://mirror.mirantis.com/SUB_DISTRIB_REVISION/saltstack-2017.7/xenial xenial main"
+ pinning: |-
+ Package: libsodium18
+ Pin: release o=SaltStack
+ Pin-Priority: 50
+
+ Package: *
+ Pin: release o=SaltStack
+ Pin-Priority: 1100
+ mcp_extra:
+ source: "deb [arch=amd64] http://mirror.mirantis.com/SUB_DISTRIB_REVISION/extra/xenial xenial main"
+ ubuntu:
+ source: "deb [arch=amd64] http://mirror.mirantis.com/SUB_DISTRIB_REVISION/ubuntu xenial main restricted universe"
+ ubuntu-upd:
+ source: "deb [arch=amd64] http://mirror.mirantis.com/SUB_DISTRIB_REVISION/ubuntu xenial-updates main restricted universe"
+ ubuntu-sec:
+ source: "deb [arch=amd64] http://mirror.mirantis.com/SUB_DISTRIB_REVISION/ubuntu xenial-security main restricted universe"
+'''
+ def img = docker.image(dockerImageName)
+ def extraReposYaml = config.get('extraReposYaml', defaultExtraReposYaml)
+
+ img.pull()
+
+ try {
+ img.inside(dockerOptsFinal) {
+ withEnv(envOpts) {
+ try {
+ // Currently, we don't have any other point to install
+ // runtime dependencies for tests.
+ if (baseRepoPreConfig) {
+ // Warning! POssible point of 'allow-downgrades' issue
+ // Probably, need to add such flag into apt.prefs
+ sh("""#!/bin/bash -xe
+ echo "Installing extra-deb dependencies inside docker:"
+ echo > /etc/apt/sources.list
+ rm -vf /etc/apt/sources.list.d/* || true
+ """)
+ common.debianExtraRepos(extraReposYaml)
+ sh('''#!/bin/bash -xe
+ apt-get update
+ apt-get install -y python-netaddr reclass
+ ''')
+
+ }
+ runCommands.sort().each { command, body ->
+ common.warningMsg("Running command: ${command}")
+ // doCall is the closure implementation in groovy, allow to pass arguments to closure
+ body.call()
+ }
+ // If we didn't dropped for now - test has been passed.
+ TestMarkerResult = true
+ }
+ finally {
+ runFinally.sort().each { command, body ->
+ common.warningMsg("Running ${command} command.")
+ // doCall is the closure implementation in groovy, allow to pass arguments to closure
+ body.call()
+ }
+ }
+ }
+ }
+ }
+ catch (Exception er) {
+ common.warningMsg("IgnoreMe:Something wrong with img.Message:\n" + er.toString())
+ }
+
+ try {
+ common.warningMsg("IgnoreMe:Force cleanup slave.Ignore docker-daemon errors")
+ timeout(time: 10, unit: 'SECONDS') {
+ sh(script: "set -x; docker kill ${dockerContainerName} || true", returnStdout: true)
+ }
+ timeout(time: 10, unit: 'SECONDS') {
+ sh(script: "set -x; docker rm --force ${dockerContainerName} || true", returnStdout: true)
+ }
+ }
+ catch (Exception er) {
+ common.warningMsg("IgnoreMe:Timeout to delete test docker container with force!Message:\n" + er.toString())
+ }
+
+ if (TestMarkerResult) {
+ common.infoMsg("Test finished: SUCCESS")
+ } else {
+ common.warningMsg("Test finished: FAILURE")
+ }
+ return TestMarkerResult
+}
+
+/**
+ * Wrapper around setupDockerAndTest, to run checks against new Reclass version
+ * that current model is compatible with new Reclass.
+ *
+ * @param config - LinkedHashMap with configuration params:
+ * dockerHostname - (required) Hostname to use for Docker container.
+ * distribRevision - (optional) Revision of packages to use (default proposed).
+ * extraRepo - (optional) Extra repo to use to install new Reclass version. Has
+ * high priority on distribRevision
+ * targetNodes - (required) List nodes to check pillar data.
+ */
+def compareReclassVersions(config) {
+ def common = new com.mirantis.mk.Common()
+ def salt = new com.mirantis.mk.Salt()
+ common.infoMsg("Going to test new reclass for CFG node")
+ def distribRevision = config.get('distribRevision', 'proposed')
+ def venv = config.get('venv')
+ def extraRepo = config.get('extraRepo', '')
+ def extraRepoKey = config.get('extraRepoKey', '')
+ def targetNodes = config.get('targetNodes')
+ sh "rm -rf ${env.WORKSPACE}/old ${env.WORKSPACE}/new"
+ sh "mkdir -p ${env.WORKSPACE}/old ${env.WORKSPACE}/new"
+ def configRun = [
+ 'formulasRevision': distribRevision,
+ 'dockerExtraOpts' : [
+ "-v /srv/salt/reclass:/srv/salt/reclass:ro",
+ "-v /etc/salt:/etc/salt:ro",
+ "-v /usr/share/salt-formulas/:/usr/share/salt-formulas/:ro"
+ ],
+ 'envOpts' : [
+ "WORKSPACE=${env.WORKSPACE}",
+ "NODES_LIST=${targetNodes.join(' ')}"
+ ],
+ 'runCommands' : [
+ '001_Update_Reclass_package' : {
+ sh('apt-get update && apt-get install -y reclass')
+ },
+ '002_Test_Reclass_Compatibility': {
+ sh('''
+ reclass-salt -b /srv/salt/reclass -t > ${WORKSPACE}/new/inventory || exit 1
+ for node in $NODES_LIST; do
+ reclass-salt -b /srv/salt/reclass -p $node > ${WORKSPACE}/new/$node || exit 1
+ done
+ ''')
+ }
+ ]
+ ]
+ if (extraRepo) {
+ // FIXME
+ configRun['runCommands']['0001_Additional_Extra_Repo_Passed'] = {
+ sh("""
+ echo "${extraRepo}" > /etc/apt/sources.list.d/mcp_extra.list
+ [ "${extraRepoKey}" ] && wget -O - ${extraRepoKey} | apt-key add -
+ """)
+ }
+ }
+ if (setupDockerAndTest(configRun)) {
+ common.infoMsg("New reclass version is compatible with current model: SUCCESS")
+ def inventoryOld = salt.cmdRun(venv, "I@salt:master", "reclass-salt -b /srv/salt/reclass -t", true, null, true).get("return")[0].values()[0]
+ // [0..-31] to exclude 'echo Salt command execution success' from output
+ writeFile(file: "${env.WORKSPACE}/old/inventory", text: inventoryOld[0..-31])
+ for (String node in targetNodes) {
+ def nodeOut = salt.cmdRun(venv, "I@salt:master", "reclass-salt -b /srv/salt/reclass -p ${node}", true, null, true).get("return")[0].values()[0]
+ writeFile(file: "${env.WORKSPACE}/old/${node}", text: nodeOut[0..-31])
+ }
+ def reclassDiff = common.comparePillars(env.WORKSPACE, env.BUILD_URL, '')
+ currentBuild.description = reclassDiff
+ if (reclassDiff != '<b>No job changes</b>') {
+ throw new RuntimeException("Pillars with new reclass version has been changed: FAILED")
+ } else {
+ common.infoMsg("Pillars not changed with new reclass version: SUCCESS")
+ }
+ } else {
+ throw new RuntimeException("New reclass version is not compatible with current model: FAILED")
+ }
+}
+
+/**
+ * Wrapper over setupDockerAndTest, to test CC model.
+ *
+ * @param config - dict with params:
+ * dockerHostname - (required) salt master's name
+ * clusterName - (optional) model cluster name
+ * extraFormulas - (optional) extraFormulas to install. DEPRECATED
+ * formulasSource - (optional) formulas source (git or pkg, default pkg)
+ * reclassVersion - (optional) Version of used reclass (branch, tag, ...) (optional, default master)
+ * reclassEnv - (require) directory of model
+ * ignoreClassNotfound - (optional) Ignore missing classes for reclass model (default false)
+ * aptRepoUrl - (optional) package repository with salt formulas
+ * aptRepoGPG - (optional) GPG key for apt repository with formulas
+ * testContext - (optional) Description of test
+ Return: true\exception
+ */
+
+def testNode(LinkedHashMap config) {
+ def common = new com.mirantis.mk.Common()
+ def result = ''
+ def dockerHostname = config.get('dockerHostname')
+ def reclassEnv = config.get('reclassEnv')
+ def clusterName = config.get('clusterName', "")
+ def formulasSource = config.get('formulasSource', 'pkg')
+ def extraFormulas = config.get('extraFormulas', 'linux')
+ def reclassVersion = config.get('reclassVersion', 'master')
+ def ignoreClassNotfound = config.get('ignoreClassNotfound', false)
+ def aptRepoUrl = config.get('aptRepoUrl', "")
+ def aptRepoGPG = config.get('aptRepoGPG', "")
+ def testContext = config.get('testContext', 'test')
+ config['envOpts'] = [
+ "RECLASS_ENV=${reclassEnv}", "SALT_STOPSTART_WAIT=5",
+ "MASTER_HOSTNAME=${dockerHostname}", "CLUSTER_NAME=${clusterName}",
+ "MINION_ID=${dockerHostname}", "FORMULAS_SOURCE=${formulasSource}",
+ "EXTRA_FORMULAS=${extraFormulas}", "RECLASS_VERSION=${reclassVersion}",
+ "RECLASS_IGNORE_CLASS_NOTFOUND=${ignoreClassNotfound}", "DEBUG=1",
+ "APT_REPOSITORY=${aptRepoUrl}", "APT_REPOSITORY_GPG=${aptRepoGPG}",
+ "EXTRA_FORMULAS_PKG_ALL=true"
+ ]
+
+ config['runCommands'] = [
+ '001_Clone_salt_formulas_scripts': {
+ sh(script: 'git clone https://github.com/salt-formulas/salt-formulas-scripts /srv/salt/scripts', returnStdout: true)
+ },
+
+ '002_Prepare_something' : {
+ sh('''rsync -ah ${RECLASS_ENV}/* /srv/salt/reclass && echo '127.0.1.2 salt' >> /etc/hosts
cd /srv/salt && find . -type f \\( -name '*.yml' -or -name '*.sh' \\) -exec sed -i 's/apt-mk.mirantis.com/apt.mirantis.net:8085/g' {} \\;
- cd /srv/salt && find . -type f \\( -name '*.yml' -or -name '*.sh' \\) -exec sed -i 's/apt.mirantis.com/apt.mirantis.net:8085/g' {} \\;""")
- sh("""for s in \$(python -c \"import site; print(' '.join(site.getsitepackages()))\"); do
- sudo -H pip install --install-option=\"--prefix=\" --upgrade --force-reinstall -I \
- -t \"\$s\" git+https://github.com/salt-formulas/reclass.git@${reclassVersion};
- done""")
- timeout(time: testTimeout, unit: 'SECONDS') {
- sh('''#!/bin/bash
+ cd /srv/salt && find . -type f \\( -name '*.yml' -or -name '*.sh' \\) -exec sed -i 's/apt.mirantis.com/apt.mirantis.net:8085/g' {} \\;
+ ''')
+ },
+
+ '004_Run_tests' : {
+ def testTimeout = 40 * 60
+ timeout(time: testTimeout, unit: 'SECONDS') {
+ sh('''#!/bin/bash
source /srv/salt/scripts/bootstrap.sh
cd /srv/salt/scripts
source_local_envs
@@ -66,58 +283,189 @@
source /srv/salt/scripts/bootstrap.sh
cd /srv/salt/scripts
saltservice_restart''')
- sh('''#!/bin/bash
+
+ sh('''#!/bin/bash
source /srv/salt/scripts/bootstrap.sh
cd /srv/salt/scripts
source_local_envs
saltmaster_init''')
- if (!legacyTestingMode.toBoolean()) {
- sh('''#!/bin/bash
- source /srv/salt/scripts/bootstrap.sh
- cd /srv/salt/scripts
- verify_salt_minions''')
- }
+ sh('''#!/bin/bash
+ source /srv/salt/scripts/bootstrap.sh
+ cd /srv/salt/scripts
+ verify_salt_minions''')
+ }
}
- // If we didn't dropped for now - test has been passed.
- TestMarkerResult = true
- }
+ ]
+ config['runFinally'] = [
+ '001_Archive_artefacts': {
+ sh(script: "cd /tmp; tar -czf ${env.WORKSPACE}/nodesinfo.tar.gz *reclass*", returnStatus: true)
+ archiveArtifacts artifacts: "nodesinfo.tar.gz"
+ }
+ ]
+ testResult = setupDockerAndTest(config)
+ if (testResult) {
+ common.infoMsg("Node test for context: ${testContext} model: ${reclassEnv} finished: SUCCESS")
+ } else {
+ throw new RuntimeException("Node test for context: ${testContext} model: ${reclassEnv} finished: FAILURE")
}
- }
- catch (Exception er) {
- common.warningMsg("IgnoreMe:Something wrong with img.Message:\n" + er.toString())
- }
+ return testResult
+}
- if (legacyTestingMode.toBoolean()) {
- common.infoMsg("Running legacy mode test for master hostname ${masterName}")
- def nodes = sh(script: "find /srv/salt/reclass/nodes -name '*.yml' | grep -v 'cfg*.yml'", returnStdout: true)
- for (minion in nodes.tokenize()) {
- def basename = sh(script: "set +x;basename ${minion} .yml", returnStdout: true)
- if (!basename.trim().contains(masterName)) {
- testMinion(basename.trim())
- }
- }
- }
+/**
+ * setup and test salt-master
+ *
+ * @param masterName salt master's name
+ * @param clusterName model cluster name
+ * @param extraFormulas extraFormulas to install. DEPRECATED
+ * @param formulasSource formulas source (git or pkg)
+ * @param reclassVersion Version of used reclass (branch, tag, ...) (optional, default master)
+ * @param testDir directory of model
+ * @param formulasSource Salt formulas source type (optional, default pkg)
+ * @param formulasRevision APT revision for formulas (optional default stable)
+ * @param ignoreClassNotfound Ignore missing classes for reclass model
+ * @param dockerMaxCpus max cpus passed to docker (default 0, disabled)
+ * @param legacyTestingMode do you want to enable legacy testing mode (iterating through the nodes directory definitions instead of reading cluster models)
+ * @param aptRepoUrl package repository with salt formulas
+ * @param aptRepoGPG GPG key for apt repository with formulas
+ * Return true | false
+ */
- try {
- common.warningMsg("IgnoreMe:Force cleanup slave.Ignore docker-daemon errors")
- timeout(time: 10, unit: 'SECONDS') {
- sh(script: "set -x; docker kill ${dockerContainerName} || true", returnStdout: true)
- }
- timeout(time: 10, unit: 'SECONDS') {
- sh(script: "set -x; docker rm --force ${dockerContainerName} || true", returnStdout: true)
- }
- }
- catch (Exception er) {
- common.warningMsg("IgnoreMe:Timeout to delete test docker container with force!Message:\n" + er.toString())
- }
+def setupAndTestNode(masterName, clusterName, extraFormulas = '*', testDir, formulasSource = 'pkg',
+ formulasRevision = 'stable', reclassVersion = "master", dockerMaxCpus = 0,
+ ignoreClassNotfound = false, legacyTestingMode = false, aptRepoUrl = '', aptRepoGPG = '', dockerContainerName = false) {
+ def common = new com.mirantis.mk.Common()
+ // TODO
+ common.errorMsg('You are using deprecated function!Please migrate to "setupDockerAndTest".' +
+ 'It would be removed after 2018.q4 release!Pushing forced 60s sleep..')
+ sh('sleep 60')
+ // timeout for test execution (40min)
+ def testTimeout = 40 * 60
+ def TestMarkerResult = false
+ def saltOpts = "--retcode-passthrough --force-color"
+ def workspace = common.getWorkspace()
+ def img = docker.image("mirantis/salt:saltstack-ubuntu-xenial-salt-2017.7")
+ img.pull()
- if (TestMarkerResult) {
- common.infoMsg("Test finished: SUCCESS")
- } else {
- common.warningMsg("Test finished: FAILURE")
- }
- return TestMarkerResult
+ if (formulasSource == 'pkg') {
+ if (extraFormulas) {
+ common.warningMsg("You have passed deprecated variable:extraFormulas=${extraFormulas}. " +
+ "\n It would be ignored, and all formulas would be installed anyway")
+ }
+ }
+ if (!dockerContainerName) {
+ dockerContainerName = 'setupAndTestNode' + UUID.randomUUID().toString()
+ }
+ def dockerMaxCpusOpt = "--cpus=4"
+ if (dockerMaxCpus > 0) {
+ dockerMaxCpusOpt = "--cpus=${dockerMaxCpus}"
+ }
+ try {
+ img.inside("-u root:root --hostname=${masterName} --ulimit nofile=4096:8192 ${dockerMaxCpusOpt} --name=${dockerContainerName}") {
+ withEnv(["FORMULAS_SOURCE=${formulasSource}", "EXTRA_FORMULAS=${extraFormulas}", "EXTRA_FORMULAS_PKG_ALL=true",
+ "DISTRIB_REVISION=${formulasRevision}",
+ "DEBUG=1", "MASTER_HOSTNAME=${masterName}",
+ "CLUSTER_NAME=${clusterName}", "MINION_ID=${masterName}",
+ "RECLASS_VERSION=${reclassVersion}", "RECLASS_IGNORE_CLASS_NOTFOUND=${ignoreClassNotfound}",
+ "APT_REPOSITORY=${aptRepoUrl}", "SALT_STOPSTART_WAIT=5",
+ "APT_REPOSITORY_GPG=${aptRepoGPG}"]) {
+ try {
+ // Currently, we don't have any other point to install
+ // runtime dependencies for tests.
+ sh("""#!/bin/bash -xe
+ echo "Installing extra-deb dependencies inside docker:"
+ echo "APT::Get::AllowUnauthenticated 'true';" > /etc/apt/apt.conf.d/99setupAndTestNode
+ echo "APT::Get::Install-Suggests 'false';" >> /etc/apt/apt.conf.d/99setupAndTestNode
+ echo "APT::Get::Install-Recommends 'false';" >> /etc/apt/apt.conf.d/99setupAndTestNode
+ rm -vf /etc/apt/sources.list.d/* || true
+ echo 'deb [arch=amd64] http://mirror.mirantis.com/$DISTRIB_REVISION/ubuntu xenial main restricted universe' > /etc/apt/sources.list
+ echo 'deb [arch=amd64] http://mirror.mirantis.com/$DISTRIB_REVISION/ubuntu xenial-updates main restricted universe' >> /etc/apt/sources.list
+ apt-get update
+ apt-get install -y python-netaddr
+ """)
+ sh(script: "git clone https://github.com/salt-formulas/salt-formulas-scripts /srv/salt/scripts", returnStdout: true)
+ sh("""rsync -ah ${testDir}/* /srv/salt/reclass && echo '127.0.1.2 salt' >> /etc/hosts
+ cd /srv/salt && find . -type f \\( -name '*.yml' -or -name '*.sh' \\) -exec sed -i 's/apt-mk.mirantis.com/apt.mirantis.net:8085/g' {} \\;
+ cd /srv/salt && find . -type f \\( -name '*.yml' -or -name '*.sh' \\) -exec sed -i 's/apt.mirantis.com/apt.mirantis.net:8085/g' {} \\;
+ """)
+ // FIXME: should be changed to use reclass from mcp_extra_nigtly?
+ sh("""for s in \$(python -c \"import site; print(' '.join(site.getsitepackages()))\"); do
+ sudo -H pip install --install-option=\"--prefix=\" --upgrade --force-reinstall -I \
+ -t \"\$s\" git+https://github.com/salt-formulas/reclass.git@${reclassVersion};
+ done""")
+ timeout(time: testTimeout, unit: 'SECONDS') {
+ sh('''#!/bin/bash
+ source /srv/salt/scripts/bootstrap.sh
+ cd /srv/salt/scripts
+ source_local_envs
+ configure_salt_master
+ configure_salt_minion
+ install_salt_formula_pkg
+ source /srv/salt/scripts/bootstrap.sh
+ cd /srv/salt/scripts
+ saltservice_restart''')
+ sh('''#!/bin/bash
+ source /srv/salt/scripts/bootstrap.sh
+ cd /srv/salt/scripts
+ source_local_envs
+ saltmaster_init''')
+
+ if (!legacyTestingMode.toBoolean()) {
+ sh('''#!/bin/bash
+ source /srv/salt/scripts/bootstrap.sh
+ cd /srv/salt/scripts
+ verify_salt_minions
+ ''')
+ }
+ }
+ // If we didn't dropped for now - test has been passed.
+ TestMarkerResult = true
+ }
+
+ finally {
+ // Collect rendered per-node data.Those info could be simply used
+ // for diff processing. Data was generated via reclass.cli --nodeinfo,
+ /// during verify_salt_minions.
+ sh(script: "cd /tmp; tar -czf ${env.WORKSPACE}/nodesinfo.tar.gz *reclass*", returnStatus: true)
+ archiveArtifacts artifacts: "nodesinfo.tar.gz"
+ }
+ }
+ }
+ }
+ catch (Exception er) {
+ common.warningMsg("IgnoreMe:Something wrong with img.Message:\n" + er.toString())
+ }
+
+ if (legacyTestingMode.toBoolean()) {
+ common.infoMsg("Running legacy mode test for master hostname ${masterName}")
+ def nodes = sh(script: "find /srv/salt/reclass/nodes -name '*.yml' | grep -v 'cfg*.yml'", returnStdout: true)
+ for (minion in nodes.tokenize()) {
+ def basename = sh(script: "set +x;basename ${minion} .yml", returnStdout: true)
+ if (!basename.trim().contains(masterName)) {
+ testMinion(basename.trim())
+ }
+ }
+ }
+
+ try {
+ common.warningMsg("IgnoreMe:Force cleanup slave.Ignore docker-daemon errors")
+ timeout(time: 10, unit: 'SECONDS') {
+ sh(script: "set -x; docker kill ${dockerContainerName} || true", returnStdout: true)
+ }
+ timeout(time: 10, unit: 'SECONDS') {
+ sh(script: "set -x; docker rm --force ${dockerContainerName} || true", returnStdout: true)
+ }
+ }
+ catch (Exception er) {
+ common.warningMsg("IgnoreMe:Timeout to delete test docker container with force!Message:\n" + er.toString())
+ }
+
+ if (TestMarkerResult) {
+ common.infoMsg("Test finished: SUCCESS")
+ } else {
+ common.warningMsg("Test finished: FAILURE")
+ }
+ return TestMarkerResult
}
@@ -128,5 +476,60 @@
*/
def testMinion(minionName) {
- sh(script: "bash -c 'source /srv/salt/scripts/bootstrap.sh; cd /srv/salt/scripts && verify_salt_minion ${minionName}'", returnStdout: true)
+ sh(script: "bash -c 'source /srv/salt/scripts/bootstrap.sh; cd /srv/salt/scripts && verify_salt_minion ${minionName}'", returnStdout: true)
+}
+
+/**
+ * Wrapper over setupAndTestNode, to test exactly one CC model.
+ Whole workspace and model - should be pre-rendered and passed via MODELS_TARGZ
+ Flow: grab all data, and pass to setupAndTestNode function
+ under-modell will be directly mirrored to `model/{cfg.testReclassEnv}/* /srv/salt/reclass/*`
+ *
+ * @param cfg - dict with params:
+ MODELS_TARGZ http link to arch with (models|contexts|global_reclass)
+ modelFile
+ DockerCName directly passed to setupAndTestNode
+ EXTRA_FORMULAS directly passed to setupAndTestNode
+ DISTRIB_REVISION directly passed to setupAndTestNode
+ reclassVersion directly passed to setupAndTestNode
+
+ Return: true\exception
+ */
+
+def testCCModel(cfg) {
+ def common = new com.mirantis.mk.Common()
+ common.errorMsg('You are using deprecated function!Please migrate to "testNode".' +
+ 'It would be removed after 2018.q4 release!Pushing forced 60s sleep..')
+ sh('sleep 60')
+ sh(script: 'find . -mindepth 1 -delete || true', returnStatus: true)
+ sh(script: "wget --progress=dot:mega --auth-no-challenge -O models.tar.gz ${cfg.MODELS_TARGZ}")
+ // unpack data
+ sh(script: "tar -xzf models.tar.gz ")
+ common.infoMsg("Going to test exactly one context: ${cfg.modelFile}\n, with params: ${cfg}")
+ content = readFile(file: cfg.modelFile)
+ templateContext = readYaml text: content
+ clusterName = templateContext.default_context.cluster_name
+ clusterDomain = templateContext.default_context.cluster_domain
+
+ def testResult = false
+ testResult = setupAndTestNode(
+ "cfg01.${clusterDomain}",
+ clusterName,
+ '',
+ cfg.testReclassEnv, // Sync into image exactly one env
+ 'pkg',
+ cfg.DISTRIB_REVISION,
+ cfg.reclassVersion,
+ 0,
+ false,
+ false,
+ '',
+ '',
+ cfg.DockerCName)
+ if (testResult) {
+ common.infoMsg("testCCModel for context: ${cfg.modelFile} model: ${cfg.testReclassEnv} finished: SUCCESS")
+ } else {
+ throw new RuntimeException("testCCModel for context: ${cfg.modelFile} model: ${cfg.testReclassEnv} finished: FAILURE")
+ }
+ return testResult
}