Saving running environment

This commit is contained in:
Karsten Jakobsen 2021-09-03 08:42:15 +02:00
parent 89ba1aad1e
commit 37226484e2
4 changed files with 1698 additions and 469 deletions

516
scripts/bulk.py.new Normal file
View file

@ -0,0 +1,516 @@
import netaddr
import csv
from dcim.choices import InterfaceTypeChoices, InterfaceModeChoices
from dcim.models import Platform, DeviceRole, Site
from ipam.models import IPAddress, VRF, Interface, Prefix, VLAN
from tenancy.models import Tenant
from virtualization.models import VirtualMachine, Cluster
from virtualization.choices import VirtualMachineStatusChoices
from extras.scripts import Script, TextVar, ChoiceVar, ObjectVar
from extras.models import Tag
from utilities.forms import APISelect
class VM:
status: str
tenant: Tenant
cluster: Cluster
site: Site
csv_ip_address: str
ip_address: IPAddress
DEFAULT_TAGS = ['ansible', 'zero_day']
DEFAULT_DOMAIN_PRIVATE = 'privatedns.zone'
DEFAULT_DOMAIN_PUBLIC = 'publicdns.zone'
def __init__(self, status, tenant, cluster, datazone, env, platform, role, backup, vcpus, memory, disk, ip_address, hostname, extra_tags):
# IP address can first be created after vm
self.csv_ip_address = ip_address
self.set_cluster(cluster)
self.set_status(status)
self.set_tenant(tenant)
self.set_datazone(datazone)
self.set_env(env)
self.set_platform(platform)
self.set_role(role)
self.set_backup_tag(backup)
self.set_vcpus(vcpus)
self.set_memory(memory)
self.set_disk(disk)
self.set_hostname(hostname)
self.set_vlan(None)
self.set_extra_tags(extra_tags)
def set_extra_tags(self, extra_tags):
try:
if extra_tags is None:
self.extra_tags = extra_tags
else:
self.extra_tags = extra_tags.split(',')
except Exception as e:
raise Exception("Extra tags - {0}".format(e))
def set_vlan(self, vlan):
try:
self.vlan = VLAN.objects.get(
vid=vlan,
site=self.site
)
except Exception:
self.vlan = None
def generate_hostname(self):
# I now proclaim this VM, First of its Name, Queen of the Andals and the First Men, Protector of the Seven Kingdoms
vm_index = "001"
vm_search_for = "{0}-{1}-{2}-".format(self.site.slug, self.env.name.split('_')[1], self.role.name.split(':')[0])
vms = VirtualMachine.objects.filter(
name__startswith=vm_search_for
)
if len(vms) > 0:
# Get last of its kind
last_vm_index = int(vms[len(vms) - 1].name.split('-')[3]) + 1
if last_vm_index < 10:
vm_index = '00' + str(last_vm_index)
elif last_vm_index < 100:
vm_index = '0' + str(last_vm_index)
else:
vm_index = str(last_vm_index)
return "{0}{1}".format(vm_search_for, vm_index)
def get_vrf(self):
return VRF.objects.get(
name="global"
)
def get_fqdn(self):
return "{}.{}".format(self.hostname, self.DEFAULT_DOMAIN_PRIVATE if netaddr.IPNetwork(self.ip_address.address).is_private() is True else self.DEFAULT_DOMAIN_PUBLIC)
def set_ip_address(self, vm):
try:
if not isinstance(self.vlan, VLAN):
ip_check = IPAddress.objects.filter(address=self.csv_ip_address)
if len(ip_check) > 0:
raise Exception(str(ip_check[0].address) + ' is already assigned to ' + str(ip_check[0].interface.name))
self.ip_address = IPAddress(
address=self.csv_ip_address,
vrf=self.get_vrf(),
tenant=self.tenant,
family=4,
)
else:
# Auto assign IPs from vlan
prefix = Prefix.objects.get(
vlan=self.vlan,
site=vm.site
)
prefix.is_pool = True
# Save as pool
prefix.save()
ip_address = prefix.get_first_available_ip()
self.ip_address = IPAddress(
address=ip_address,
vrf=self.get_vrf(),
tenant=self.tenant,
family=4,
)
self.ip_address.dns_name = self.get_fqdn()
self.ip_address.save()
except Exception as e:
self.ip_address = None
raise Exception("IP address - {0}".format(e))
def get_vlan(self):
return False
def set_hostname(self, hostname):
try:
self.hostname = hostname if hostname is not None else self.generate_hostname()
except Exception as e:
raise Exception("Hostname - {0}".format(e))
def set_disk(self, disk):
try:
self.disk = disk
except Exception as e:
raise Exception("Disk - {0}".format(e))
def set_memory(self, memory):
try:
self.memory = memory
except Exception as e:
raise Exception("Memory - {0}".format(e))
def set_vcpus(self, vcpus):
try:
self.vcpus = vcpus
except Exception as e:
raise Exception("vcpus - {0}".format(e))
def set_backup_tag(self, backup):
try:
if isinstance(backup, Tag):
self.backup = backup
else:
self.backup = Tag.objects.filter(name="{0}".format(backup))[0]
except Exception as e:
raise Exception("Tag backup {0} does not exist, {1}".format(backup, e))
def set_site(self, site):
try:
if isinstance(site, Site):
self.site = site
else:
raise Exception("site is not of instance 'Site'")
except Exception as e:
raise Exception('Site does not exist - ' + str(e))
def set_role(self, role):
try:
self.role = DeviceRole.objects.get(
name=role
)
except Exception as e:
raise Exception('Role does not exist - ' + str(e))
def set_platform(self, platform):
try:
if isinstance(platform, Platform):
self.platform = platform
else:
self.platform = Platform.objects.get(
name=platform
)
except Exception as e:
raise Exception("Platform does not exist {0}".format(e))
def set_env(self, env):
try:
if isinstance(env, Tag):
self.env = env
else:
self.env = Tag.objects.get(name="env_{0}".format(env))
except Exception as e:
raise Exception("Tag env does not exist - {0}".format(e, env))
def set_datazone(self, datazone):
try:
self.datazone = Tag.objects.get(name="datazone_{0}".format(datazone))
except Exception as e:
raise Exception("Tag datazone does not exist - {0}".format(e))
def set_cluster(self, cluster):
try:
if isinstance(cluster, Cluster):
self.cluster = cluster
else:
self.cluster = Cluster.objects.get(
name=cluster
)
self.set_site(self.cluster.site)
except Exception as e:
raise Exception("Cluster does not exist {0}".format(e))
def set_tenant(self, tenant):
try:
if isinstance(tenant, Tenant):
self.tenant = tenant
else:
self.tenant = Tenant.objects.get(
slug=tenant
)
except Exception as e:
raise Exception("Tenant does not exist {0}".format(e))
def set_status(self, status):
try:
self.status = VirtualMachineStatusChoices.STATUS_STAGED if status == 'staged' else VirtualMachineStatusChoices.STATUS_PLANNED
except Exception as e:
raise Exception("Status does not exist {0}".format(e))
def get_ip_address(self):
return self.ip_address
def __create_ip_address(self, vm):
self.set_ip_address(vm)
vm.primary_ip4 = self.get_ip_address()
vm.save()
def __create_tags(self, vm: VirtualMachine):
vm.tags.add(self.datazone)
vm.tags.add(self.env)
vm.tags.add(self.backup)
for tag in self.DEFAULT_TAGS:
vm.tags.add(tag)
if self.extra_tags is not None:
for tag in self.extra_tags:
vm.tags.add(tag)
vm.save()
self.set_tags(vm.tags)
def set_tags(self, tags):
self.tags = tags
def get_tags(self):
list = []
for tag in self.tags.all():
list.append(tag.name)
return list
def __create_vm(self):
vm = VirtualMachine(
status=self.status,
cluster=self.cluster,
platform=self.platform,
role=self.role,
tenant=self.tenant,
name=self.hostname,
disk=self.disk,
memory=self.memory,
vcpus=self.vcpus,
)
vm.save()
return vm
def __create_interface(self, vm: VirtualMachine):
"""
Setup interface and add IP address
"""
try:
# Get net address tools
ip = netaddr.IPNetwork(vm.primary_ip4.address)
prefix_search = str(ip.network) + '/' + str(ip.prefixlen)
prefix = Prefix.objects.get(
prefix=prefix_search,
is_pool=True,
site=self.site,
)
interfaces = vm.get_config_context().get('interfaces')
interface = Interface(
name=interfaces['nic0']['name'],
mtu=interfaces['nic0']['mtu'],
virtual_machine=vm,
type=InterfaceTypeChoices.TYPE_VIRTUAL
)
# If we need anything other than Access, here is were to change it
if interfaces['nic0']['mode'] == "Access":
interface.mode = InterfaceModeChoices.MODE_ACCESS
interface.untagged_vlan = prefix.vlan
interface.save()
self.ip_address.interface = interface
self.ip_address.save()
except Exception as e:
raise Exception("Error while creating interface - {}".format(e))
return True
def create(self):
try:
vm = self.__create_vm()
self.__create_ip_address(vm)
self.__create_tags(vm)
self.__create_interface(vm)
except Exception as e:
raise e
return True
class BulkDeployVM(Script):
"""
Example CSV full:
status,tenant,cluster,datazone,env,platform,role,backup,vcpus,memory,disk,hostname,ip_address,extra_tags
staged,patientsky-hosting,odn1,1,vlb,base:v1.0.0-coreos,redirtp:v0.2.0,backup_nobackup,1,1024,10,odn1-vlb-redirtp-001,10.50.61.10/24,"voip,test_tag,cluster_id_voip_galera_001"
staged,patientsky-hosting,odn1,1,vlb,base:v1.0.0-coreos,consul:v1.0.1,backup_general_1,2,2048,20,odn1-vlb-consul-001,10.50.61.11/24,"voip,test_tag,cluster_id_voip_galera_002"
staged,patientsky-hosting,odn1,1,vlb,base:v1.0.0-coreos,rediast:v0.2.0,backup_general_4,4,4096,30,odn1-vlb-rediast-001,10.50.61.12/24,"voip,test_tag,cluster_id_voip_galera_003"
Example CSV minimal (If all defaults are set):
vcpus,memory,disk,ip_address,extra_tags
1,1024,10,10.50.61.10/24,"voip,test_tag,cluster_id_voip_galera_001"
2,2048,20,10.50.61.11/24,"voip,test_tag,cluster_id_voip_galera_002"
4,4096,30,10.50.61.12/24,"voip,test_tag,cluster_id_voip_galera_003"
** Required Params **
Param: cluster - vSphere cluster name
Param: env - Adds 'env_xxx' tag
Param: platform - VM Platform e.g base:v1.0.0-coreos
Param: backup - Adds backup tag
Param: vcpus - Virtual CPUs (hot add)
Param: memory - Virtual memory (hot add)
Param: disk - Disk2 size
Param: hostname - VM hostname (Optional if 'role' is set)
Param: role - VM Device role
Param: ip_address - VM IP address
** Optional Params **
Param: status - VM status (default 'staged')
Param: tenant - Netbox tenant (default slug:'patientsky-hosting')
Param: datazone - Adds 'datazone_x' tag (default 'rr')
Param: extra_tags - Adds extra tags to VM
"""
DEFAULT_CSV_FIELDS = "env,platform,role,backup,vcpus,memory,disk,hostname,ip_address,extra_tags"
datazone_rr: bool = True
class Meta:
name = "Bulk deploy new VMs"
description = "Deploy new virtual machines from existing platforms"
fields = ['vms', 'default_status', 'default_tenant', 'default_datazone', 'default_backup', 'default_role']
field_order = ['vms', 'default_status', 'default_tenant', 'default_datazone', 'default_backup', 'default_role']
commit_default = False
vms = TextVar(
label="Import CSV",
description="CSV data",
required=True,
default=DEFAULT_CSV_FIELDS
)
default_status = ChoiceVar(
label="Default Status",
description="Default CSV field `status` if none given",
required=False,
choices=(
(VirtualMachineStatusChoices.STATUS_STAGED, 'Staged (Deploy now)'),
(VirtualMachineStatusChoices.STATUS_PLANNED, 'Planned (Save for later)')
)
)
default_tenant = ObjectVar(
label="Default Tenant",
default=1,
required=False,
description="Default CSV field `tenant` if none given",
queryset=Tenant.objects.all()
)
default_datazone = ChoiceVar(
label="Default Datazone",
description="Default CSV field `datazone` if none given",
default="rr",
required=False,
choices=(
('rr', 'Round robin (1,2)'),
('1', '1'),
('2', '2')
)
)
default_cluster = ObjectVar(
label="Default Cluster",
description="Default CSV field `cluster` if none given",
required=False,
queryset=Cluster.objects.all()
)
default_env = ObjectVar(
label="Default Environment",
description="Default CSV field `env` if none given",
default="env_inf",
required=False,
queryset=Tag.objects.filter(
name__startswith='env_',
).order_by('name'),
)
default_platform = ObjectVar(
label="Default Platform",
description="Default CSV field `platform` if none given",
required=False,
widget=APISelect(
api_url='/api/dcim/platforms/?name__ic=_',
display_field='display_name',
),
queryset=Platform.objects.all().order_by('name')
)
default_role = ObjectVar(
label="Default Role",
description="Default CSV field `role` if none given",
default=None,
required=False,
widget=APISelect(
api_url='/api/dcim/device-roles/?vm_role=true',
display_field='display_name',
),
queryset=DeviceRole.objects.filter(
vm_role=True
).order_by('name')
)
default_backup = ObjectVar(
label="Default Backup",
description="Default CSV field `backup` if none given",
required=False,
queryset=Tag.objects.filter(
name__startswith='backup_',
).order_by('name'),
)
def get_vm_data(self):
return self.vm_data
def set_csv_data(self, vms):
self.csv_raw_data = csv.DictReader(vms, delimiter=',')
def get_csv_raw_data(self):
return self.csv_raw_data
def set(self, data):
self.set_csv_data(data['vms'].splitlines())
def get_datazone(self, datazone):
if datazone == 'rr':
datazone = 1 if self.datazone_rr else 2
self.datazone_rr = not self.datazone_rr
return datazone
def run(self, data):
# Set data from raw csv
self.set(data)
line = 1
for raw_vm in self.get_csv_raw_data():
try:
vm = VM(
status=raw_vm.get('status') if raw_vm.get('status') is not None else data['default_status'],
tenant=raw_vm.get('tenant') if raw_vm.get('tenant') is not None else data['default_tenant'],
datazone=raw_vm.get('datazone') if raw_vm.get('datazone') is not None else self.get_datazone(data['default_datazone']),
cluster=raw_vm.get('cluster') if raw_vm.get('cluster') is not None else data['default_cluster'],
env=raw_vm.get('env') if raw_vm.get('env') is not None else data['default_env'],
platform=raw_vm.get('platform') if raw_vm.get('platform') is not None else data['default_platform'],
role=raw_vm.get('role') if raw_vm.get('role') is not None else data['default_role'],
backup=raw_vm.get('backup') if raw_vm.get('backup') is not None else data['default_backup'],
vcpus=raw_vm.get('vcpus'),
memory=raw_vm.get('memory'),
disk=raw_vm.get('disk'),
hostname=raw_vm.get('hostname'),
ip_address=raw_vm.get('ip_address'),
extra_tags=raw_vm.get('extra_tags'),
)
vm.create()
self.log_success(
"{} `{}` for `{}`, `{}`, in cluster `{}`, env `{}`, datazone `{}`, backup `{}`".
format(
vm.status.capitalize(),
vm.hostname,
vm.tenant,
vm.ip_address.address,
vm.cluster,
str(vm.env.name).split('_')[1],
vm.datazone,
vm.backup,
)
)
line += 1
except Exception as e:
self.log_failure("CSV line {}, Error while creating VM \n`{}`".format(line, e))
return data['vms']