WIP: Aruba switches #103

Draft
jeltz wants to merge 10 commits from aruba into master
12 changed files with 498 additions and 0 deletions

View file

@ -0,0 +1,83 @@
#!/usr/bin/env python3
import base64
import time
import functools
import urllib.parse
import requests
from ansible.errors import AnsibleActionFail
from ansible.plugins.action import ActionBase
class Aruba:
def __init__(self, base_url):
self._session = requests.session()
self._base_url = base_url
def _url(self, url):
return urllib.parse.urljoin(self._base_url, url)
def login(self, username, password):
response = self._session.post(
self._url("/rest/v4/login-sessions"),
json={"userName": username, "password": password},
)
if response.status_code != requests.codes.created:
raise AnsibleActionFail("Login failed")
def logout(self):
response = self._session.delete(self._url("/rest/v4/login-sessions"))
if response.status_code != requests.codes.no_content:
raise AnsibleActionFail("Logout failed")
def restore(self, config):
response = self._session.post(
self._url(
"/rest/v4/system/config/cfg_restore/payload"
),
json={
"config_base64_encoded": base64.b64encode(config.encode()),
"is_forced_reboot_enabled": True,
},
)
if response.status_code != requests.codes.accepted:
raise AnsibleActionFail("Restore failed")
response = self._session.get(
self._url(
"/rest/v4/system/config/cfg_restore/payload/status"
)
)
print(response.text)
class ActionModule(ActionBase):
_VALID_ARGS = frozenset(("username", "password", "config", "url"))
def _require_arg(self, name):
try:
return self._task.args[name]
except KeyError:
raise AnsibleActionFail("Missing argument: {}".format(name))
def run(self, tmp=None, task_vars=None):
task_vars = task_vars or {}
result = super().run(tmp, task_vars)
base_url = self._task.args.get("url")
username = self._require_arg("username")
password = self._require_arg("password")
config = self._require_arg("config")
aruba = Aruba(base_url)
aruba.login(username, password)
try:
aruba.restore(config)
except:
raise
else:
aruba.logout()
return result

View file

@ -4,6 +4,7 @@ roles_path = ./roles
retry_files_enabled = False
inventory = ./hosts
filter_plugins = ./filter_plugins
action_plugins = ./action_plugins
ansible_managed = Ansible managed, modified on %Y-%m-%d %H:%M:%S
nocows = 1
forks = 15

17
filter_plugins/aruba.py Normal file
View file

@ -0,0 +1,17 @@
class FilterModule:
def filters(self):
return {
"aruba_ints": aruba_ints,
}
def aruba_ints(seq, sep=",", hyphen="-"):
ranges = []
for value in sorted(seq):
if not ranges or ranges[-1][1] + 1 != value:
ranges.append((value, value))
else:
ranges[-1] = (ranges[-1][0], value)
return sep.join(
(f"{a}" if a == b else f"{a}{hyphen}{b}" for a, b in ranges)
)

16
filter_plugins/enquote.py Normal file
View file

@ -0,0 +1,16 @@
class FilterModule:
def filters(self):
return {
"enquote": enquote,
}
def enquote(string, delimiter='"', escape="\\"):
translation = str.maketrans(
{
delimiter: f"{escape}{delimiter}",
escape: f"{escape}{escape}",
}
)
escaped = string.translate(translation)
return f"{delimiter}{escaped}{delimiter}"

View file

@ -0,0 +1,9 @@
class FilterModule:
def filters(self):
return {
"contains": contains,
}
def contains(a, b):
return b in a

View file

@ -8,6 +8,7 @@ class FilterModule:
def filters(self):
return {
"remove_domain_suffix": remove_domain_suffix,
"hostname": hostname,
"ipaddr_sort": ipaddr_sort,
}
@ -17,6 +18,11 @@ def remove_domain_suffix(name):
return parent.to_text()
def hostname(fqdn):
name = dns.name.from_text(fqdn)
return name.relativize(name.parent()).to_text()
def ipaddr_sort(addrs, types, unknown_after=True):
check_types = {
"global": attrgetter("is_global"),

View file

@ -0,0 +1,11 @@
class FilterModule:
def filters(self):
return {
"choices": choices,
}
def choices(value, choices):
if value not in choices:
raise ValueError(f"{value} not in {choices}")
return value

1
hosts
View file

@ -96,6 +96,7 @@ radius-fleming.adm.auro.re
dns-1.int.infra.auro.re
isp-1.rtr.infra.auro.re
isp-2.rtr.infra.auro.re
test-1.switch.infra.auro.re
dhcp-1.isp.auro.re
dhcp-2.isp.auro.re
radius-fleming-backup.adm.auro.re

84
playbooks/aruba.yml Executable file
View file

@ -0,0 +1,84 @@
#!/usr/bin/env ansible-playbook
---
- hosts:
- test-1.switch.infra.auro.re
gather_facts: false
vars:
aruba__api_url: "http://{{ inventory_hostname }}"
aruba__api_username: "manager"
aruba__api_password: "manager"
aruba__model: J9773A
aruba__release: YA.16.11.002
aruba__hostname: "{{ inventory_hostname | hostname }}"
aruba__rest_enabled: true
aruba__ssh_enabled: true
aruba__ntp_servers:
- 10.128.0.1
- 2a09:6840:128:0:1::1
aruba__timezone: Europe/Paris
aruba__dns_servers:
- 10.128.0.1
- 2a09:6840:128:0:1::1
aruba__dns_domain_names:
- switch.infra.auro.re
- infra.auro.re
- toto.auro.re
aruba__manager_password: "manager"
aruba__operator_password: "operator"
aruba__default_gateways:
- 10.131.0.1
- 2a09:6840:131:0:1::1
aruba__vlans:
1:
name: Default
131:
name: Switchs
addresses:
- 10.131.1.1/16
- 2a09:6840:131:1:1::1/56
1000:
name: "Client 0"
1001:
name: "Client 1"
1002:
name: "Client 2"
1003:
name: "Client 3"
1004:
name: "Client 4"
aruba__interfaces:
1:
name: Uplink
untagged: 131
tagged:
- 1000
- 1001
- 1002
- 1003
- 1004
loop_protect: true
lldp: true
2:
name: "Client 0"
untagged: 1000
loop_protect: true
3:
name: "Client 1"
untagged: 1001
loop_protect: true
4:
name: "Client 2"
untagged: 1002
speed_duplex: 100-full
loop_protect: true
5:
name: "Client 3"
untagged: 1003
loop_protect: true
6:
name: "Client 4"
untagged: 1004
loop_protect: true
roles:
- aruba
...

View file

@ -0,0 +1,11 @@
---
aruba__ntp_servers: []
aruba__vlans: {}
aruba__interfaces: {}
aruba__default_gateways: []
aruba__ssh_enabled: False
aruba__rest_enabled: True
aruba__dns_domain_names: []
aruba__loop_protect_disable_timer: 30
aruba__loop_protect_tx_interval: 3
...

View file

@ -0,0 +1,97 @@
---
- name: Generate configuration
set_fact:
aruba__config: "{{ lookup('template', './config.j2') }}"
when: "aruba__config is not defined"
- name: Restore configuration
aruba_cfg_restore:
url: "http://{{ inventory_hostname }}/"
username: "{{ aruba__api_username }}"
password: "{{ aruba__api_password }}"
config: "{{ aruba__config }}"
#- name: Login to switch
# delegate_to: localhost
# uri:
# url: "{{ aruba__api_base_url }}/rest/v4/login-sessions"
# method: POST
# status_code: 201
# body_format: json
# body:
# userName: "{{ aruba__api_username }}"
# password: "{{ aruba__api_password }}"
# register: login
#- name: Get diff
# delegate_to: localhost
# uri:
# url: "{{ aruba__api_base_url }}/rest/v4/system/config/cfg_restore/payload/latest_diff"
# method: POST
# body_format: json
# status_code: 202
# body:
# config_base64_encoded: "{{ aruba__config | b64encode }}"
# headers:
# Cookie: "{{ login.json.cookie }}"
# register: diff
#- name: Diff
# debug:
# msg: "{{ diff }}"
#- name: Get diff
# delegate_to: localhost
# uri:
# url: "{{ aruba__api_base_url }}/rest/v4/system/config/cfg_restore/payload/latest_diff/status"
# method: GET
# status_code: 200
# headers:
# Cookie: "{{ login.json.cookie }}"
# register: diff
#- name: Diff
# debug:
# msg: "{{ diff }}"
#- name: Restore configuration
# delegate_to: localhost
# uri:
# url: "{{ aruba__api_base_url }}/rest/v4/system/config/cfg_restore/payload"
# method: POST
# body_format: json
# status_code: 202
# body:
# config_base64_encoded: "{{ aruba__config | b64encode }}"
# is_forced_reboot_enabled: true
# headers:
# Cookie: "{{ login.json.cookie }}"
# register: status
#
#- name: XX
# debug:
# msg: "{{ status }}"
#
#- name: Get diff
# delegate_to: localhost
# uri:
# url: "{{ aruba__api_base_url }}/rest/v4/system/config/cfg_restore/payload/status"
# method: GET
# status_code: 200
# headers:
# Cookie: "{{ login.json.cookie }}"
# register: diff
#- name: Diff
# debug:
# msg: "{{ diff }}"
#- name: Logout
# delegate_to: localhost
# uri:
# url: "{{ aruba__api_base_url }}/rest/v4/login-sessions"
# method: DELETE
# status_code: 204
# headers:
# Cookie: "{{ login.json.cookie }}"
...

View file

@ -0,0 +1,162 @@
; {{ aruba__model }} Configuration Editor; Created on release #{{ aruba__release }}
hostname {{ aruba__hostname | hostname | truncate(32) | enquote }}
include-credentials
{% if aruba__ntp_servers %}
timesync ntp
ntp unicast
{% for addr in aruba__ntp_servers %}
ntp server {{ addr | ipaddr }} iburst
{% endfor %}
{% if aruba__timezone == "Europe/Paris" %}
time daylight-time-rule western-europe
time timezone 60
{% endif %}
{% endif %}
{% for addr in aruba__dns_servers[:2] %}
ip dns server-address priority {{ loop.index }} {{ addr | ipaddr }}
{% endfor %}
{% for domain in aruba__dns_domain_names[:5] %}
ip dns domain-name {{ domain | enquote }}
{% endfor %}
activate provision disable
activate software-update disable
{% if False %}
snmpv3 enable
snmpv3 only
snmpv3 user "re2o"
snmpv3 group ManagerPriv user "re2o" sec-model ver3
snmp-server community "public" Operator
{% endif %}
no cdp run
lldp run
{%
set lldp_disabled =
aruba__interfaces.keys()
| difference(aruba__interfaces
| dict2items
| selectattr("value.lldp", "defined")
| selectattr("value.lldp", "==", True)
| map(attribute="key"))
| list
%}
{% if lldp_disabled %}
lldp admin-status {{ lldp_disabled | aruba_ints }} disable
{% endif %}
password manager sha1 {{ aruba__manager_password | hash("sha1") }}
{% if aruba__operator_password is defined %}
password operator sha1 {{ aruba__operator_password | hash("sha1") }}
{% endif %}
#}
{% if aruba__ssh_enabled %}
ip ssh
{# ip ssh cipher aes256ctr #}
{# ip ssh kex ecdh-sha2-nistp521 #}
{# ip ssh mac hmac-sha2-256 #}
ip ssh filetransfer
{% else %}
no ip ssh
{% endif %}
no telnet-server
no tftp
{% if aruba__rest_enabled %}
{# FIXME: ssl #}
web-management plaintext
rest-interface
{% endif %}
{%
set loop_protect =
aruba__interfaces
| dict2items
| selectattr("value.loop_protect", "defined")
| selectattr("value.loop_protect")
| map(attribute="key")
| list
%}
{% if loop_protect %}
loop-protect disable-timer {{ aruba__loop_protect_disable_timer | int }}
loop-protect transmit-interval {{ aruba__loop_protect_tx_interval | int }}
loop-protect {{ loop_protect | aruba_ints }}
{% endif %}
{% if aruba__default_gateways | ipv4 %}
ip default-gateway {{ aruba__default_gateways | ipv4 | first }}
{% endif %}
{% if aruba__default_gateways | ipv6 %}
{# ipv6 default-gateway {{ aruba__default_gateways | ipv6 | first }} #}
{% endif %}
{% for id, vlan in aruba__vlans.items() %}
vlan {{ id | int }}
{% if vlan.name is defined %}
name {{ vlan.name | truncate(32) | enquote }}
{% endif %}
{%
set untagged =
aruba__interfaces
| dict2items
| selectattr("value.untagged", "defined")
| selectattr("value.untagged", "==", id)
| map(attribute="key")
| list
%}
{% if untagged %}
untagged {{ untagged | aruba_ints }}
{% endif %}
{%
set tagged =
aruba__interfaces
| dict2items
| selectattr("value.tagged", "defined")
| selectattr("value.tagged", "contains", id)
| map(attribute="key")
| list
%}
{% if tagged %}
tagged {{ tagged | aruba_ints }}
{% endif %}
{% if vlan.addresses | default([]) %}
{% for addr in vlan.addresses | ipv4 %}
ip address {{ addr | ipaddr("host") }}
{% endfor %}
{% for addr in vlan.addresses | ipv6 %}
ipv6 address {{ addr | ipaddr("host") }}
{% endfor %}
{% else %}
no ip address
{% endif %}
exit
{% endfor %}
{% for id, iface in aruba__interfaces.items() %}
interface {{ id | int }}
{% if iface.name is defined %}
name {{ iface.name | truncate(32) | enquote }}
{% endif %}
{% if iface.enabled | default(True) %}
enable
{% else %}
no enable
{% endif %}
{# TODO: split and check speed/duplex #}
{% if iface.speed_duplex is defined %}
speed-duplex {{ iface.speed_duplex }}
{% endif %}
no flow-control
exit
{% endfor %}