Commit c7ef78f0 by Szeberényi Imre

Merge branch 'webhook-iac' into 'master'

Webhook iac

See merge request !19
parents 2ca1821d cc827f1f
......@@ -60,7 +60,7 @@ from vm.models import (
InstanceTemplate, Lease, InterfaceTemplate, Node, Trait, Instance
)
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]))
for l in LANGUAGES)
......@@ -1540,16 +1540,20 @@ class CIDataForm(forms.ModelForm):
ci_user_data = forms.CharField(
widget=forms.Textarea(attrs={'rows': 12, 'style' : 'display:none;'}),
required=False)
ci_network_config = forms.CharField(
widget=forms.Textarea(attrs={'rows': 5, 'style' : 'display:none;'}),
required=False)
def clean(self):
data = self.cleaned_data
meta_data_validator(self.instance, data['ci_meta_data'])
user_data_validator(self.instance, data['ci_user_data'])
network_data_validator(self.instance, data['ci_network_config'])
return super().clean()
class Meta:
model = Instance
fields = ('ci_meta_data', 'ci_user_data',)
fields = ('ci_meta_data', 'ci_user_data', 'ci_network_config',)
class GroupPermissionForm(forms.ModelForm):
......
......@@ -5,6 +5,7 @@ from django.contrib.auth.models import Group, User
from vm.models import Instance, InstanceTemplate, Lease, Interface, Node, InstanceActivity
from firewall.models import Vlan, Rule
from storage.models import Disk, StorageActivity
from vm.models.common import Variable
class RuleSerializer(serializers.ModelSerializer):
class Meta:
......@@ -50,7 +51,7 @@ class InstanceTemplateSerializer(serializers.ModelSerializer):
class Meta:
model = InstanceTemplate
fields = [ 'id', 'name', 'description', 'parent', 'owner', 'access_method', 'boot_menu',
'lease', 'raw_data', 'cloud_init', 'ci_meta_data', 'ci_user_data', 'system',
'lease', 'raw_data', 'cloud_init', 'ci_network_config', 'ci_meta_data', 'ci_user_data', 'system',
'has_agent', 'num_cores', 'ram_size', 'max_ram_size', 'arch', 'priority', 'disks']
......@@ -60,6 +61,12 @@ class LeaseSerializer(serializers.ModelSerializer):
fields = [ 'id', 'name', 'suspend_interval_seconds', 'delete_interval_seconds']
class VariableSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Variable
fields = [ 'id', 'key', 'value', 'url']
class DiskSerializer(serializers.ModelSerializer):
class Meta:
model = Disk
......@@ -71,6 +78,7 @@ class InstanceSerializer(serializers.ModelSerializer):
ipv4addr = serializers.SerializerMethodField('get_ipv4')
ipv6addr = serializers.SerializerMethodField('get_ipv6')
vlans = serializers.SerializerMethodField('get_vlans')
macaddr = serializers.SerializerMethodField('get_mac')
#interfaces = serializers.SerializerMethodField('get_interfaces')
def get_ipv4(self, i):
......@@ -79,6 +87,9 @@ class InstanceSerializer(serializers.ModelSerializer):
def get_ipv6(self, i):
return str(i.ipv6)
def get_mac(self, i):
return str(i.mac).lower()
def get_vlans(self, i):
return list(net.vlan.id for net in i.interface_set.all() if net.host)
......@@ -87,13 +98,19 @@ class InstanceSerializer(serializers.ModelSerializer):
class Meta:
model = Instance
fields = ['id', 'name', 'description', 'status', 'owner', 'access_method', 'boot_menu', 'pw', 'is_base',
'lease', 'raw_data', 'cloud_init', 'ci_meta_data', 'ci_user_data', 'system', 'req_traits', 'interface_set',
'has_agent', 'num_cores', 'ram_size', 'max_ram_size', 'arch', 'priority', 'disks', 'node', 'ipv4addr', 'ipv6addr', 'vlans']
fields = ['id', 'name', 'description', 'status', 'owner', 'access_method', 'boot_menu', 'pw', 'is_base', 'macaddr',
'lease', 'raw_data', 'cloud_init', 'ci_meta_data', 'ci_user_data', 'ci_network_config', 'system', 'req_traits', 'interface_set',
'has_agent', 'num_cores', 'ram_size', 'max_ram_size', 'arch', 'priority', 'disks', 'node', 'ipv4addr', 'ipv6addr', 'vlans',
'hookurl']
extra_kwargs = {
'disks': {'required': False, 'allow_empty': True,},
'req_traits': {'required': False, 'allow_empty': True,},
'interface_set': {'required': False, 'allow_empty': True,}
'interface_set': {'required': False, 'allow_empty': True,},
'hookurl': {'required': False },
'ci_network_config': {'required': False, },
'ci_user_data': {'required': False, },
'ci_meta_data': {'required': False, },
'raw_data': {'required': False, },
}
......
......@@ -1374,3 +1374,8 @@ textarea[name="new_members"] {
width: 95%;
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"] {
position: absolute;
width: 95%;
height: 350px;
}
#ace-network-data {
position: absolute;
width: 95%;
height: 150px;
}
\ No newline at end of file
var Websock_native; // not sure
$(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', {
mode: "ace/mode/yaml", useWorker: false,
selectionStyle: "text" });
......@@ -18,12 +18,23 @@ $(function() {
user_data.getSession().on("change", function () {
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.setUseSoftTabs(true);
user_data.session.setTabSize(4);
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-user-data').style.fontSize='14px';
document.getElementById('ace-network-data').style.fontSize='14px';
/* */
$('#vm-details-cidata-save').click(function(e) {
$.ajax({
......
......@@ -34,6 +34,7 @@
</div>
{{ form.cloud_init|as_crispy_field }}
{{ form.ci_meta_data|as_crispy_field }}
{{ form.ci_network_config|as_crispy_field }}
{{ form.ci_user_data|as_crispy_field }}
</fieldset>
......
......@@ -59,6 +59,7 @@
</div>
{{ form.cloud_init|as_crispy_field }}
{{ form.ci_meta_data|as_crispy_field }}
{{ form.ci_network_config|as_crispy_field }}
{{ form.ci_user_data|as_crispy_field }}
</fieldset>
<fieldset>
......
......@@ -16,6 +16,9 @@
{{ ci_data.ci_meta_data|as_crispy_field }}
<div id="ace-meta-data" style="border: 1px solid #ccc;"></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 }}
<div id="ace-user-data" style="border: 1px solid #ccc;"></div>
<div style="height: 360px;"></div>
......
......@@ -68,6 +68,7 @@ from .views import (
EnableTwoFactorView, DisableTwoFactorView,
AclUserGroupAutocomplete, AclUserAutocomplete,
RescheduleView, GroupImportView, GroupExportView,
VariableREST, GetVariableREST,
)
from .views.node import node_ops, NodeREST, GetNodeREST
from .views.vm import vm_ops, vm_mass_ops
......@@ -83,6 +84,8 @@ urlpatterns = [
path('acpi/group/', GroupREST.as_view()),
path('acpi/group/<int:pk>/', GetGroupREST.as_view()),
path('acpi/vm/', InstanceREST.as_view()),
path('acpi/var/', VariableREST.as_view()),
path('acpi/var/<int:pk>/', GetVariableREST.as_view(), name='variable-detail'),
path('acpi/node/', NodeREST.as_view()),
path('acpi/node/<int:pk>/', GetNodeREST.as_view()),
path('acpi/vm/<int:pk>/', GetInstanceREST.as_view()),
......
......@@ -90,6 +90,23 @@ def user_data_validator(instance, value):
except jinja2.exceptions.TemplateError as exc:
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):
"""Validate value as a connect command template.
......
......@@ -18,6 +18,7 @@
import json
import logging
from multiprocessing import context
import re
from collections import OrderedDict
from urllib.parse import urljoin
......@@ -56,9 +57,10 @@ from rest_framework.views import APIView
from rest_framework.parsers import JSONParser
from rest_framework.authentication import TokenAuthentication, BasicAuthentication
from rest_framework.permissions import IsAdminUser
from vm.models.common import Variable
from storage.models import StorageActivity
from dashboard.serializers import InstanceActivitySerializer, StorageActivitySerializer
from dashboard.serializers import InstanceActivitySerializer, StorageActivitySerializer, VariableSerializer
from common.models import HumanReadableException, HumanReadableObject
from ..models import GroupProfile, Profile
......@@ -107,6 +109,40 @@ class GetStorageActivityREST(APIView):
return JsonResponse(serializer.data, safe=False)
class VariableREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, format=None):
if request.query_params.get('key'):
try:
template = Variable.objects.filter(key__istartswith=request.query_params.get('key')).get()
serializer = VariableSerializer(template, many=False, context={'request': request})
return JsonResponse(serializer.data, safe=False)
except:
return JsonResponse({}, status=404)
templates = Variable.objects.all()
serializer = VariableSerializer(templates, many=True, context={'request': request})
return JsonResponse(serializer.data, safe=False)
def post(self, request, format=None):
data = JSONParser().parse(request)
serializer = VariableSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
class GetVariableREST(APIView):
authentication_classes = [TokenAuthentication,BasicAuthentication]
permission_classes = [IsAdminUser]
def get(self, request, pk, format=None):
act = Variable.objects.get(pk=pk)
serializer = VariableSerializer(act, many=False, context={'request': request})
return JsonResponse(serializer.data, safe=False)
class RedirectToLoginMixin(AccessMixin):
redirect_exception_classes = (PermissionDenied, )
......
......@@ -448,14 +448,14 @@ class Disk(TimeStampedModel):
return result
@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('type', 'raw-ro')
params.setdefault('size', None)
disk = cls.__create(params=params, user=user)
queue_name = disk.get_remote_queue_name('storage', priority="fast")
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
).get(timeout=15)
disk.size = result['size']
......
......@@ -34,7 +34,7 @@ def create(disk_desc):
@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
......
......@@ -17,6 +17,8 @@
from django.contrib import admin
from .models.common import Variable
from .models import (Instance, InstanceActivity, InstanceTemplate, Interface,
InterfaceTemplate, Lease, NamedBaseResourceConfig, Node,
NodeActivity, Trait)
......@@ -36,3 +38,4 @@ admin.site.register(NamedBaseResourceConfig)
admin.site.register(Node)
admin.site.register(NodeActivity)
admin.site.register(Trait)
admin.site.register(Variable)
# Generated by Django 3.2.3 on 2022-10-04 09:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('vm', '0010_auto_20220914_1532'),
]
operations = [
migrations.AddField(
model_name='instance',
name='hookurl',
field=models.CharField(blank=True, help_text='If expiration due, call remote url.', max_length=250, verbose_name='hookurl'),
),
]
# 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'),
),
]
# Generated by Django 3.2.3 on 2022-10-10 14:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('vm', '0013_auto_20221006_1509'),
]
operations = [
migrations.CreateModel(
name='Variable',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('key', models.CharField(max_length=100, unique=True, verbose_name='variable name')),
('value', models.CharField(max_length=150, verbose_name='variable value')),
],
),
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: {{ net.mac }}\n addresses: \n - {{ net.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='instance',
name='ci_user_data',
field=models.TextField(blank=True, default="#cloud-config\n\nssh_pwauth: 1\n\nusers:\n - name: {{ sysuser }} \n sudo: ['ALL=(ALL) NOPASSWD:ALL']\n groups: sudo\n shell: /bin/bash\n chpasswd: { expire: False }\n lock-passwd: false\nchpasswd:\n list: |\n {{ sysuser }}:{{ password }}\n expire: False", help_text='When cloud-init is active, set user-data (YAML format)', verbose_name='CI User 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: {{ net.mac }}\n addresses: \n - {{ net.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_user_data',
field=models.TextField(blank=True, default="#cloud-config\n\nssh_pwauth: 1\n\nusers:\n - name: {{ sysuser }} \n sudo: ['ALL=(ALL) NOPASSWD:ALL']\n groups: sudo\n shell: /bin/bash\n chpasswd: { expire: False }\n lock-passwd: false\nchpasswd:\n list: |\n {{ sysuser }}:{{ password }}\n expire: False", help_text='When cloud-init is active, set user-data (YAML format)', verbose_name='CI User Data'),
),
]
......@@ -33,6 +33,14 @@ ARCHITECTURES = (('x86_64', 'x86-64 (64 bit)'),
('i686', 'x86 (32 bit)'))
class Variable(Model):
key = CharField(verbose_name='variable name', unique=True, max_length=100)
value = CharField(verbose_name='variable value', max_length=150)
def __str__(self):
return self.key
class BaseResourceConfigModel(Model):
"""Abstract base for models with base resource configuration parameters.
......
......@@ -23,6 +23,7 @@ from functools import partial
from importlib import import_module
from ipaddress import ip_interface
from logging import getLogger
from urllib import request
from warnings import warn
from xml.dom.minidom import Text
......@@ -60,7 +61,7 @@ from common.models import (
from common.operations import OperatedMixin
from ..tasks import agent_tasks
from .activity import (ActivityInProgressError, InstanceActivity)
from .common import BaseResourceConfigModel, Lease
from .common import BaseResourceConfigModel, Lease, Variable
from .network import Interface
from .node import Node, Trait
......@@ -87,15 +88,34 @@ platform: circle3
CI_USER_DATA_DEF = """
#cloud-config
ssh_pwauth: 1
users:
- name: {{ sysuser }}
sudo: ['ALL=(ALL) NOPASSWD:ALL']
groups: sudo
shell: /bin/bash
ssh_pwauth: True
chpasswd: { expire: 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: {{ net.mac }}
addresses:
- {{ net.ipv4 }}/24
gateway4: ipv4
nameservers:
addresses:
- 8.8.8.8
""".strip()
try:
......@@ -150,6 +170,8 @@ class VirtualMachineDescModel(BaseResourceConfigModel):
'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=_(
'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,
help_text=_("A set of traits required for a "
"node to declare to be suitable "
......@@ -295,13 +317,13 @@ class NetTemplate:
self.ipv4 = str(net.host.ipv4)
self.ipv6 = str(net.host.ipv6)
self.name = str(net.vlan.name)
self.mac = str(net.host.mac)
def __init__(self, instance):
self.vlans = list(NetTemplate.Host(net) for net in instance.interface_set.all() if net.host)
self.ipv4 = str(instance.ipv4)
self.ipv6 = str(instance.ipv6)
self.mac = str(instance.mac)
class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
......@@ -325,6 +347,8 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
)
name = CharField(blank=True, max_length=100, verbose_name=_('name'),
help_text=_("Human readable name of instance."))
hookurl = CharField(blank=True, max_length=250, verbose_name=_('hookurl'),
help_text=_("If expiration due, call remote url."))
description = TextField(blank=True, verbose_name=_('description'))
template = ForeignKey(InstanceTemplate, blank=True, null=True,
related_name='instance_set', on_delete=SET_NULL,
......@@ -421,6 +445,10 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
return self.status
def get_ci_data_dict(self):
list = Variable.objects.all()
vars = { }
for var in list:
vars[var.key] = var.value
datas = {
"sysuser": "cloud",
"hostname": self.short_hostname,
......@@ -430,6 +458,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
"acl": AclTemplate(self),
"ssh": SSHKeyTemplate(self),
"ci": CITemplate(),
"variables": vars,
}
return datas
......@@ -447,12 +476,29 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
template = env.from_string(data)
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):
ci_datas = self.get_ci_data_dict()
template = env.from_string(data)
data = template.render(ci_datas)
yaml.dump(yaml.load(data, Loader=yaml.Loader))
return True
def callhookurl(self):
import requests as req
if self.hookurl:
logger.info("notify remote host (callhookurl): " + str(self.hookurl))
try:
req.post(url=str(self.hookurl))
except:
logger.info("Error when call hookurl: " + str(self.hookurl))
def _update_status(self):
"""Set the proper status of the instance to Instance.status.
......
......@@ -539,14 +539,16 @@ class DeployOperation(InstanceOperation):
import json
logger.info('create ci image')
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.save()
self.instance.disks.add(disk)
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,
user_data=self.instance.get_user_data)
user_data=self.instance.get_user_data,
network_data=self.instance.get_network_config)
@register_operation
class DeployDisksOperation(SubOperationMixin, InstanceOperation):
......@@ -616,6 +618,9 @@ class DestroyOperation(InstanceOperation):
self.instance.destroyed_at = timezone.now()
self.instance.save()
logger.info('destroy vm operation ' + str(system))
if system:
self.instance.callhookurl()
@register_operation
class DeleteVmOperation(SubOperationMixin, RemoteInstanceOperation):
......@@ -1027,6 +1032,9 @@ class SleepOperation(InstanceOperation):
self.instance.shutdown_net()
self.instance._suspend_vm(parent_activity=activity)
self.instance.yield_node()
logger.info('sleep vm operation ' + str(system))
if system:
self.instance.callhookurl()
@register_operation
class SuspendVmOperation(SubOperationMixin, RemoteInstanceOperation):
......@@ -1498,7 +1506,7 @@ class CIDataChangeOperation(InstanceOperation):
accept_states = ('STOPPED', 'PENDING')
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):
logger.debug('save cloud-init: %s \n %s', ci_meta_data, ci_user_data)
if self.instance.status == 'RUNNING' and not with_shutdown:
......@@ -1513,6 +1521,7 @@ class CIDataChangeOperation(InstanceOperation):
self.instance.ci_meta_data = ci_meta_data
self.instance.ci_user_data = ci_user_data
self.instance.ci_network_config = ci_network_config
self.instance.full_clean()
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