How to check for a certain Status Code (4xx) in Ansible? - ansible

What I want is: stop the program until status code 412 appears and then proceed.
I have the following code to check for the status code 412:
- name: Check if plan was applied
uri:
url: "https://website.some.url.com/api/v1/clusters/elasticsearch/{{elasticClusterDetails.elastic$
method: GET
user: admin
password: "{{rootpw.stdout}}"
force_basic_auth: yes
validate_certs: no
register: result
until: result.status == 412
retries: 20
delay: 30
After a few retries I get
ERROR: [...] status was 412 not 200
So the 412 actually comes up but is not recognized as fulfillment of the 'until' condition and the program exits.
From my understanding, the request can't be made when the code switches from 200 to 412.
What do I need to change to not getting an error at Code 412?
Please NOTE: This is no duplicate because checking for 4xx status code is different than to check for 2xx

Take a look at the docs for the uri module, and you'll see that there is a status_code attribute that can be used to specify one or more status codes that are considered "successful". So something like (assuming that you expect to receive either a 200 or 412 response):
- name: Check if plan was applied
uri:
url: "https://website.some.url.com/api/v1/clusters/elasticsearch/{{elasticClusterDetails.elastic$
method: GET
user: admin
password: "{{rootpw.stdout}}"
force_basic_auth: yes
validate_certs: no
status_code: [200,412]
register: result
until: result.status == 412
retries: 20
delay: 30
The problem you have right now is that the 412 status code is considered a failure.
You could also set ignore_errors: true on the task, but using the status_code attribute is probably better, because it still allows the task to failure in the event you receive unexpected status codes.
NB: The docs say, "can also be a comma-separated list of status codes", but the source looks like it expects an actual YAML list. So you may need to tweak the value depending on which value actually works.

Related

Prometheus Alertmanager Expression Problem/Question(s) concerning Lists

I am trying to create an prometheus-alert-expression that checks if a mountpoint is mounted and sends an alert if the mountpoint is missing .. The Idea was something like this:
groups:
- name: mountpoints
rules:
- alert: /ghome missing
expr: absent(node_filesystem_avail_bytes{mountpoint="/ghome", instance="my.machine.org:9100"})
for: 60s
labels:
severity: critical
annotations:
summary: "/ghome missing on ({{ $labels.instance }})."
description: "VALUE = {{ $value }}\n LABELS = {{ $labels }}"
This kinda works. But is there a way of passing a list/vector to the mountpoint(s) and/or instance(s).
Using this Expression I'll have to write an alert-rule for each instance and each mountpoint.
I had the Idea of trying regular-Expressions like
expr: absent(node_filesystem_avail_bytes{mountpoint=~"/ghome|/something|/other", instance=~"my.machine.org:9100|another.machine.org:9100"})
.. but this obviously does not work.
Does anybody have an idea how to implement this ?
Greetings
Volker
When we pass an expression such as absent(my_metric{label=~"1|2"}), this is evaluated as such:
my_metric{label=~"1|2"} might return 4 possible results:
No result
my_metric{label="1"}
my_metric{label="2"}
both my_metric{label="1"} and my_metric{label="2"}
And the absent function is then called upon these results, and for absent to return "1" it will only do so when there are no results. Missing the case when 1 of them is absent.
Unfortunately there's no one-liner for this, we'll have to be explict with absent, we can either have multiple alert-rules or use the or operator such as:
absent(my_metric{label="1"}) or absnet(my_metrci{label="2"})

GCP Workflow: Handling http functions responses other than 200

When calling http endpoint in GCP workflow, only HttpStatus 200 is considered a success.
How to handle other Success Status codes? 201, 202, etc.
Example workflow from samples:
- readItem:
try:
call: http.get
args:
url: https://example.com/someapi
auth:
type: OIDC
result: APIResponse
except:
as: e
steps:
- knownErrors:
switch:
- condition: ${not("HttpError" in e.tags)}
next: connectionProblem
- condition: ${e.code == 404}
next: urlNotFound
- condition: ${e.code == 403}
next: authProblem
- UnhandledException:
raise: ${e}
- urlFound:
return: ${APIResponse.body}
- connectionProblem:
return: "Connection problem; check URL"
- urlNotFound:
return: "Sorry, URL wasn't found"
- authProblem:
return: "Authentication error"
If the api endpoint https://example.com/someapi returns anything other than a 200 status code the connectionProblem is invoked.
This is the same if its a GET or POST request.
What is the best way of handling this?
There is nothing referencing how to handle other 200s statuses in the documentation for Google Workflows so I assume this is not possible without treating them as errors.
This means that in order to do it you are going to need to add an extra step to deal with this status as an error handling strategy, like - condition: ${e.code == 201} for example.
Alternatively you could open a feature request in Google's Issue Tracker so that they can consider implementing different treatements of such status codes or at least so that this is touched on more details in the documentation.
At present response codes >= 400 and <= 599 are considered an error and will raise an exception. i.e. 200s are considered a success and will not.
Alternatively, if you want to trigger an exception handler for return codes in this range (or for any other reason), this can be done by adding an additional step to the try call, for example (illustration only):
main:
steps:
- getStuff:
try:
steps:
- callStep:
call: http.get
args:
url: <SOME URL>
result: r
- checkNotOK:
switch:
- condition: ${r.code == 202}
raise: ${r}
retry:
predicate: ${custom_predicate}
max_retries: 5
backoff:
initial_delay: 2
max_delay: 60
multiplier: 2
custom_predicate:
params: [e]
steps:
- what_to_repeat:
switch:
- condition: ${e.code == 202}
return: true
- otherwise:
return: false

Taurus pre-request authentication

I've faced a problem using Taurus. Could someone help me, please? I'm trying to simulate 300 users but before sending those 300 users POST requests, I need to generate a token. The token is attached to the request in that way:
- url: http://url?user_token=${auth_token}
Now I have the following scenario:
load_api:
requests:
- once:
- url: https://endpoint/authenticateUser
method: POST
headers:
Content-Type: application/json
body:
username: username
password: pass
generateToken: true
extract-jsonpath:
auth_token:
jsonpath: $.token
label: get_token
- url: http://url/user_token=${auth_token}
method: POST
headers:
Content-Type: application/json
body-file: test_data/body.json
label: sending_300
As you can see, a token is generated for each thread. And I need that it is generated before the script and then the token is attached to the URL as a parameter. I've tried to separate those for two scenarios but in that way variables from one script can't be used in the other. I was also looking at global variables but it seems like that kind of variable can be created only before executing. So, if someone could help me, I'll appreciate your time spent.
EDIT (thank you so much Dmitri T):
Here is a workable script:
execution:
concurrency: 300
scenario: load_test
scenarios:
load_test:
requests:
- if: ${__groovy(ctx.getThreadNum() == 0 && vars.getIteration() == 1,)}
then:
- url: https://url/authenticateUser
method: POST
headers:
Content-Type: application/json
body:
username: username
password: pass
generateToken: true
extract-jsonpath:
auth_token:
jsonpath: $.token
label: get_token
jsr223: props.put('auth_token', vars.get('auth_token'))
else:
- url: http://endpoint?user_token=${__P(auth_token,)}
method: POST
headers:
Content-Type: application/json
body-file: test_data/body.json
label: sending_300_reqs
think-time: 10s # waiter for processing auth request
If you want to generate a token once and share it across 300 threads:
Generate token for 1st thread during the first iteration using If block and convert it to JMeter Property in JSR223 block. The condition for the If block would be:
${__groovy(ctx.getThreadNum() == 0 && vars.getIteration() == 1,)}
and the code for JSR223 block:
props.put('auth_token', vars.get('auth_token'))
check out Top 8 JMeter Java Classes You Should Be Using with Groovy article to learn what these ctx, props and vars words mean
In your 2nd request refer the property using __P() function
http://url/user_token=${__P(auth_token,)}

Listing more than 100 records in Route 53 using Ansible

I'm currently using the route53_facts module on a project. I have 250 record sets in one hosted zone. I'm having difficulty with listing all record sets in that zone. The Route 53 API works by returning pages of maximum 100 records at a time. In order to retrieve the next page, you must pass the NextRecordName response value to the route53_facts module's start_record_name: field (pretty straightforward).
The issue I'm having specifically is getting Ansible to do this. Presumably one would do this using a loop, e.g. in pseudocode:
start
get 100 records
do until response does not contain NextRecordName:
get 100 records (start_record_name=NextRecordName)
end
In Ansible, I have written the below task to do this:
- block:
- name: List record sets in a given hosted zone
route53_facts:
query: record_sets
hosted_zone_id: "/hostedzone/ZZZ1111112222"
max_items: 100
start_record_name: "{{ record_sets.NextRecordName | default(omit) }}"
register: record_sets
until: record_sets.NextRecordName is not defined
when: "'{{ hosted_zone['Name'] }}' == 'test.example.com.'"
...however, this does not work as expected. Instead of continuously paging through responses until no more records are left, it repeatedly returns the first 100 records ("the first page").
As I can see from the Ansible debug output, start_record_name: is repeatedly null:
"attempts": 2,
"changed": false,
"invocation": {
"module_args": {
"aws_access_key": null,
"aws_secret_key": null,
"change_id": null,
"delegation_set_id": null,
"dns_name": null,
"ec2_url": null,
"health_check_id": null,
"health_check_method": "list",
"hosted_zone_id": "/hostedzone/ZZZ1111112222",
"hosted_zone_method": "list",
"max_items": "100",
"next_marker": null,
"profile": null,
"query": "record_sets",
"region": null,
"resource_id": null,
"security_token": null,
"start_record_name": null,
"type": null,
"validate_certs": true
}
},
...my guess is that the | default(omit) filter is always being executed. In other words, record_sets.NextRecordName is never initialized at this point in the task.
I'm hoping somebody can assist me in getting Ansible to return all records from a zone in Route 53. I think I've gotten tangled up in Ansible's looping behavior. Thanks!
Caveat this with "as best I can tell:"
To answer your question, it actually seems that until: and register: do not interact in the same way that when: and register: do. The best explanation I have is that until: behaves like a database transaction: it rolls back the register: assignment if the conditional is false, meaning that when the body of the until: task is tried again, it uses the same parameters as the first time. The only thing which keeps an until: block from being an infinite loop is the retries: value.
So, in your specific case, I think this will do the job:
- name: initial record_set
route53_facts:
# bootstrap so the upcoming "when:" will evaluate correctly
register: record_facts
- set_fact:
# capture the initial answer
records0: '{{ record_facts.ResourceRecordSets }}'
- name: rest of them
route53_facts:
start_record_name: '{{ record_facts.NextRecordName }}'
register: record_facts
when: record_facts.NextRecordName | default("")
with_sequence: count=10
- set_fact:
all_records: >-
{{ record0 + (record_facts.results |
selectattr("ResourceRecordSets", "defined") |
map(attribute="ResourceRecordSets") | list) }}
The with_sequence: is a hack because loop: (for which with_* is syntatic sugar) needs a list of items over which to iterate, but given that the responses that come back without NextRecordName will cause the when: to fail, skipping them, makes the (in your case) 3 through 10 items resolve almost immediately.
Then you just need to pull out the actual response data from the now list of route53_facts: replies, and glue them to the initial one to get the complete list.
Having said all of that, I am now convinced that route53_facts: (and any other AWS module that pushes the burden of that iteration into the playbook) behavior is a bug. The module caller already has a max_items: available to them, but it's an implementation detail that that any value can't be larger than some random pagination cut-off.

How do i know the service status in ansible?

In my ansible coding i want to know the status of the service like service httpd status (service is runngin or not) the result would be store in to variable. Using that status i will use some other code in ansible.
I am using ansible service module there is no option for status. If i use the shell module i got this warning
[WARNING]: Consider using service module rather than running service
so is it any other module doing to get service status?
No, there is no standard module to get services' statuses.
But you can suppress warning for specific command task if you know what are you doing:
- command: service httpd status
args:
warn: false
I've posted a quick note about this trick a while ago.
You can use the service_facts module.
For example, say I want to see the status of Apache.
- name: Check for apache status
service_facts:
- debug:
var: ansible_facts.services.apache2.state
The output is:
ok: [192.168.blah.blah] => {
"ansible_facts.services.apache2.state": "running"
}
If you would like to see all of them, you can do that by just going two levels up in the array:
var: ansible_facts.services
The output will list all the services, and will look like this (truncated for the sake of brevity):
"apache2": {
"name": "apache2",
"source": "sysv",
"state": "running"
},
"apache2.service": {
"name": "apache2.service",
"source": "systemd",
"state": "running"
},
"apparmor": {
"name": "apparmor",
"source": "sysv",
"state": "running"
},
etc,
etc
I am using Ansible 2.7. Here are the docs for that module: Click here
here is an example of starting a service and then checking status using service facts, in my example you have to register the variable then output it using debug var and pointing to the correct format in the json chain resulting output:
## perform start service for alertmanager
- name: Start service alertmanager if not started
become: yes
service:
name: alertmanager
state: started
## check to see the state of the alertmanager service status
- name: Check status of alertmanager service
service_facts:
register: service_state
- debug:
var: service_state.ansible_facts.services["alertmanager.service"].state
Hopefully service: allow user to query service status #3316 will be merged into the core module soon.
You can patch it by hand using this diff to system/service.py
Here's my diff using ansible 2.2.0.0. I've run this on my mac/homebrew install and it works for me.
This is the file that I edited: /usr/local/Cellar/ansible/2.2.0.0_2/libexec/lib/python2.7/site-packages/ansible/modules/core/system/service.py
## -36,11 +36,12 ##
- Name of the service.
state:
required: false
- choices: [ started, stopped, restarted, reloaded ]
+ choices: [ started, stopped, status, restarted, reloaded ]
description:
- C(started)/C(stopped) are idempotent actions that will not run
- commands unless necessary. C(restarted) will always bounce the
- service. C(reloaded) will always reload. B(At least one of state
+ commands unless necessary. C(status) would report the status of
+ the service C(restarted) will always bounce the service.
+ C(reloaded) will always reload. B(At least one of state
and enabled are required.)
sleep:
required: false
## -1455,7 +1456,7 ##
module = AnsibleModule(
argument_spec = dict(
name = dict(required=True),
- state = dict(choices=['running', 'started', 'stopped', 'restarted', 'reloaded']),
+ state = dict(choices=['running', 'started', 'stopped', 'status', 'restarted', 'reloaded']),
sleep = dict(required=False, type='int', default=None),
pattern = dict(required=False, default=None),
enabled = dict(type='bool'),
## -1501,6 +1502,9 ##
else:
service.get_service_status()
+ if module.params['state'] == 'status':
+ module.exit_json(state=service.running)
+
# Calculate if request will change service state
service.check_service_changed()

Resources