I'm updating several hosts with ansible at the same time, however I have a limitation...
I have to download artifacts from a common repository with no more than 3 simultaneous downloads!
The current solution I have is to limit the whole playbook to max three concurrent tasks
strategy: linear
serial: 3
Is it possible to limit concurrency only for particular task step rather than the whole playbook?
There's no direct way. Only workarounds like run_once loop with delegate_to or multiplying the task with loop and executing only one item per host.
See issue #12170, which is closed with "won't fix" status for details.
delegate_to loop:
- mytask: ..
delegate_to: "{{item}}"
run_once: true
# many diff ways to make the loop
with_inventory_hostnames: all
multiplied task:
- name: target task
debug: msg="Performing task on {{ inventory_hostname }}, item is {{ item }}"
with_items: "{{ play_hosts }}"
when: "hostvars[item].inventory_hostname == inventory_hostname"
Yes, it's possible to only limit the concurrency of a certain task.
You just need to add the throttle keyword to your download task.
Example:
- name: Download payload.tar.gz
get_url:
url: https://example.com/path/payload.tar.gz
dest: /mnt/scratch/payload.tar.gz
mode: '0640'
throttle: 3
Note that throttle was introduced in Ansible 2.9.
Thanks to previous comments and #12170 I have come with my proposal, which still is not working as desired for the download case.
Notice that 2 is the maximum number of concurrent tasks executions desired.
- name: Download at ratio three at most
win_get_url:
url: http://ipv4.download.thinkbroadband.com/100MB.zip
dest: c:/ansible/100MB.zip
force: yes
with_sequence: start=0 end={{ (( play_hosts | length ) / 2 ) | round (0, 'floor') | int }}
when: "(( ansible_play_batch.index(inventory_hostname) % (( play_hosts | length ) / 2 )) | round) == (item | int)"
While this will match the when on each iteration only if for certain hosts I still can see all the server performing the download at the same time.
Another way of testing it is with debug a message and a add a delay between iterations. This way is clear that only two are executed at each iterations.
- debug:
msg: "Item {{ item }} with modulus {{ (( ansible_play_batch.index(inventory_hostname) % (( play_hosts | length ) / 2 )) | round) }}"
with_sequence: start=0 end={{ (( play_hosts | length ) / 2 ) | round (0, 'floor') | int }}
loop_control:
pause: 2
when: "(( ansible_play_batch.index(inventory_hostname) % (( play_hosts | length ) / 2 )) | round) == (item | int)"
This seems like a case of the XY problem.
Why not download the files once to your controller, and then use the copy task to fan out from the controller to each host?
(I guess bandwidth concerns between your controller and hosts may cause issues for large files, but it's probably not going to be much different to limiting the download to 3 hosts anyway.)
You can override forks variable in ansible.cf. default value is 5.
ansible.cf
[defaults]
forks = 3
More info
Related
I have two variables:
name: "abc232323defg10"
cycle: "4"
I need to generate a list:
list:
- abc232323defg10
- abc232323defg9
- abc232323defg8
- abc232323defg7
where:
abc232323defg9 = abc232323defg(10-(cycle-3)),
abc232323defg8 = abc232323defg(10-(cycle-2)),
abc232323defg7 = abc232323defg(10-(cycle-1))
The variable "cycle" is the same as the number of items in the list, and I already have item where last 2 characters are the "largest number" (that is number 10 in the example). So other items should have last two characters subtracted from this "largest number" (with increments for each cycle). Cycle is never bigger then "largest number", but can be equal.
Order in the list is not relevant.
PS last two characters can be any number or even combination of one letter (a-z,A-Z) and one number. So it can be t1, or e9, or 88... that is why I think I need regex.
Any idea?
Given the variables
_name: "abc232323defg10"
cycle: "4"
Declare the variables prefix and index by splitting _name
prefix: "{{ _name|regex_replace('^(.+?)(\\d+)$', '\\1') }}"
index: "{{ _name|regex_replace('^(.+?)(\\d+)$', '\\2') }}"
give
prefix: abc232323defg
index: 10
Declare the list indexes
indexes: "{{ query('sequence', params) }}"
params: "start={{ index|int }} end={{ index|int - cycle|int + 1 }} stride=-1"
give
indexes: ['10', '9', '8', '7']
Finally, create product of the prefix and indexes, and join them
names: "{{ [prefix]|product(indexes)|map('join')|list }}"
gives the expected result
names:
- abc232323defg10
- abc232323defg9
- abc232323defg8
- abc232323defg7
Example of a complete playbook for testing
- hosts: localhost
vars:
_name: "abc232323defg10"
cycle: "4"
prefix: "{{ _name|regex_replace('^(.+?)(\\d+)$', '\\1') }}"
index: "{{ _name|regex_replace('^(.+?)(\\d+)$', '\\2') }}"
indexes: "{{ query('sequence', params) }}"
params: "start={{ index|int }} end={{ index|int - cycle|int + 1 }} stride=-1"
names: "{{ [prefix]|product(indexes)|map('join')|list }}"
tasks:
- debug:
msg: |
prefix: {{ prefix }}
index: {{ index }}
indexes: {{ indexes }}
names:
{{ names|to_nice_yaml|indent(2) }}
My variable info has below value. (Actual case has huge data).
I am trying to search for specific word XYZ_data_001 and get the size information, which is after the pattern physical disk,
XYZ_data_001 file system device, special, dsync off, directio on, physical disk, 16384.00 MB, Free: 0.00 MB 2 0 6 0 8388607
XYZ_data_002 file system device, special, dsync off, directio on, physical disk, 16384.00 MB, Free: 0.00 MB 2 0 13 0 8388607
here is what is tried
- name: Print size
ansible.builtin.debug:
msg: "{{ info | regex_search('XYZ_data_001(.+)') | split('physical disk,') | last }}"
this will give me below output
ok: [testhost] => {
"msg": " 16384.00 MB, Free: 0.00 MB 2 0 6 0 8388607 "
}
Thanks in advance
You can use
{{ info | regex_search('XYZ_data_001\\b.*physical disk,\\s*(\\d[\\d.]*)', '\\1') }}
See the regex demo.
Details:
XYZ_data_001 - a XYZ_data_001 string
\b - a word boundary
.* - any text (any zero or more chars other than line break chars as many as possible)
physical disk, - a literal string
\s* - zero or more whitespaces
(\d[\d.]*) - Group 1 (\1): a digit and then zero or more digits or dots.
There are two filters in the collection Community.General that will help you to create dictionaries from the info.
Split the lines, split and trim the items, and use the filter community.general.dict to create the list of dictionaries
info_dict1: "{{ info.splitlines()|
map('split', ',')|
map('map', 'trim')|
map('zip', ['dev', 'spec', 'dsync', 'dir', 'disk', 'size', 'free'])|
map('map', 'reverse')|
map('community.general.dict') }}"
gives
info_dict1:
- dev: XYZ_data_001 file system device
dir: directio on
disk: physical disk
dsync: dsync off
free: 'Free: 0.00 MB 2 0 6 0 8388607'
size: 16384.00 MB
spec: special
- dev: XYZ_data_002 file system device
dir: directio on
disk: physical disk
dsync: dsync off
free: 'Free: 0.00 MB 2 0 13 0 8388607'
size: 16384.00 MB
spec: special
Split the attribute dev and use the filter community.general.dict_kv to create the list of dictionaries with the attribute device
info_dev: "{{ info_dict1|
map(attribute='dev')|
map('split')|
map('first')|
map('community.general.dict_kv', 'device') }}"
gives
info_dev:
- device: XYZ_data_001
- device: XYZ_data_002
Combine the dictionaries
info_dict2: "{{ info_dict1|zip(info_dev)|map('combine') }}"
gives
info_dict2:
- dev: XYZ_data_001 file system device
device: XYZ_data_001
dir: directio on
disk: physical disk
dsync: dsync off
free: 'Free: 0.00 MB 2 0 6 0 8388607'
size: 16384.00 MB
spec: special
- dev: XYZ_data_002 file system device
device: XYZ_data_002
dir: directio on
disk: physical disk
dsync: dsync off
free: 'Free: 0.00 MB 2 0 13 0 8388607'
size: 16384.00 MB
spec: special
This way you can add other attributes if needed.
Q: "Search for specific word XYZ_data_001 and get the size."
A: Create a dictionary device_size
device_size: "{{ info_dict2|items2dict(key_name='device', value_name='size') }}"
gives
device_size:
XYZ_data_001: 16384.00 MB
XYZ_data_002: 16384.00 MB
Search the dictionary
- debug:
msg: "Size of XYZ_data_001 is {{ device_size.XYZ_data_001 }}"
gives
msg: Size of XYZ_data_001 is 16384.00 MB
Example of a complete playbook for testing
- hosts: localhost
vars:
info: |
XYZ_data_001 file system device, special, dsync off, directio on, physical disk, 16384.00 MB, Free: 0.00 MB 2 0 6 0 8388607
XYZ_data_002 file system device, special, dsync off, directio on, physical disk, 16384.00 MB, Free: 0.00 MB 2 0 13 0 8388607
info_dict1: "{{ info.splitlines()|
map('split', ',')|
map('map', 'trim')|
map('zip', ['dev', 'spec', 'dsync', 'dir', 'disk', 'size', 'free'])|
map('map', 'reverse')|
map('community.general.dict') }}"
info_dev: "{{ info_dict1|
map(attribute='dev')|
map('split')|
map('first')|
map('community.general.dict_kv', 'device') }}"
info_dict2: "{{ info_dict1|zip(info_dev)|map('combine') }}"
device_size: "{{ info_dict2|items2dict(key_name='device', value_name='size') }}"
tasks:
- debug:
var: info_dict1
- debug:
var: info_dev
- debug:
var: info_dict2
- debug:
var: device_size
- debug:
msg: "Size of XYZ_data_001 is {{ device_size.XYZ_data_001 }}"
I have been experimenting a lot with writing unit tests for alerts as per this: https://prometheus.io/docs/prometheus/latest/configuration/unit_testing_rules/#alerts-yml
I have some simple cases out, but now I am tackling rules that are less trivial. For example this:
abs(
avg_over_time(my_metrics{service_name="aService"}[1m])
-
avg_over_time(my_metrics{service_name="aService"}[3m])
)
/ stddev_over_time(my_metrics{service_name="aService"}[3m])
> 3
I have one file with the above rule and then this is in my test:
- interval: 1m
# Series data.
input_series:
- series: 'my_metrics{service_name="aService"}'
values: '0 0 0 0 1 0 0 0 0 '
alert_rule_test:
- eval_time: 3m
alertname: myalert
exp_alerts:
- exp_labels:
severity: warning
service_name: aService
exp_annotations:
summary: "some text"
description: "some other text"
I am not sure what my series should look like in order to test deviation from the mean. Is it even possible to test such rule?
Thank you
EDIT
I can have a succesful test if I set it > 0 as opposed to >3 I have tried to set a series of this sort:
'10+10x2 30+1000x1000'
but I cannot understand what would be the correct setup to have it triggered
This isn't a direct answer, rather a tip from someone who spent quite some time on these tests. Did you know that apart from testing alert expressions, you can unittest PromQL expressions as well? See how it can be useful:
evaluation_interval: 1m
tests:
- interval: 1m
input_series:
- series: test_metric
values: 1 1 1 10 1 1 1
promql_expr_test:
- expr: avg_over_time(test_metric[1m])
eval_time: 4m
exp_samples:
- value: #5.5
- expr: avg_over_time(test_metric[3m])
eval_time: 4m
exp_samples:
- value: #3.25
- expr: stddev_over_time(test_metric[3m])
eval_time: 4m
exp_samples:
- value: #3.897114317029974
I've split your alert expression into three separate, simple parts. If you run this unittest, you will see the commented-out values in the error message. From here it is not difficult to join pieces together and see why the alert is not happening. You can use that to build a working sequence of values.
I generate events on multiple computers that list service names that aren't running. I want to make a chart that displays the top offending service names.
I can use the following to get a table for the dashboard:
ComputerName="*.ourDomain.com" sourcetype="WinEventLog:Application" EventCode=7223 SourceName="internalSystem"
| eval Date_Time=strftime(_time, "%Y-%m-%d %H:%M")
| table host, Date_Time, Message, EventCode
Typical Message(s) will contain:
The following services were not running after 5603 seconds and a start command has been sent:
Service1
Service2
The following services were not running after 985 seconds and a start command has been sent:
Service2
Service3
Using regex I can make a named group of everything but the first line with (?<Services>((?<=\n)).*)
However, I don't think this is the right approach as I don't know how to do a valuation for the chart with this information.
So in essence, how do I grab and tally service names from messages in Splunk?
Edit 1:
Coming back to this after a few days.
I created a field extraction called "Services" with regex that grabs the contents of each message after the first line.
If I use | stats count BY Services it counts each message as a whole instead of the lines inside. The results look like this:
Service1 Service2 | Count: 1
Service2 Service3 | Count: 1
My intention is to have it treat each line as its own value so the results would look like:
Service1 | Count: 1
Service2 | Count: 2
Service3 | Count: 1
I tried | mvexpand Services but it didn't change the output so I assume I'm either using it improperly or it's not applicable here.
I think you can do it with the stats command.
| stats count by service
will give a number of appearances for each service. You then can choose the bar chart visualization to create a graph.
I ended up using split() and mvexpand to solve this problem.
This is what worked in the end:
My search
| eval events=split(Service, "
")
| mvexpand events
| eval events=replace(events, "[\n\r]", "")
| stats count BY events
I had to add the replace() method because any event with just one service listed was being treated differently from an event with multiple, after the split on an event with multiple services each service had a carriage return, hence the replace.
My end result dashboard chart:
For Chart dropping down that is clean:
index="yourIndex" "<searchCriteria>" | stats count(eval(searchmatch("
<searchCriteria>"))) as TotalCount
count(eval(searchmatch("search1"))) as Name1
count(eval(searchmatch("search2" ))) as Name2
count(eval(searchmatch("search3"))) as Name3
| transpose 5
| rename column as "Name", "row 1" as "Count"
Horizontal table example with percentages:
index=something "Barcode_Fail" OR "Barcode_Success" | stats
count(eval(searchmatch("Barcode_Success"))) as SuccessCount
count(eval(searchmatch("Barcode_Fail"))) as FailureCount
count(eval(searchmatch("Barcode_*"))) as Totals | eval
Failure_Rate=FailureCount/Totals |eval Success_Rate=SuccessCount/Totals
I'm trying to parse the output of a command that returned a line like this (there's more output, but this is the line that I'm after):
Remaining Time: 3 Minutes and 12 Seconds
And when there is no time left it returns a line like this:
Remaining Time: 0 Seconds
I'd like to extract the amount of minutes and seconds, so I can feed it to GNU date -d. First I tried this:
- name: determine how much time we have left
set_fact:
time_left: "{{ cmd_output.stdout | regex_search(time_left_regex, '\\1', '\\2') | join(' ') }}"
vars:
time_left_regex: 'Remaining Time: ([0-9]+ Minutes) and ([0-9]+ Seconds)'
But this does not handle the case when there is no time left. So I then tried something like this:
- name: determine how much time we have left
set_fact:
time_left: "{{ cmd_output.stdout | regex_findall(time_left_regex, '\\1') }}"
vars:
time_left_regex: 'Next Execution:.*([0-9]{1,2} (Minutes|Seconds))'
But this only returns something like:
ok: [localhost] => {
"msg": "time left: [[u'2 Seconds', u'Seconds']]" }
I think I'm on the right track but I need a better regex, so maybe somebody can help me out here?
Thank you so much in advance.
You can make the minutes part optional. The minutes will be in group 1 and the seconds will be in group 2.
Remaining Time: (?:([0-9]+ Minutes) and )?([0-9]+ Seconds)
Regex demo
It's possible to split the string (line) and combine a dictionary. For example
- set_fact:
time_left: "{{ time_left|default({})|
combine({myline[item]: myline[item+1]}) }}"
loop: "{{ range(0, myline|length + 1, 3)|list }}"
vars:
myline: "{{ cmd_output.stdout.split(':').1.split()|reverse|list }}"
- debug:
var: time_left
for various command outputs
cmd_output.stdout: 'Remaining Time: 3 Minutes and 12 Seconds'
cmd_output.stdout: 'Remaining Time: 0 Seconds'
cmd_output.stdout: 'Remaining Time: 2 Days and 7 Hours and 3 Minutes and 12 Seconds'
gives (respectively)
"time_left": {
"Minutes": "3",
"Seconds": "12"
}
"time_left": {
"Seconds": "0"
}
"time_left": {
"Days": "2",
"Hours": "7",
"Minutes": "3",
"Seconds": "12"
}