Collection of CheckMK checks (see https://checkmk.com/). All checks and plugins are provided as is. Absolutely no warranty. Send any comments to thl-cmk[at]outlook[dot]com

Skip to content
Snippets Groups Projects
Commit 9c20d9bd authored by thl-cmk's avatar thl-cmk :flag_na:
Browse files

added local delivery of images and configs

parent b874c39f
No related branches found
No related tags found
No related merge requests found
pnp/open-pnp.py 100644 → 100755
......@@ -21,10 +21,11 @@ from pathlib import Path
import sys
import xmltodict
import time
from typing import Optional
from typing import Optional, List
import logging
from netifaces import interfaces, ifaddresses, AF_INET, AF_INET6
BIND_PNP_SERVER = '0.0.0.0'
BIND_PNP_SERVER = '::'
PORT = 8080
TIME_FORMAT = '%Y-%m-%dT%H:%M:%S%Z'
STATUS_REFRESH = 10
......@@ -44,6 +45,9 @@ try:
except ModuleNotFoundError:
pass
CONFIG_BASE_URL = CONFIG_BASE_URL.rstrip('/')
IMAGE_BASE_URL = IMAGE_BASE_URL.rstrip('/')
class ErrorCodes:
__readable = {
......@@ -128,7 +132,7 @@ class Device:
self.platform: str = platform
self.hw_rev: str = hw_rev
self.serial: str = serial
self.src_address: str = src_address
self.ip_address: str = src_address
self.current_job: str = current_job
self.first_seen: str = first_seen
self.last_contact: str = last_contact
......@@ -176,7 +180,7 @@ class Device:
@error_code.setter
def error_code(self, error_code: int):
self.__error_code = error_code
self.state_readable = ERROR.readable(error_code)
self.error_code_readable = ERROR.readable(error_code)
app = Flask(__name__, template_folder='./templates')
......@@ -192,11 +196,12 @@ current_dir = Path(__file__)
devices: [str, Device] = {}
def device_info(udi: str, correlator: str, info_type: str) -> str:
def pnp_device_info(udi: str, correlator: str, info_type: str) -> str:
# info_type can be one of:
# image, hardware, filesystem, udi, profile, all
if devices[udi].current_job != 'urn:cisco:pnp:image-install':
devices[udi].current_job = 'urn:cisco:pnp:device-info'
device = devices[udi]
if device.current_job != 'urn:cisco:pnp:image-install':
device.current_job = 'urn:cisco:pnp:device-info'
jinja_context = {
'udi': udi,
'correlator': correlator,
......@@ -205,12 +210,13 @@ def device_info(udi: str, correlator: str, info_type: str) -> str:
return render_template('device_info.xml', **jinja_context)
def backoff(udi: str, correlator: str, minutes: Optional[int] = 2) -> str:
def pnp_backoff(udi: str, correlator: str, minutes: Optional[int] = 1) -> str:
seconds = 0
hours = 0
devices[udi].status = f'backoff for {hours:02d}:{minutes:02d}:{seconds:02d}'
devices[udi].current_job = 'urn:cisco:pnp:backoff'
devices[udi].backoff = False
device = devices[udi]
device.status = f'backoff for {hours:02d}:{minutes:02d}:{seconds:02d}'
device.current_job = 'urn:cisco:pnp:backoff'
device.pnp_backoff = False
jinja_context = {
'udi': udi,
'correlator': correlator,
......@@ -222,10 +228,11 @@ def backoff(udi: str, correlator: str, minutes: Optional[int] = 2) -> str:
# will not be used as we remove PNP via EEM. PNP terminate is missing a "write mem"
def backoff_terminate(udi: str, correlator: str) -> str:
devices[udi].status = f'finished'
devices[udi].pnp_floe = PNPFLOW.FINISHED
devices[udi].current_job = 'urn:cisco:pnp:backoff-terminate'
def pnp_backoff_terminate(udi: str, correlator: str) -> str:
device = devices[udi]
device.status = f'finished'
device.pnp_floe = PNPFLOW.FINISHED
device.current_job = 'urn:cisco:pnp:backoff-terminate'
jinja_context = {
'udi': udi,
'correlator': correlator,
......@@ -233,11 +240,11 @@ def backoff_terminate(udi: str, correlator: str) -> str:
return render_template('backoff_terminate.xml', **jinja_context)
def install_image(udi: str, correlator: str) -> str:
def pnp_install_image(udi: str, correlator: str) -> str:
device = devices[udi]
device.current_job = 'urn:cisco:pnp:image-install'
device.pnp_flow = PNPFLOW.UPDATE_START
device.backoff = True
device.pnp_backoff = True
device.refresh_data = True
jinja_context = {
'udi': udi,
......@@ -246,12 +253,12 @@ def install_image(udi: str, correlator: str) -> str:
'image': device.target_image.image,
'md5': device.target_image.md5,
'destination': device.destination_name,
'delay': 0, # seconds
'delay': 0, # reload in seconds
}
return render_template('image_install.xml', **jinja_context)
def config_upgrade(udi: str, correlator: str) -> str:
def pnp_config_upgrade(udi: str, correlator: str) -> str:
device = devices[udi]
device.current_job = 'urn:cisco:pnp:device-info'
device.pnp_flow = PNPFLOW.CONFIG_START
......@@ -260,12 +267,12 @@ def config_upgrade(udi: str, correlator: str) -> str:
'correlator': correlator,
'base_url': CONFIG_BASE_URL,
'serial_number': device.serial,
'delay': 0, # seconds
'delay': 0, # reload in seconds
}
return render_template('config_upgrade.xml', **jinja_context)
def bye(udi: str, correlator: str) -> str:
def pnp_bye(udi: str, correlator: str) -> str:
jinja_context = {
'udi': udi,
'correlator': correlator,
......@@ -289,7 +296,7 @@ def create_new_device(udi: str, src_add: str):
current_job='urn:cisco:pnp:device-info',
)
device = devices[udi]
device.backoff = True
device.pnp_backoff = True
if device.platform in PLATFORMS:
platform = PLATFORMS[device.platform]
if platform.image in IMAGES:
......@@ -334,6 +341,22 @@ def check_update(udi: str):
device.hard_error = True
def get_local_ip_addresses() -> List[str]:
_addresses = []
for iface_name in interfaces():
try:
for _address in ifaddresses(iface_name).setdefault(AF_INET):
_addresses.append(_address['addr'])
except TypeError:
pass
try:
for _address in ifaddresses(iface_name).setdefault(AF_INET6):
_addresses.append(_address['addr'])
except TypeError: # if no ip address in interface ifaddresses(iface_name) comes back with None
pass
return _addresses
@app.route('/')
def root():
return redirect('/status', 302)
......@@ -347,6 +370,7 @@ def status():
jinja_context = {
'devices': device_list,
'refresh': STATUS_REFRESH,
'config_base_url': CONFIG_BASE_URL,
}
result = render_template('status.html', **jinja_context)
return Response(result)
......@@ -368,14 +392,14 @@ def buttons():
return redirect('/status', 302)
@app.route('/configs/<path:path>')
def serve_configs(path):
return send_from_directory('configs', path)
@app.route('/configs/<path:file>')
def serve_configs(file):
return send_from_directory('configs', file, mimetype='text/plain')
@app.route('/images/<path:path>')
def serve_sw_images(path):
return send_from_directory('images', path)
@app.route('/images/<path:file>')
def serve_sw_images(file):
return send_from_directory('images', file, mimetype='application/octet-stream')
@app.route('/pnp/HELLO')
......@@ -387,41 +411,39 @@ def pnp_hello():
def pnp_work_request():
src_add = request.environ.get('HTTP_X_REAL_IP', request.remote_addr)
data = xmltodict.parse(request.data)
# print(f'REQUEST: {data}')
correlator = data['pnp']['info']['@correlator']
udi = data['pnp']['@udi']
if udi in devices.keys():
device = devices[udi]
device.last_contact = time.strftime(TIME_FORMAT)
device.src_address = src_add
device.ip_address = src_add
if device.hard_error:
return Response(backoff(udi, correlator), mimetype='text/xml')
if device.backoff:
return Response(backoff(udi, correlator), mimetype='text/xml')
return Response(pnp_backoff(udi, correlator, 59), mimetype='text/xml')
if device.pnp_backoff:
return Response(pnp_backoff(udi, correlator), mimetype='text/xml')
if device.pnp_flow == PNPFLOW.NEW:
device.pnp_flow = PNPFLOW.INFO
return Response(device_info(udi, correlator, 'all'), mimetype='text/xml')
return Response(pnp_device_info(udi, correlator, 'all'), mimetype='text/xml')
if device.pnp_flow == PNPFLOW.UPDATE_NEEDED:
device.pnp_flow = PNPFLOW.UPDATE_START
return Response(install_image(udi, correlator), mimetype='text/xml')
return Response(pnp_install_image(udi, correlator), mimetype='text/xml')
if device.pnp_flow == PNPFLOW.UPDATE_RELOAD:
return Response(device_info(udi, correlator, 'all'), mimetype='text/xml')
return Response(pnp_device_info(udi, correlator, 'all'), mimetype='text/xml')
if device.pnp_flow == PNPFLOW.UPDATE_DOWN:
return Response(config_upgrade(udi, correlator), mimetype='text/xml')
return Response(pnp_config_upgrade(udi, correlator), mimetype='text/xml')
if device.pnp_flow == PNPFLOW.CONFIG_DOWN: # will never reach this point, as pnp is removed bei EEM :-)
return Response(backoff_terminate(udi, correlator), mimetype='text/xml')
return Response(pnp_backoff_terminate(udi, correlator), mimetype='text/xml')
return Response('', 200)
else:
create_new_device(udi, src_add)
# return Response(device_info(udi, correlator, 'all'), mimetype='text/xml')
devices[udi].state = PNPFLOW.NEW
return Response(backoff(udi, correlator), mimetype='text/xml')
return Response(pnp_backoff(udi, correlator), mimetype='text/xml')
@app.route('/pnp/WORK-RESPONSE', methods=['POST'])
def pnp_work_response():
data = xmltodict.parse(request.data)
# print(f'RESPONSE: {data}')
src_add = request.environ.get('HTTP_X_REAL_IP', request.remote_addr)
udi = data['pnp']['@udi']
job_type = data['pnp']['response']['@xmlns']
......@@ -429,7 +451,7 @@ def pnp_work_response():
create_new_device(udi, src_add)
device = devices[udi]
device.src_address = src_add
device.ip_address = src_add
device.last_contact = time.strftime(TIME_FORMAT)
if job_type == 'urn:cisco:pnp:fault': # error without job info (correlator):-(
......@@ -439,7 +461,7 @@ def pnp_work_response():
job_status = int(data['pnp']['response']['@success'])
if job_status == 1: # success
if job_type != 'urn:cisco:pnp:backoff':
device.backoff = True
device.pnp_backoff = True
device.error_count = 0
if job_type == 'urn:cisco:pnp:device-info':
if device.pnp_flow in [PNPFLOW.INFO, PNPFLOW.UPDATE_RELOAD]:
......@@ -455,7 +477,7 @@ def pnp_work_response():
device.status = 'Finished. You can remove the device from the list :-)'
elif job_type == 'urn:cisco:pnp:backoff':
pass
return Response(bye(udi, correlator), mimetype='text/xml')
return Response(pnp_bye(udi, correlator), mimetype='text/xml')
elif job_status == 0:
error_code = int(data['pnp']['response']['errorInfo']['errorCode'].split(' ')[-1])
device.error_count += 1
......@@ -463,7 +485,7 @@ def pnp_work_response():
device.error_code = error_code
if error_code in [ERROR.PNP_ERROR_BAD_CHECKSUM, ERROR.PNP_ERROR_FILE_NOT_FOUND]:
device.hard_error = True
return Response(bye(udi, correlator), mimetype='text/xml')
return Response(pnp_bye(udi, correlator), mimetype='text/xml')
device.current_job = 'none'
return Response('', 200)
......@@ -476,5 +498,19 @@ if __name__ == '__main__':
print('CONFIG_BASE_URL not set, check ./vars/vars.py')
exit(1)
print('runnig PnP server. Stop with ctrl+c')
print()
print('Running PnP server. Stop with ctrl+c')
print(f'Bind IP ...............: {BIND_PNP_SERVER}')
print(f'Server port ...........: {PORT}')
print(f'Image base URL ........: {IMAGE_BASE_URL}')
print(f'Config file base URL ..: {CONFIG_BASE_URL}')
print()
print('The PnP server is running on the following URL(s)')
if BIND_PNP_SERVER in ['0.0.0.0', '::']:
addresses = get_local_ip_addresses()
for address in addresses:
print(f' http://{address}:{PORT}/status')
else:
print(f'Status page running on : http://{BIND_PNP_SERVER}:{PORT}/status')
print()
app.run(host=BIND_PNP_SERVER, port=PORT)
certifi==2022.9.24
chardet==5.0.0
charset-normalizer==3.0.1
click==8.1.3
Flask==2.2.2
idna==3.4
importlib-metadata==5.0.0
importlib-metadata==5.1.0
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.1
pkg_resources==0.0.0
requests==2.28.1
typing_extensions==4.4.0
urllib3==1.26.12
netifaces==0.11.0
Werkzeug==2.2.2
xmltodict==0.13.0
zipp==3.10.0
zipp==3.11.0
......@@ -26,23 +26,24 @@
<th>Serial number</th>
<th>Platform</th>
<th>HW rev.</th>
<th>Device IP</th>
<th>IP-Address</th>
<th>PnP Flow</th>
<th>Status</th>
<td>First seen</td>
<th>First seen</th>
<th>Last contact</th>
<th>Current job</th>
<th>Current<br>version</th>
<th>Target<br>version</th>
<th>Current image</th>
<th>Target image</th>
<th>Size</th>
<th>Free space</th>
<th>Destination</th>
<!-- <th>Current image</th>-->
<!-- <th>Target image</th>-->
<!-- <th>Size</th>-->
<!-- <th>Free space</th>-->
<!-- <th>Destination</th>-->
<th>Error<br>code</th>
<th>Last error</th>
<th>Error<br>count</th>
<th>Hard<br>error</th>
<th>Backoff<br>count</th>
<!-- <th>Backoff</th>-->
</tr>
{% for device in devices %}
<tr>
......@@ -54,10 +55,11 @@
<input type="submit" value="Remove" name="{{ device.udi }}"/>
</form>
</td>
<td>{{ device.serial }}</td>
<!-- <td>{{ device.serial }}</td>-->
<td><a href="{{ config_base_url }}/{{ device.serial }}.cfg" target="_blank">{{ device.serial }}</a></td>
<td>{{ device.platform }}</td>
<td>{{ device.hw_rev }}</td>
<td>{{ device.src_address }}</td>
<td>{{ device.ip_address }}</td>
<td>{{ device.pnp_flow_readable }}</td>
<td>{{ device.status }} </td>
<td>{{ device.first_seen }}</td>
......@@ -65,22 +67,23 @@
<td>{{ device.current_job }}</td>
<td>{{ device.version }}</td>
<td>{{ device.target_image.version }}</td>
<td>{{ device.image }}</td>
<td>{{ device.target_image.image }}</td>
<td>{{ device.target_image.size }}</td>
<td>{{ device.destination_free }}</td>
<td>{{ device.destination_name }}</td>
<!-- <td>{{ device.image }}</td>-->
<!-- <td>{{ device.target_image.image }}</td>-->
<!-- <td>{{ device.target_image.size }}</td>-->
<!-- <td>{{ device.destination_free }}</td>-->
<!-- <td>{{ device.destination_name }}</td>-->
<td>{{ device.error_code }}</td>
<td>{{ device.error_code_readable }}</td>
<td>{{ device.error_count }}</td>
<td>{{ device.hard_error }}</td>
<td>{{ device.backoff }}</td>
<!-- <td>{{ device.backoff }}</td>-->
</tr>
{% endfor %}
</table>
<footer>
<br><br>
<p>Written by: <a href="mailto:thl-cmk@outlook.com,">thl-cmk</a> | Date: 2022-12-05 | for more information see
<a href="https://thl-cmk.hopto.org/gitlab/bits-and-bytes/cisco_day0_provision">Cisco Day0 provision</a> </p>
<a href="https://thl-cmk.hopto.org/gitlab/bits-and-bytes/cisco_day0_provision" target="_blank">Cisco Day0 provisioning</a> </p>
</footer>
</body>
</html>
......
......@@ -18,6 +18,8 @@ PLATFORMS = {
),
'C1117-4PMLTEEAWE': Model(
# image='C1100_17_01',
image='C1100_17_09',
# image='C1100_17_09',
image='C1100_17_06_04',
),
}
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
BIND_PNP_SERVER = '0.0.0.0'
PORT = 8080
TIME_FORMAT = '%Y-%m-%dT%H:%M:%S%Z'
STATUS_REFRESH = 5
IMAGE_BASE_URL = 'http://192.168.10.15'
CONFIG_BASE_URL = 'http://192.168.10.15'
FLASK_DEBUG = False
\ No newline at end of file
# BIND_PNP_SERVER = '0.0.0.0'
# BIND_PNP_SERVER = '::'
# PORT = 8080
# TIME_FORMAT = '%Y-%m-%dT%H:%M:%S%Z'
# STATUS_REFRESH = 60
# FLASK_DEBUG = True
IMAGE_BASE_URL = 'http://192.168.10.133:8080/images'
CONFIG_BASE_URL = 'http://192.168.10.133:8080/configs'
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment