Skip to content

Commit

Permalink
Add documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
sanecz committed Feb 5, 2022
1 parent ff92df3 commit 57a4e56
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 6 deletions.
2 changes: 1 addition & 1 deletion netbox_ipmi_ovh/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
class NetboxIpmiOvhConfig(PluginConfig):
name = 'netbox_ipmi_ovh'
verbose_name = 'Netbox ipmi ovh'
description = ''
description = 'A plugin used to add a button in the dcim.device to allow easier access to the IPMI for OVH managed bare metal servers.'
version = '0.0.1'
author = 'Lisa Bekdache'
author_email = '[email protected]'
Expand Down
11 changes: 11 additions & 0 deletions netbox_ipmi_ovh/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
class NetboxIpmiOvh(Exception):
"""
Base exception for the netbox-ipmi-ovh plugin
"""
pass


class NetboxIpmiOvhTimeout(NetboxIpmiOvh):
"""
Exception when OVH is too slow to answer
i.e BMC is down on the server
"""
pass


class NetboxIpmiOvhError(NetboxIpmiOvh):
"""
Exception for wrong arguments or error thrown
by the OVH API
"""
pass
33 changes: 30 additions & 3 deletions netbox_ipmi_ovh/ipmi.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@
from netbox_ipmi_ovh.exceptions import NetboxIpmiOvhTimeout, NetboxIpmiOvhError

def wait_for_ovh_task(client, service_name, task_id):
# ovh timeout is between 120 seconds and 20minutes in worst case (trust me i tried)
# todo: maybe do an async task later?
for _ in range(120):
"""
Wait for OVH task to finish
:param client: Instance of ovh.Client()
:param service_name: Name of the dedicated server (by ovh name: nsXXXXXX.ip-XX-YY-ZZ.tld
:param task_id: ID of the task from /dedicated/server/{service_name}/features/ipmi/access
:raises NetboxIpmiOvhError: Raised when status is changed to ovhError/customerError and ipmi
cannot be given
:raises NetboxIpmiOvhTimeout: Raised when requests take more than 120seconds, should happend
rarely / never except if your BMC is dying
:return: Nothing, if it returns then the command is successful
"""
for _ in range(120): # ovh timeout is between 120 seconds and 20minutes in worst case (trust me i tried)
result = client.get(f"/dedicated/server/{service_name}/task/{task_id}")
if result["status"] == "done":
return
Expand All @@ -16,9 +27,23 @@ def wait_for_ovh_task(client, service_name, task_id):


def request_ipmi_access(client, service_name, access_type, ssh_key=None, ip_to_allow=None):
"""
Request an IPMI access by calling the OVH API
:param client: Instance of ovh.Client()
:param service_name: Name of the dedicated server (by ovh name: nsXXXXXX.ip-XX-YY-ZZ.tld
:param access_type: IPMI console access type
:param ssh_key: SSH key name to allow access on KVM/IP interface with (name from /me/sshKey)
:param ip_to_allow: IP to allow connection from for this IPMI session
:return: URL to kvmIP, ssh command line in string or JNLP xml string depending on the access type
:rtype: str
"""
# I could also do the check of the features in the template_content
# but i do not want to slow down the loading of the page, as we don't
# know if the OVH api is up or down or very slow


# Retreive available features for the server (sometime somes are not enabled or even IPMI is not enabled)
result = client.get(
f'/dedicated/server/{service_name}/features/ipmi'
)
Expand All @@ -34,6 +59,7 @@ def request_ipmi_access(client, service_name, access_type, ssh_key=None, ip_to_a
f"Supported access types: {', '.join(supported_features)} "
)

# https://api.ovh.com/console/#/dedicated/server/{serviceName}/features/ipmi/access#POST
parameters = {
"type": access_type,
"ttl": 15,
Expand All @@ -56,6 +82,7 @@ def request_ipmi_access(client, service_name, access_type, ssh_key=None, ip_to_a

wait_for_ovh_task(client, service_name, result['taskId'])

# finally retreive the IPMI access
result = client.get(
f'/dedicated/server/{service_name}/features/ipmi/access',
type=access_type
Expand Down
1 change: 1 addition & 0 deletions netbox_ipmi_ovh/navigation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from extras.plugins import PluginMenuItem

# Add the IPMI Config link to the menu Plugins
menu_items = (
PluginMenuItem(
link_text="IPMI OVH", link="plugins:netbox_ipmi_ovh:ipmi_config",
Expand Down
24 changes: 22 additions & 2 deletions netbox_ipmi_ovh/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@
NETBOX_CURRENT_VERSION = version.parse(settings.VERSION)

PLUGIN_SETTINGS = settings.PLUGINS_CONFIG.get("netbox_ipmi_ovh", {})

# Required configuration
OVH_ENDPOINTS = PLUGIN_SETTINGS["endpoints"]
OVH_ENDPOINT_FIELD = PLUGIN_SETTINGS["ovh_endpoint_field"]
OVH_SERVER_NAME_FIELD = PLUGIN_SETTINGS["ovh_server_name_field"]

# Allowed access type for an IPMI access
MAPPING_ACCESS_TYPE = {
"kvmipHtml5URL": lambda access: redirect(access),
"kvmipJnlp": lambda access: HttpResponse(access, content_type='application/x-java-jnlp-file'),
Expand All @@ -34,6 +37,9 @@ class BaseIpmiView(PermissionRequiredMixin, View):

@staticmethod
def _get_user_config(user):
"""
Retrieve user-specific settings
"""
try:
usercfg = UserIpmiCfg.objects.get(user=user)
except UserIpmiCfg.DoesNotExist:
Expand All @@ -48,6 +54,11 @@ class UserIpmiCfgView(BaseIpmiView):
template_name = 'netbox_ipmi_ovh/ipmi_config.html'

def post(self, request):
"""
Update the user configuration:
- ssh_key_name: SSH key name to allow access on KVM/IP interface with (name from /me/sshKey)
- allowed_ip: IP to allow connection from for this IPMI session
"""
usercfg = self._get_user_config(request.user)

form = UserIpmiCfgForm(data=request.POST, instance=usercfg)
Expand Down Expand Up @@ -80,19 +91,28 @@ class IpmiView(BaseIpmiView):
template_error = "netbox_ipmi_ovh/ipmi_error.html"

def get(self, request):
"""
Query the OVH API to retrieive an IPMI access for a device
Works only on bare-metal dedicated cloud servers
"""
device_id = request.GET.get("device")
access_type = request.GET.get("type")
usercfg = self._get_user_config(request.user)
device = Device.objects.get(id=device_id)
ovh_server_name = getattr(device, OVH_SERVER_NAME_FIELD)

if not ovh_server_name:
return render(request, self.template_error, {
"error_message": f"Server name not found, please fill the field {OVH_SERVER_NAME_FIELD}"
})

if hasattr(device, OVH_ENDPOINT_FIELD):
ovh_endpoint = getattr(device, OVH_ENDPOINT_FIELD)
elif OVH_ENDPOINT_FIELD in device.custom_field_data:
ovh_endpoint = device.custom_field_data[OVH_ENDPOINT_FIELD]
else:
return render(request, self.template_error, {
"error_message": "No OVH endpoint has been detected, cannot process request"
"error_message": f"No OVH endpoint has been detected, cannot process request, please fill the field {OVH_ENDPOINT_FIELD}"
})

if ovh_endpoint not in OVH_ENDPOINTS:
Expand All @@ -102,7 +122,7 @@ def get(self, request):

client = Client(**OVH_ENDPOINTS[ovh_endpoint])

# trying to retrieive user IP to send to ovh
# trying to retreive user IP to send to ovh
# if no ip is send in args to ovh api, they will use the ip used for
# the http call to their api, and it will be the server ip

Expand Down

0 comments on commit 57a4e56

Please sign in to comment.