diff --git a/dashboards/grafana/identity/apis.yaml b/dashboards/grafana/identity/apis.yaml index 2972389..fb5dfcd 100644 --- a/dashboards/grafana/identity/apis.yaml +++ b/dashboards/grafana/identity/apis.yaml @@ -8,6 +8,6 @@ options: tooltip: {mode: "single", sort: "none"} targets: - refId: "A" - target: "sortByName(groupByNodes(removeEmptySeries(stats.timers.openstack.api.$environment.$zone.identity.*.*.*.mean, 0.01), 'sum', 8, 7, 9), true)" + target: "sortByName(groupByNodes(removeEmptySeries(stats.timers.openstack.api.$environment.$zone.identity.*.*.mean, 0.01), 'sum', 8, 7, 9), true)" title: "API calls duration" type: "timeseries" diff --git a/dashboards/grafana/identity/longest_api.yaml b/dashboards/grafana/identity/longest_api.yaml index e2b6e7c..f7550d4 100644 --- a/dashboards/grafana/identity/longest_api.yaml +++ b/dashboards/grafana/identity/longest_api.yaml @@ -9,6 +9,6 @@ options: tooltip: {"mode": "single", "sort": "none"} targets: - refId: "A" - target: "maximumAbove(groupByNodes(removeEmptySeries(stats.timers.openstack.api.$environment.$zone.identity.*.*.*.upper), 'sum', 8, 7, 9), 1000)" + target: "maximumAbove(groupByNodes(removeEmptySeries(stats.timers.openstack.api.$environment.$zone.identity.*.*.upper), 'sum', 8, 7, 9), 1000)" title: "Highest API calls duration" type: "timeseries" diff --git a/inventory/production/group_vars/all.yml b/inventory/production/group_vars/all.yml index 00456a9..d6f7a0c 100644 --- a/inventory/production/group_vars/all.yml +++ b/inventory/production/group_vars/all.yml @@ -1,6 +1,7 @@ --- # flavor -#test_flavor: s3.medium.2 +test_flavor: "SCS-2V:2:20" +test_image: "Ubuntu 22.04" # cloud_name: test_keypair_name: api_monitoring_test diff --git a/playbooks/files/get_server_metadata.py b/playbooks/files/get_server_metadata.py new file mode 100755 index 0000000..858bad7 --- /dev/null +++ b/playbooks/files/get_server_metadata.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 + +import openstack +import sys + +conn = openstack.connect() + +server = conn.compute.find_server(sys.argv[1]) + +if server: + metadata = server.get_metadata(conn.compute) diff --git a/playbooks/files/list_keypairs.py b/playbooks/files/list_keypairs.py new file mode 100755 index 0000000..44ff3fa --- /dev/null +++ b/playbooks/files/list_keypairs.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 + +import openstack + + +conn = openstack.connect() + +list(conn.compute.keypairs()) diff --git a/playbooks/files/list_routers.py b/playbooks/files/list_routers.py new file mode 100755 index 0000000..fb8ee01 --- /dev/null +++ b/playbooks/files/list_routers.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 + +import openstack + +conn = openstack.connect() + +list(conn.network.routers()) diff --git a/playbooks/files/list_security_groups.py b/playbooks/files/list_security_groups.py new file mode 100755 index 0000000..a8cc142 --- /dev/null +++ b/playbooks/files/list_security_groups.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 + +import openstack + +conn = openstack.connect() + +list(conn.network.security_groups()) +list(conn.network.security_group_rules()) diff --git a/playbooks/scenario2_simple_ecs.yaml b/playbooks/scenario2_simple_ecs.yaml new file mode 100644 index 0000000..65b0a23 --- /dev/null +++ b/playbooks/scenario2_simple_ecs.yaml @@ -0,0 +1,233 @@ +--- + +- name: Scenario 2 - basic compute functionality + hosts: localhost + vars: + prefix: scenario2- + tasks: + - set_fact: + prefix: "{{ (prefix + ( lookup('env', 'TASK_EXECUTOR_JOB_ID') | default(99999999 | random | to_uuid | hash('md5'), true) ) ) }}" + + - set_fact: + test_server_fqdn: "{{ (prefix + '.host') }}" + test_security_group_name: "{{ (prefix + '-sg') }}" + test_keypair_name: "{{ (prefix + '-kp') }}" + test_network_name: "{{ ( prefix + '-net') }}" +# test_image: Standard_Fedora_35_latest + + - name: Query specific flavor + openstack.cloud.compute_flavor_info: + name: "{{ test_flavor }}" + register: flavor_result + + # using s2.medium.2 as fallback in case the flavor from inv is not available in the env + - set_fact: + test_flavor: s2.medium.2 + when: flavor_result.openstack_flavors | length==0 + + - debug: + msg: "Using prefix {{ prefix }}" + + - name: List Keypairs + script: list_keypairs.py + args: + executable: python3 + tags: 'service=compute' + + - name: Ensure .ssh exists + file: + path: "~/.ssh" + state: directory + + - block: + + - name: Create Keypair + include_role: + name: keypair + vars: + keypair_name: "{{ test_keypair_name }}" + state: "present" + + - name: List Networks + openstack.cloud.networks_info: + + - name: List Flavors + openstack.cloud.compute_flavor_info: + + - name: List Subnets + openstack.cloud.subnets_info: + + - name: List Routers + script: list_routers.py + args: + executable: python3 + tags: 'service=network' + + - name: Create VPC (Router + Net + Subnet) + include_role: + name: vpc + vars: + network_name: "{{ test_network_name }}" + state: "present" + + - name: List SecurityGroups with Rules + script: list_security_groups.py + args: + executable: python3 + tags: 'service=compute' + + - name: Create SecurityGroup + openstack.cloud.security_group: + name: "{{ test_security_group_name }}" + description: Test Security group created by APImon + + - name: Create SecurityGroupRule + openstack.cloud.security_group_rule: + security_group: "{{ test_security_group_name }}" + protocol: tcp + port_range_min: 22 + port_range_max: 22 + remote_ip_prefix: 0.0.0.0/0 + + - name: List Images + openstack.cloud.image_info: + + - name: List Servers + openstack.cloud.server_info: + + ############ + - name: Create Server in default AZ + openstack.cloud.server: + auto_ip: false + name: "{{ test_server_fqdn }}" + image: "{{ test_image }}" + flavor: "{{ test_flavor }}" + key_name: "{{ test_keypair_name }}" + network: "{{ test_network_name }}" + security_groups: "{{ test_security_group_name }}" + tags: + - "metric=create_server" + - "az=default" + register: server + + - name: get server id + set_fact: + server_id: "{{ server.id }}" + + - name: Attach FIP + openstack.cloud.floating_ip: + server: "{{ server_id }}" + tags: + - "metric=create_server" + - "az=default" + + - name: get server info + openstack.cloud.server_info: + server: "{{ server_id }}" + register: server + tags: + - "metric=create_server" + - "az=default" + + - set_fact: + server_ip: "{{ server['openstack_servers'][0]['public_v4'] }}" + tags: + - "metric=create_server" + - "az=default" + + - name: find servers by name + openstack.cloud.server_info: + server: "{{ test_server_fqdn }}" + register: servers + tags: + - "metric=create_server" + - "az=default" + + - name: Debug server info + debug: + var: servers + + # Wait for the server to really start and become accessible + - name: Wait for SSH port to become active + wait_for: + port: 22 + host: "{{ server_ip }}" + timeout: 600 + tags: ["az=default", "service=compute", "metric=create_server"] + + - name: Try connecting + retries: 10 + delay: 1 + command: "ssh -o 'UserKnownHostsFile=/dev/null' -o 'StrictHostKeyChecking=no' {{ test_user }}@{{ server_ip }} -i ~/.ssh/{{ test_keypair_name }}.pem" + tags: ["az=default", "service=compute", "metric=create_server"] + + ############ + - name: Get Server metadata + script: "get_server_metadata.py {{ server_id }}" + args: + executable: python3 + tags: 'service=compute' + + - name: Modify metadata + openstack.cloud.server_metadata: + server: "{{ server_id }}" + state: present + meta: + meta_k1: v1 + meta_k2: v2 + + - name: Delete metadata + openstack.cloud.server_metadata: + server: "{{ server_id }}" + state: absent + meta: + meta_k1: + meta_k2: + + #- name: Set Tags on the Server + # opentelekomcloud.cloud.tag: + # server: "{{ server_id }}" + # state: present + # tags: + # - tag1 + # - tag2 + # mode: set + + - name: Delete SecurityGroupRule + openstack.cloud.security_group_rule: + state: "absent" + security_group: "{{ test_security_group_name }}" + protocol: tcp + port_range_min: 2208 + port_range_max: 2209 + remote_ip_prefix: 0.0.0.0/0 + + always: + - block: + - name: Delete server + openstack.cloud.server: + state: absent + name: "{{ server_id | default(test_server_fqdn) }}" + delete_fip: True + tags: ["metric=delete_server"] + + - name: Delete SecurityGroup + openstack.cloud.security_group: + state: "absent" + name: "{{ test_security_group_name }}" + + - name: Delete VPC + include_role: + name: vpc + vars: + state: absent + network_name: "{{ test_network_name }}" + + - name: Delete Keypair + include_role: + name: keypair + vars: + keypair_name: "{{ test_keypair_name }}" + force_delete_key: true + state: "absent" + ignore_errors: true diff --git a/roles/keypair/defaults/main.yaml b/roles/keypair/defaults/main.yaml new file mode 100644 index 0000000..fb582cc --- /dev/null +++ b/roles/keypair/defaults/main.yaml @@ -0,0 +1,10 @@ +--- +prefix: test- # This should be overridden in inventory + +keypair_name: "{{ (prefix + 'KeyPair') }}" +keypair_private_key_dest: "{{ ('~/.ssh/' + keypair_name + '.pem') }}" + +# set this to force key deletion +force_delete_key: false + +state: present diff --git a/roles/keypair/tasks/main.yaml b/roles/keypair/tasks/main.yaml new file mode 100644 index 0000000..16067e2 --- /dev/null +++ b/roles/keypair/tasks/main.yaml @@ -0,0 +1,42 @@ +--- +# Management of the keypair +- block: + - name: Create KeyPair + openstack.cloud.keypair: + state: present + name: "{{ keypair_name }}" + register: keypair_output + + - name: Ensure directory is present + ansible.builtin.file: + path: "{{ keypair_private_key_dest | dirname }}" + state: "directory" + when: + - keypair_output.changed + + - name: Save private key + copy: + dest: "{{ keypair_private_key_dest }}" + content: "{{ keypair_output['key']['private_key'] }}" + mode: 0600 + when: + - keypair_output is defined and keypair_output['key']['private_key'] is defined + - keypair_output.changed + + - set_fact: + ssh_keys: "{{ ssh_keys|default({}) | combine({keypair_name: keypair_output['key']['public_key']}) }}" + when: state != 'absent' + +- block: + - name: Delete KeyPair + openstack.cloud.keypair: + state: absent + name: "{{ keypair_name }}" + + - name: delete private key + file: + path: "{{ keypair_private_key_dest }}" + state: absent + when: force_delete_key|bool + + when: state == 'absent' diff --git a/roles/server_create_delete/defaults/main.yaml b/roles/server_create_delete/defaults/main.yaml new file mode 100644 index 0000000..5a18a6d --- /dev/null +++ b/roles/server_create_delete/defaults/main.yaml @@ -0,0 +1,5 @@ +--- +ssh_user_name: "linux" +ssh_key_pub: "" +coreos: False +metric_suffix: "" diff --git a/roles/server_create_delete/tasks/main.yml b/roles/server_create_delete/tasks/main.yml new file mode 100644 index 0000000..5105729 --- /dev/null +++ b/roles/server_create_delete/tasks/main.yml @@ -0,0 +1,88 @@ +--- +# tasks file for roles/server_create_delete + +- block: + - name: Create Server in {{ availability_zone }} + openstack.cloud.server: + auto_ip: false + availability_zone: "{{ availability_zone | default(omit) }}" + name: "{{ server_fqdn }}" + image: "{{ server_image }}" + flavor: "{{ server_flavor }}" + key_name: "{{ server_keypair_name }}" + network: "{{ server_net }}" + security_groups: "{{ security_group }}" + # WARN: template and use of vars may cause replacement of \" to \' + userdata: "{{ lookup('template', 'coreos-ignition.json.j2') | to_json | string }}" + tags: + - "metric=create_server{{ metric_suffix }}" + register: server + + - name: get server id + set_fact: + server_id: "{{ server.id }}" + + - name: Attach FIP + openstack.cloud.floating_ip: + server: "{{ server_id }}" + tags: + - "metric=create_server{{ metric_suffix }}" + - "az={{ availability_zone }}" + + - name: get server info + openstack.cloud.server_info: + server: "{{ server_id }}" + register: server + tags: + - "metric=create_server{{ metric_suffix }}" + - "az={{ availability_zone }}" + + - set_fact: + server_ip: "{{ server['openstack_servers'][0]['public_v4'] }}" + tags: + - "metric=create_server{{ metric_suffix }}" + - "az={{ availability_zone }}" + + # Wait for the server to really start and become accessible + - name: Wait for SSH port to become active + ansible.builtin.wait_for: + port: 22 + host: "{{ server_ip }}" + timeout: 300 + tags: ["az={{ availability_zone }}", "service=compute", "metric=create_server{{ metric_suffix }}"] + + - name: Wait another 5 sec for coreos + pause: + seconds: 5 + tags: ["az={{ availability_zone }}", "service=compute", "metric=create_server{{ metric_suffix }}"] + when: + - coreos is defined + - coreos | bool + + - name: Try connecting + retries: 10 + delay: 1 + command: "ssh -o 'UserKnownHostsFile=/dev/null' -o 'StrictHostKeyChecking=no' {{ ssh_user_name }}@{{ server_ip }} -i ~/.ssh/{{ server_keypair_name }}.pem" + tags: ["az={{ availability_zone }}", "service=compute", "metric=create_server{{ metric_suffix }}"] + + always: + - name: Debug server info + debug: + var: server + + - name: Delete server from {{ availability_zone }} + openstack.cloud.server: + state: absent + name: "{{ server_id }}" + delete_fip: True + when: server_id is defined + tags: ["metric=delete_server"] + + - name: Try to get deleted server info + openstack.cloud.server_info: + server: "{{ server_id }}" + register: server + + - assert: + that: + - server.openstack_servers | length == 0 diff --git a/roles/server_create_delete/templates/coreos-ignition.json.j2 b/roles/server_create_delete/templates/coreos-ignition.json.j2 new file mode 100644 index 0000000..ceefffb --- /dev/null +++ b/roles/server_create_delete/templates/coreos-ignition.json.j2 @@ -0,0 +1,16 @@ +{ + "ignition": { + "version": "3.0.0" + }, + "passwd": { + "users": [ + { + "groups": ["sudo"], + "name": "{{ ssh_user_name }}", + "sshAuthorizedKeys": [ + "{{ ssh_key_pub | replace('\n','') }}" + ] + } + ] + } +} diff --git a/roles/server_create_delete/templates/default.j2 b/roles/server_create_delete/templates/default.j2 new file mode 100644 index 0000000..e69de29 diff --git a/roles/upload_image/defaults/main.yaml b/roles/upload_image/defaults/main.yaml new file mode 100644 index 0000000..325454a --- /dev/null +++ b/roles/upload_image/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +upload_image_images_location: "/tmp/ansible/images" diff --git a/roles/upload_image/tasks/main.yaml b/roles/upload_image/tasks/main.yaml new file mode 100644 index 0000000..42a1f27 --- /dev/null +++ b/roles/upload_image/tasks/main.yaml @@ -0,0 +1,50 @@ +--- +# Upload image into the cloud +# +- name: Create directory for images + ansible.builtin.file: + name: "{{ upload_image_images_location }}" + state: "directory" + recurse: true + +- set_fact: + image_url_basename: "{{ image_url | basename }}" + +- set_fact: + image_file: "{{ image_url_basename | regex_replace('^(.*)\\.(xz|gz)$', '\\1') }}" + +- name: Find whether we have file locally + ansible.builtin.stat: + path: "{{ upload_image_images_location }}/{{ image_file }}" + register: image_present + +- name: Download image + ansible.builtin.get_url: + url: "{{ image_url }}" + dest: "{{ upload_image_images_location }}" + validate_certs: false + register: download + when: + - not image_present.stat.exists + +- name: Unpack file + ansible.builtin.command: "unxz {{ download.dest }}" + args: + chdir: "{{ upload_image_images_location }}" + creates: "{{ image_file }}" + register: unxz + when: + - not image_present.stat.exists + - download.dest is defined + +- name: Upload image + openstack.cloud.image: + name: "{{ image_name }}" + container_format: "bare" + disk_format: "qcow2" + state: "present" + min_disk: 1 + timeout: 1200 + filename: "{{ upload_image_images_location }}/{{ image_file }}" + tags: + - "metric=image_upload" diff --git a/roles/volume_create_delete/tasks/main.yml b/roles/volume_create_delete/tasks/main.yml new file mode 100644 index 0000000..4d13d5b --- /dev/null +++ b/roles/volume_create_delete/tasks/main.yml @@ -0,0 +1,22 @@ +--- +# tasks file for roles/volume_create_delete + +- block: + - name: "Create Volume in {{ availability_zone }}" + openstack.cloud.volume: + state: present + availability_zone: "{{ availability_zone | default(omit) }}" + size: 10 + display_name: "{{ volume_name }}" + tags: + - "metric=create_volume" + register: volume + + always: + - name: "Delete Volume" + openstack.cloud.volume: + state: absent + display_name: "{{ volume_name }}" + tags: + - "metric=delete_volume" + - "metric=delete_volume_{{ availability_zone }}" diff --git a/roles/vpc/defaults/main.yml b/roles/vpc/defaults/main.yml new file mode 100644 index 0000000..2ef7ea1 --- /dev/null +++ b/roles/vpc/defaults/main.yml @@ -0,0 +1,27 @@ +--- +# defaults file for ansible-role-otc-vpc + +# Elements to be created by default. Order is important. +task_manager: + - net + - subnet + - router + +state: present + +external_network_name: admin_external_net +prefix: test- # This should be overridden in inventory + +router_name_suffix: otc-vpc-router +network_name_suffix: otc-net +subnet_name_suffix: default-subnet +network_name: "{{ (prefix + network_name_suffix) }}" +router_name: "{{ (prefix + router_name_suffix) }}" + +subnet_name: "{{ (prefix + subnet_name_suffix) }}" +subnet_cidr: "192.168.110.0/24" +subnet_dns_servers: "{{ ['100.125.4.25', '8.8.8.8'] }}" + +# By default SNAT will be disabled on the router. Pass `enable_snat: false` +# to disable it +#enable_snat: None diff --git a/roles/vpc/tasks/main.yml b/roles/vpc/tasks/main.yml new file mode 100644 index 0000000..9345739 --- /dev/null +++ b/roles/vpc/tasks/main.yml @@ -0,0 +1,25 @@ +--- +# tasks file for otc-vpc-role +# Create +- block: + - include: "{{ net_task }}.yaml" + loop_control: + loop_var: net_task + with_items: "{{ task_manager }}" + + - name: Assert that elements were registered + assert: + that: + - net_network is defined + - net_subnet is defined + - net_router is defined + + when: state == 'present' + +# Remove +- block: + - include: "{{ net_task }}.yaml" + loop_control: + loop_var: net_task + with_items: "{{ task_manager | reverse | list }}" + when: state == 'absent' diff --git a/roles/vpc/tasks/net.yaml b/roles/vpc/tasks/net.yaml new file mode 100644 index 0000000..7157bc3 --- /dev/null +++ b/roles/vpc/tasks/net.yaml @@ -0,0 +1,6 @@ +--- +- name: Create Network + openstack.cloud.network: + name: "{{ network_name }}" + state: "{{ state }}" + register: net_network diff --git a/roles/vpc/tasks/router.yaml b/roles/vpc/tasks/router.yaml new file mode 100644 index 0000000..e594b67 --- /dev/null +++ b/roles/vpc/tasks/router.yaml @@ -0,0 +1,28 @@ +--- +# upstream expects enable_snat to be defaulted to true, but in OTC it is not that +# therefore it is required to enable snat manually later or use heat +- name: Create Router + openstack.cloud.router: + name: "{{ router_name }}" + state: "{{ state }}" + network: "{{ external_network_name }}" + enable_snat: "{{ enable_snat | default(omit) }}" + interfaces: + - net: "{{ network_name }}" + subnet: "{{ subnet_name }}" + register: net_router + when: state != 'absent' + +- name: Assert that SNAT is as requested + assert: + that: + - net_router.router.external_gateway_info.enable_snat == enable_snat + when: + - state != 'absent' + - enable_snat is defined + +- name: Delete Router + openstack.cloud.router: + name: "{{ router_name }}" + state: "{{ state }}" + when: state == 'absent' diff --git a/roles/vpc/tasks/subnet.yaml b/roles/vpc/tasks/subnet.yaml new file mode 100644 index 0000000..8d17aa4 --- /dev/null +++ b/roles/vpc/tasks/subnet.yaml @@ -0,0 +1,16 @@ +--- +- name: Create default subnet + openstack.cloud.subnet: + name: "{{ subnet_name }}" + state: "{{ state }}" + network_name: "{{ network_name }}" + cidr: "{{ subnet_cidr }}" + dns_nameservers: "{{ subnet_dns_servers | default(omit) }}" + register: net_subnet + when: state != 'absent' + +- name: Delete default subnet + openstack.cloud.subnet: + name: "{{ subnet_name }}" + state: "{{ state }}" + when: state == 'absent'