Commit 6d803ade by Karsa Zoltán István

network config for cloud-init

parent b2ed9564
...@@ -60,7 +60,7 @@ from vm.models import ( ...@@ -60,7 +60,7 @@ from vm.models import (
InstanceTemplate, Lease, InterfaceTemplate, Node, Trait, Instance InstanceTemplate, Lease, InterfaceTemplate, Node, Trait, Instance
) )
from .models import Profile, GroupProfile, Message from .models import Profile, GroupProfile, Message
from .validators import domain_validator, meta_data_validator, user_data_validator from .validators import domain_validator, meta_data_validator, user_data_validator, network_data_validator
LANGUAGES_WITH_CODE = ((l[0], format_lazy("{} ({})",l[1], l[0])) LANGUAGES_WITH_CODE = ((l[0], format_lazy("{} ({})",l[1], l[0]))
for l in LANGUAGES) for l in LANGUAGES)
...@@ -1540,16 +1540,20 @@ class CIDataForm(forms.ModelForm): ...@@ -1540,16 +1540,20 @@ class CIDataForm(forms.ModelForm):
ci_user_data = forms.CharField( ci_user_data = forms.CharField(
widget=forms.Textarea(attrs={'rows': 12, 'style' : 'display:none;'}), widget=forms.Textarea(attrs={'rows': 12, 'style' : 'display:none;'}),
required=False) required=False)
ci_network_config = forms.CharField(
widget=forms.Textarea(attrs={'rows': 5, 'style' : 'display:none;'}),
required=False)
def clean(self): def clean(self):
data = self.cleaned_data data = self.cleaned_data
meta_data_validator(self.instance, data['ci_meta_data']) meta_data_validator(self.instance, data['ci_meta_data'])
user_data_validator(self.instance, data['ci_user_data']) user_data_validator(self.instance, data['ci_user_data'])
network_data_validator(self.instance, data['ci_network_config'])
return super().clean() return super().clean()
class Meta: class Meta:
model = Instance model = Instance
fields = ('ci_meta_data', 'ci_user_data',) fields = ('ci_meta_data', 'ci_user_data', 'ci_network_config',)
class GroupPermissionForm(forms.ModelForm): class GroupPermissionForm(forms.ModelForm):
......
...@@ -1374,3 +1374,8 @@ textarea[name="new_members"] { ...@@ -1374,3 +1374,8 @@ textarea[name="new_members"] {
width: 95%; width: 95%;
height: 350px; height: 350px;
} }
#ace-network-data {
position: absolute;
width: 95%;
height: 150px;
}
\ No newline at end of file
...@@ -1607,4 +1607,10 @@ textarea[name="new_members"] { ...@@ -1607,4 +1607,10 @@ textarea[name="new_members"] {
position: absolute; position: absolute;
width: 95%; width: 95%;
height: 350px; height: 350px;
}
#ace-network-data {
position: absolute;
width: 95%;
height: 150px;
} }
\ No newline at end of file
var Websock_native; // not sure var Websock_native; // not sure
$(function() { $(function() {
if ($("#ace-meta-data").length && $("#ace-user-data").length) { if ($("#ace-meta-data").length && $("#ace-user-data").length && $("#ace-network-data").length) {
var meta_data = ace.edit('ace-meta-data', { var meta_data = ace.edit('ace-meta-data', {
mode: "ace/mode/yaml", useWorker: false, mode: "ace/mode/yaml", useWorker: false,
selectionStyle: "text" }); selectionStyle: "text" });
...@@ -18,12 +18,23 @@ $(function() { ...@@ -18,12 +18,23 @@ $(function() {
user_data.getSession().on("change", function () { user_data.getSession().on("change", function () {
textarea_user.val(user_data.getSession().getValue()); textarea_user.val(user_data.getSession().getValue());
}); });
var network_data = ace.edit('ace-network-data', {
mode: "ace/mode/yaml", useWorker: false,
selectionStyle: "text" });
var textarea_network = $('textarea[name="ci_network_config"]');
network_data.getSession().setValue(textarea_network.val())
network_data.getSession().on("change", function () {
textarea_network.val(network_data.getSession().getValue());
});
meta_data.session.setTabSize(4); meta_data.session.setTabSize(4);
meta_data.session.setUseSoftTabs(true); meta_data.session.setUseSoftTabs(true);
user_data.session.setTabSize(4); user_data.session.setTabSize(4);
user_data.session.setUseSoftTabs(true); user_data.session.setUseSoftTabs(true);
network_data.session.setTabSize(4);
network_data.session.setUseSoftTabs(true);
document.getElementById('ace-meta-data').style.fontSize='14px'; document.getElementById('ace-meta-data').style.fontSize='14px';
document.getElementById('ace-user-data').style.fontSize='14px'; document.getElementById('ace-user-data').style.fontSize='14px';
document.getElementById('ace-network-data').style.fontSize='14px';
/* */ /* */
$('#vm-details-cidata-save').click(function(e) { $('#vm-details-cidata-save').click(function(e) {
$.ajax({ $.ajax({
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
</div> </div>
{{ form.cloud_init|as_crispy_field }} {{ form.cloud_init|as_crispy_field }}
{{ form.ci_meta_data|as_crispy_field }} {{ form.ci_meta_data|as_crispy_field }}
{{ form.ci_network_config|as_crispy_field }}
{{ form.ci_user_data|as_crispy_field }} {{ form.ci_user_data|as_crispy_field }}
</fieldset> </fieldset>
......
...@@ -16,6 +16,9 @@ ...@@ -16,6 +16,9 @@
{{ ci_data.ci_meta_data|as_crispy_field }} {{ ci_data.ci_meta_data|as_crispy_field }}
<div id="ace-meta-data" style="border: 1px solid #ccc;"></div> <div id="ace-meta-data" style="border: 1px solid #ccc;"></div>
<div style="height: 160px;"></div> <div style="height: 160px;"></div>
{{ ci_data.ci_network_config|as_crispy_field }}
<div id="ace-network-data" style="border: 1px solid #ccc;"></div>
<div style="height: 160px;"></div>
{{ ci_data.ci_user_data|as_crispy_field }} {{ ci_data.ci_user_data|as_crispy_field }}
<div id="ace-user-data" style="border: 1px solid #ccc;"></div> <div id="ace-user-data" style="border: 1px solid #ccc;"></div>
<div style="height: 360px;"></div> <div style="height: 360px;"></div>
......
...@@ -90,6 +90,23 @@ def user_data_validator(instance, value): ...@@ -90,6 +90,23 @@ def user_data_validator(instance, value):
except jinja2.exceptions.TemplateError as exc: except jinja2.exceptions.TemplateError as exc:
raise ValidationError(exc.message) raise ValidationError(exc.message)
def network_data_validator(instance, value):
try:
instance.validate_ci_data(value)
except yaml.YAMLError as exc:
if hasattr(exc, 'problem_mark'):
if exc.context != None:
raise ValidationError(' parser says\n' + str(exc.problem_mark) + '\n ' +
str(exc.problem) + ' ' + str(exc.context) +
'\nPlease correct data and retry.')
else:
raise ValidationError(' parser says\n' + str(exc.problem_mark) + '\n ' +
str(exc.problem) + '\nPlease correct data and retry.')
else:
raise ValidationError("Something went wrong while parsing yaml file")
except jinja2.exceptions.TemplateError as exc:
raise ValidationError(exc.message)
def connect_command_template_validator(value): def connect_command_template_validator(value):
"""Validate value as a connect command template. """Validate value as a connect command template.
......
...@@ -448,14 +448,14 @@ class Disk(TimeStampedModel): ...@@ -448,14 +448,14 @@ class Disk(TimeStampedModel):
return result return result
@classmethod @classmethod
def create_ci_disk(cls, meta_data, user_data, user = None, **params): def create_ci_disk(cls, meta_data, user_data, network_data, user = None, **params):
params.setdefault('name', 'ci-disk') params.setdefault('name', 'ci-disk')
params.setdefault('type', 'raw-ro') params.setdefault('type', 'raw-ro')
params.setdefault('size', None) params.setdefault('size', None)
disk = cls.__create(params=params, user=user) disk = cls.__create(params=params, user=user)
queue_name = disk.get_remote_queue_name('storage', priority="fast") queue_name = disk.get_remote_queue_name('storage', priority="fast")
disk_desc = disk.get_disk_desc() disk_desc = disk.get_disk_desc()
result = storage_tasks.create_ci_disk.apply_async(args=[disk_desc, meta_data, user_data], result = storage_tasks.create_ci_disk.apply_async(args=[disk_desc, meta_data, user_data, network_data],
queue=queue_name queue=queue_name
).get(timeout=15) ).get(timeout=15)
disk.size = result['size'] disk.size = result['size']
......
...@@ -34,7 +34,7 @@ def create(disk_desc): ...@@ -34,7 +34,7 @@ def create(disk_desc):
@celery.task(name='storagedriver.create_ci_disk') @celery.task(name='storagedriver.create_ci_disk')
def create_ci_disk(disk_desc, meta_data, user_data): def create_ci_disk(disk_desc, meta_data, user_data, network_data):
pass pass
......
# Generated by Django 3.2.3 on 2022-10-06 14:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('vm', '0011_instance_hookurl'),
]
operations = [
migrations.AddField(
model_name='instance',
name='ci_network_config',
field=models.TextField(blank=True, default='#cloud-config\n\nusers:\n - name: {{ sysuser }} \n sudo: [\'ALL=(ALL) NOPASSWD:ALL\']\n groups: sudo\n shell: /bin/bash\n ssh_pwauth: True\n chpasswd: { expire: False }\n lock-passwd: false\n passwd: "{{ password | hash }}"', help_text='When cloud-init is active, set network-config(YAML format)', verbose_name='CI Network Data'),
),
migrations.AddField(
model_name='instancetemplate',
name='ci_network_config',
field=models.TextField(blank=True, default='#cloud-config\n\nusers:\n - name: {{ sysuser }} \n sudo: [\'ALL=(ALL) NOPASSWD:ALL\']\n groups: sudo\n shell: /bin/bash\n ssh_pwauth: True\n chpasswd: { expire: False }\n lock-passwd: false\n passwd: "{{ password | hash }}"', help_text='When cloud-init is active, set network-config(YAML format)', verbose_name='CI Network Data'),
),
]
# Generated by Django 3.2.3 on 2022-10-06 15:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('vm', '0012_auto_20221006_1433'),
]
operations = [
migrations.AlterField(
model_name='instance',
name='ci_network_config',
field=models.TextField(blank=True, default="network:\n version: 2\n ethernets:\n ens3:\n match:\n macaddress: 'mac'\n addresses: \n - ipv4/24\n gateway4: ipv4\n nameservers:\n addresses:\n - 8.8.8.8", help_text='When cloud-init is active, set network-config(YAML format)', verbose_name='CI Network Data'),
),
migrations.AlterField(
model_name='instancetemplate',
name='ci_network_config',
field=models.TextField(blank=True, default="network:\n version: 2\n ethernets:\n ens3:\n match:\n macaddress: 'mac'\n addresses: \n - ipv4/24\n gateway4: ipv4\n nameservers:\n addresses:\n - 8.8.8.8", help_text='When cloud-init is active, set network-config(YAML format)', verbose_name='CI Network Data'),
),
]
...@@ -88,15 +88,34 @@ platform: circle3 ...@@ -88,15 +88,34 @@ platform: circle3
CI_USER_DATA_DEF = """ CI_USER_DATA_DEF = """
#cloud-config #cloud-config
ssh_pwauth: 1
users: users:
- name: {{ sysuser }} - name: {{ sysuser }}
sudo: ['ALL=(ALL) NOPASSWD:ALL'] sudo: ['ALL=(ALL) NOPASSWD:ALL']
groups: sudo groups: sudo
shell: /bin/bash shell: /bin/bash
ssh_pwauth: True
chpasswd: { expire: False } chpasswd: { expire: False }
lock-passwd: false lock-passwd: false
passwd: "{{ password | hash }}" chpasswd:
list: |
{{ sysuser }}:{{ password }}
expire: False
""".strip()
CI_NETWORK_CONFIG_DEF = """
network:
version: 2
ethernets:
ens3:
match:
macaddress: 'mac'
addresses:
- ipv4/24
gateway4: ipv4
nameservers:
addresses:
- 8.8.8.8
""".strip() """.strip()
try: try:
...@@ -151,6 +170,8 @@ class VirtualMachineDescModel(BaseResourceConfigModel): ...@@ -151,6 +170,8 @@ class VirtualMachineDescModel(BaseResourceConfigModel):
'When cloud-init is active, set meta-data (YAML format)'), default=CI_META_DATA_DEF) 'When cloud-init is active, set meta-data (YAML format)'), default=CI_META_DATA_DEF)
ci_user_data = TextField(verbose_name=_('CI User Data'), blank=True, help_text=_( ci_user_data = TextField(verbose_name=_('CI User Data'), blank=True, help_text=_(
'When cloud-init is active, set user-data (YAML format)'), default=CI_USER_DATA_DEF) 'When cloud-init is active, set user-data (YAML format)'), default=CI_USER_DATA_DEF)
ci_network_config = TextField(verbose_name=_('CI Network Data'), blank=True, help_text=_(
'When cloud-init is active, set network-config(YAML format)'), default=CI_NETWORK_CONFIG_DEF)
req_traits = ManyToManyField(Trait, blank=True, req_traits = ManyToManyField(Trait, blank=True,
help_text=_("A set of traits required for a " help_text=_("A set of traits required for a "
"node to declare to be suitable " "node to declare to be suitable "
...@@ -450,6 +471,13 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -450,6 +471,13 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
template = env.from_string(data) template = env.from_string(data)
return template.render(ci_datas) return template.render(ci_datas)
@property
def get_network_config(self):
data = str(self.ci_network_config)
ci_datas = self.get_ci_data_dict()
template = env.from_string(data)
return template.render(ci_datas)
def validate_ci_data(self, data): def validate_ci_data(self, data):
ci_datas = self.get_ci_data_dict() ci_datas = self.get_ci_data_dict()
template = env.from_string(data) template = env.from_string(data)
...@@ -460,8 +488,8 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -460,8 +488,8 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
def callhookurl(self): def callhookurl(self):
import requests as req import requests as req
if self.hookurl: if self.hookurl:
logger.info("notify remote host (callhookurl)") logger.info("notify remote host (callhookurl): " + str(self.hookurl))
req.post(url=self.hookurl) req.post(url=str(self.hookurl))
def _update_status(self): def _update_status(self):
......
...@@ -539,14 +539,16 @@ class DeployOperation(InstanceOperation): ...@@ -539,14 +539,16 @@ class DeployOperation(InstanceOperation):
import json import json
logger.info('create ci image') logger.info('create ci image')
disk = Disk.create_ci_disk(meta_data=self.instance.get_meta_data, disk = Disk.create_ci_disk(meta_data=self.instance.get_meta_data,
user_data=self.instance.get_user_data) user_data=self.instance.get_user_data,
network_data=self.instance.get_network_config)
disk.full_clean() disk.full_clean()
disk.save() disk.save()
self.instance.disks.add(disk) self.instance.disks.add(disk)
return create_readable(ugettext_noop( return create_readable(ugettext_noop(
" ---- META-DATA ----\n%(meta_data)s\n\n ---- USER-DATA ----\n%(user_data)s"), " ---- META-DATA ----\n%(meta_data)s\n\n ---- NETWORK-CONFIG ----\n%(network_data)s\n\n ---- USER-DATA ----\n%(user_data)s"),
meta_data=self.instance.get_meta_data, meta_data=self.instance.get_meta_data,
user_data=self.instance.get_user_data) user_data=self.instance.get_user_data,
network_data=self.instance.get_network_config)
@register_operation @register_operation
class DeployDisksOperation(SubOperationMixin, InstanceOperation): class DeployDisksOperation(SubOperationMixin, InstanceOperation):
...@@ -616,6 +618,7 @@ class DestroyOperation(InstanceOperation): ...@@ -616,6 +618,7 @@ class DestroyOperation(InstanceOperation):
self.instance.destroyed_at = timezone.now() self.instance.destroyed_at = timezone.now()
self.instance.save() self.instance.save()
logger.info('destroy vm operation ' + str(system))
if system: if system:
self.instance.callhookurl() self.instance.callhookurl()
...@@ -1029,6 +1032,7 @@ class SleepOperation(InstanceOperation): ...@@ -1029,6 +1032,7 @@ class SleepOperation(InstanceOperation):
self.instance.shutdown_net() self.instance.shutdown_net()
self.instance._suspend_vm(parent_activity=activity) self.instance._suspend_vm(parent_activity=activity)
self.instance.yield_node() self.instance.yield_node()
logger.info('sleep vm operation ' + str(system))
if system: if system:
self.instance.callhookurl() self.instance.callhookurl()
...@@ -1502,7 +1506,7 @@ class CIDataChangeOperation(InstanceOperation): ...@@ -1502,7 +1506,7 @@ class CIDataChangeOperation(InstanceOperation):
accept_states = ('STOPPED', 'PENDING') accept_states = ('STOPPED', 'PENDING')
def _operation(self, user, activity, def _operation(self, user, activity,
ci_meta_data, ci_user_data, ci_meta_data, ci_user_data, ci_network_config,
with_shutdown=False, task=None): with_shutdown=False, task=None):
logger.debug('save cloud-init: %s \n %s', ci_meta_data, ci_user_data) logger.debug('save cloud-init: %s \n %s', ci_meta_data, ci_user_data)
if self.instance.status == 'RUNNING' and not with_shutdown: if self.instance.status == 'RUNNING' and not with_shutdown:
...@@ -1517,6 +1521,7 @@ class CIDataChangeOperation(InstanceOperation): ...@@ -1517,6 +1521,7 @@ class CIDataChangeOperation(InstanceOperation):
self.instance.ci_meta_data = ci_meta_data self.instance.ci_meta_data = ci_meta_data
self.instance.ci_user_data = ci_user_data self.instance.ci_user_data = ci_user_data
self.instance.ci_network_config = ci_network_config
self.instance.full_clean() self.instance.full_clean()
self.instance.save() self.instance.save()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment