Skip to content
Snippets Groups Projects
Commit 58bab5d0 authored by Benjamin Bertrand's avatar Benjamin Bertrand
Browse files

Add Create VM button

Allow to create a VM or Virtual IOC
parent 77c0e5cc
No related branches found
No related tags found
No related merge requests found
...@@ -646,6 +646,13 @@ class Host(CreatedMixin, db.Model): ...@@ -646,6 +646,13 @@ class Host(CreatedMixin, db.Model):
for item in kwargs['items']] for item in kwargs['items']]
super().__init__(**kwargs) super().__init__(**kwargs)
@property
def is_ioc(self):
for interface in self.interfaces:
if interface.is_ioc:
return True
return False
def __str__(self): def __str__(self):
return str(self.name) return str(self.name)
...@@ -734,6 +741,13 @@ class Interface(CreatedMixin, db.Model): ...@@ -734,6 +741,13 @@ class Interface(CreatedMixin, db.Model):
def address(self): def address(self):
return ipaddress.ip_address(self.ip) return ipaddress.ip_address(self.ip)
@property
def is_ioc(self):
for tag in self.tags:
if tag.name == 'IOC':
return True
return False
def __str__(self): def __str__(self):
return str(self.name) return str(self.name)
......
...@@ -10,6 +10,7 @@ This module defines the network blueprint forms. ...@@ -10,6 +10,7 @@ This module defines the network blueprint forms.
""" """
import ipaddress import ipaddress
from flask import current_app
from flask_login import current_user from flask_login import current_user
from wtforms import (SelectField, StringField, TextAreaField, IntegerField, from wtforms import (SelectField, StringField, TextAreaField, IntegerField,
SelectMultipleField, BooleanField, validators) SelectMultipleField, BooleanField, validators)
...@@ -157,3 +158,13 @@ class InterfaceForm(CSEntryForm): ...@@ -157,3 +158,13 @@ class InterfaceForm(CSEntryForm):
class HostInterfaceForm(HostForm, InterfaceForm): class HostInterfaceForm(HostForm, InterfaceForm):
pass pass
class CreateVMForm(CSEntryForm):
cores = SelectField('Cores', default=2, coerce=int)
memory = SelectField('Memory (GB)', default=2, coerce=int)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.cores.choices = utils.get_choices(current_app.config['VM_CORES_CHOICES'])
self.memory.choices = utils.get_choices(current_app.config['VM_MEMORY_CHOICES'])
...@@ -13,9 +13,9 @@ import ipaddress ...@@ -13,9 +13,9 @@ import ipaddress
import sqlalchemy as sa import sqlalchemy as sa
from flask import (Blueprint, render_template, jsonify, session, from flask import (Blueprint, render_template, jsonify, session,
redirect, url_for, request, flash, current_app) redirect, url_for, request, flash, current_app)
from flask_login import login_required from flask_login import login_required, current_user
from .forms import (HostForm, InterfaceForm, HostInterfaceForm, NetworkForm, from .forms import (HostForm, InterfaceForm, HostInterfaceForm, NetworkForm,
NetworkScopeForm, DomainForm) NetworkScopeForm, DomainForm, CreateVMForm)
from ..extensions import db from ..extensions import db
from ..decorators import login_groups_accepted from ..decorators import login_groups_accepted
from .. import models, utils, helpers, tasks from .. import models, utils, helpers, tasks
...@@ -79,11 +79,24 @@ def create_host(): ...@@ -79,11 +79,24 @@ def create_host():
return render_template('network/create_host.html', form=form) return render_template('network/create_host.html', form=form)
@bp.route('/hosts/view/<name>') @bp.route('/hosts/view/<name>', methods=('GET', 'POST'))
@login_required @login_required
def view_host(name): def view_host(name):
host = models.Host.query.filter_by(name=name).first_or_404() host = models.Host.query.filter_by(name=name).first_or_404()
return render_template('network/view_host.html', host=host) form = CreateVMForm()
if host.is_ioc:
form.cores.choices = utils.get_choices(current_app.config['VIOC_CORES_CHOICES'])
form.memory.choices = utils.get_choices(current_app.config['VIOC_MEMORY_CHOICES'])
if form.validate_on_submit():
if not current_user.is_admin:
flash(f'Only admin users are allowed to create a VM!', 'info')
else:
interface = host.interfaces[0]
job = tasks.trigger_vm_creation(name, interface, form.memory.data, form.cores.data)
current_app.logger.info(f'Creation of {name} requested: job {job.get_id()}')
flash(f'Creation of {name} requested!', 'success')
return redirect(url_for('network.view_host', name=name))
return render_template('network/view_host.html', host=host, form=form)
@bp.route('/hosts/edit/<name>', methods=('GET', 'POST')) @bp.route('/hosts/edit/<name>', methods=('GET', 'POST'))
......
...@@ -70,3 +70,11 @@ DOCUMENTATION_URL = 'http://ics-infrastructure.pages.esss.lu.se/csentry/index.ht ...@@ -70,3 +70,11 @@ DOCUMENTATION_URL = 'http://ics-infrastructure.pages.esss.lu.se/csentry/index.ht
# AWX job templates # AWX job templates
AWX_JOB_ENABLED = False AWX_JOB_ENABLED = False
AWX_CORE_SERVICES_UPDATE = 'ics-ans-core @ DHCP test' AWX_CORE_SERVICES_UPDATE = 'ics-ans-core @ DHCP test'
AWX_CREATE_VM = 'ics-ans-deploy-proxmox-vm'
AWX_CREATE_VIOC = 'ics-ans-deploy-vioc'
VM_CORES_CHOICES = [1, 2, 4, 6, 8, 24]
VM_MEMORY_CHOICES = [2, 4, 8, 16, 32, 128]
VM_DEFAULT_DNS = '172.16.6.21'
VIOC_CORES_CHOICES = [1, 3, 6]
VIOC_MEMORY_CHOICES = [2, 4, 8]
...@@ -14,6 +14,39 @@ from flask import current_app ...@@ -14,6 +14,39 @@ from flask import current_app
from rq import Queue from rq import Queue
def trigger_vm_creation(name, interface, memory, cores):
"""Trigger a job to create a virtual machine or virtual IOC"""
extra_vars = [
f'vmname={name}',
f'memory={memory}',
f'cores={cores}',
f'vcpus={cores}',
f'vlan_name={interface.network.vlan_name}',
f'vlan_id={interface.network.vlan_id}',
f'mac={interface.mac.address}',
]
if interface.is_ioc:
job_template = current_app.config['AWX_CREATE_VIOC']
else:
job_template = current_app.config['AWX_CREATE_VM']
extra_vars.extend([
f'ip_address={interface.ip}',
f'domain={interface.network.domain.name}',
f'dns={current_app.config["VM_DEFAULT_DNS"]}',
f'netmask={interface.network.netmask}',
f'gateway={interface.network.gateway}',
])
q = Queue()
current_app.logger.info(f'Launch new job to create the {name} VM: {job_template} with {extra_vars}')
job = q.enqueue(
launch_job_template,
job_template=job_template,
extra_vars=extra_vars,
timeout=500,
)
return job
def trigger_core_services_update(): def trigger_core_services_update():
"""Trigger a job to update the core services on the TN (DNS and DHCP) """Trigger a job to update the core services on the TN (DNS and DHCP)
......
...@@ -36,21 +36,24 @@ ...@@ -36,21 +36,24 @@
{% macro render_field(field) -%} {% macro render_field(field) -%}
{% set field_class = kwargs.pop('class_', '') + ' form-control' %} {% set field_class = kwargs.pop('class_', '') + ' form-control' %}
{% set label_size = kwargs.pop('label_size', '2') %}
{% set input_size = kwargs.pop('input_size', '10') %}
{% if field.errors %} {% if field.errors %}
{% set field_class = field_class + ' is-invalid' %} {% set field_class = field_class + ' is-invalid' %}
{% endif %} {% endif %}
<div class="form-group row"> <div class="form-group row">
{% if field.type == 'BooleanField' %} {% if field.type == 'BooleanField' %}
<div class="col-sm-10 offset-sm-2"> <div class="col-sm-{{ input_size }} offset-sm-{{ label_size }}">
<div class="form-check"> <div class="form-check">
{{ field(class_="form-check-input") }} {{ field(class_="form-check-input") }}
{{ field.label(class_="form-check-label") }} {{ field.label(class_="form-check-label") }}
</div> </div>
</div> </div>
{% else %} {% else %}
{{ field.label(class_="col-sm-2 col-form-label") }} {{ field.label(class_="col-sm-" + label_size + " col-form-label") }}
<div class="col-sm-10"> <div class="col-sm-{{ input_size }}">
{{ field(class_=field_class, **kwargs) }} {{ field(class_=field_class, **kwargs) }}
{% if field.description %} {% if field.description %}
<small class="form-text text-muted"> <small class="form-text text-muted">
...@@ -94,6 +97,29 @@ ...@@ -94,6 +97,29 @@
</div> </div>
{%- endmacro %} {%- endmacro %}
{% macro submit_button_with_confirmation(title, message) -%}
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#submitModal" }}>
{{ title }}
</button>
<!-- Modal -->
<div class="modal fade" id="submitModal" tabindex="-1" role="dialog" aria-labelledby="submit-confirmation" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title">{{ message }}</h6>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">OK</button>
</div>
</div>
</div>
</div>
{%- endmacro %}
{% macro figure(filename, description) -%} {% macro figure(filename, description) -%}
<figure class="figure"> <figure class="figure">
<img src="{{ url_for('static', filename='img/' + filename) }}" class="img-fluid mx-auto d-block" alt="{{ description }}"> <img src="{{ url_for('static', filename='img/' + filename) }}" class="img-fluid mx-auto d-block" alt="{{ description }}">
......
{% extends "network/hosts.html" %} {% extends "network/hosts.html" %}
{% from "_helpers.html" import link_to_items, link_to_stack_members, delete_button_with_confirmation %} {% from "_helpers.html" import link_to_items, link_to_stack_members,
delete_button_with_confirmation, render_field, submit_button_with_confirmation %}
{% block title %}View Host - CSEntry{% endblock %} {% block title %}View Host - CSEntry{% endblock %}
...@@ -16,22 +17,41 @@ ...@@ -16,22 +17,41 @@
{% endblock %} {% endblock %}
{% block hosts_main %} {% block hosts_main %}
<dl class="row"> <div class="row">
<dt class="col-sm-3">Hostname</dt> <div class="col-sm-9">
<dd class="col-sm-9">{{ host.name }}</dd> <dl class="row">
<dt class="col-sm-3">Device Type</dt> <dt class="col-sm-3">Hostname</dt>
<dd class="col-sm-9">{{ host.device_type }}</dd> <dd class="col-sm-9">{{ host.name }}</dd>
{% if host.items %} <dt class="col-sm-3">Device Type</dt>
<dt class="col-sm-3">Items</dt> <dd class="col-sm-9">{{ host.device_type }}</dd>
<dd class="col-sm-9">{{ link_to_items(host.items) }}</dd> {% if host.items %}
<dt class="col-sm-3">Items</dt>
<dd class="col-sm-9">{{ link_to_items(host.items) }}</dd>
{% endif %}
{% if host.stack_members() %}
<dt class="col-sm-3">Stack Members</dt>
<dd class="col-sm-9">{{ link_to_stack_members(host.stack_members()) }}</dd>
{% endif %}
<dt class="col-sm-3">Description</dt>
<dd class="col-sm-9">{{ host.description }}</dd>
</dl>
</div>
{% if host.device_type.name.startswith('Virtual') and current_user.is_admin %}
{% if host.is_ioc %}
{% set vm_type = 'Virtual IOC' %}
{% else %}
{% set vm_type = 'VM' %}
{% endif %} {% endif %}
{% if host.stack_members() %} <div class="col-sm-3">
<dt class="col-sm-3">Stack Members</dt> <form id="createVMForm" method="POST">
<dd class="col-sm-9">{{ link_to_stack_members(host.stack_members()) }}</dd> {{ form.hidden_tag() }}
{{ render_field(form.cores, label_size='6', input_size='6') }}
{{ render_field(form.memory, label_size='6', input_size='6') }}
{{ submit_button_with_confirmation('Create ' + vm_type, 'Do you really want to create the ' + vm_type + ' ' + host.name + '?') }}
</form>
</div>
{% endif %} {% endif %}
<dt class="col-sm-3">Description</dt> </div>
<dd class="col-sm-9">{{ host.description }}</dd>
</dl>
<h3>Interfaces</h3> <h3>Interfaces</h3>
<table id="interfaces_table" class="table table-hover table-sm"> <table id="interfaces_table" class="table table-hover table-sm">
<thead> <thead>
......
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