Saving running environment
This commit is contained in:
parent
89ba1aad1e
commit
37226484e2
4 changed files with 1698 additions and 469 deletions
590
scripts/bulk.py
Normal file
590
scripts/bulk.py
Normal file
|
@ -0,0 +1,590 @@
|
||||||
|
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
|
||||||
|
comment: str
|
||||||
|
|
||||||
|
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, backup_offsite, 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_backup_offsite_tag(backup_offsite)
|
||||||
|
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)
|
||||||
|
self.set_comments()
|
||||||
|
|
||||||
|
def set_comments(self):
|
||||||
|
try:
|
||||||
|
extra_tags = ""
|
||||||
|
for tag in self.extra_tags:
|
||||||
|
extra_tags += tag + ","
|
||||||
|
|
||||||
|
if self.backup_offsite is not None:
|
||||||
|
self.comments = "status,tenant,cluster,datazone,env,platform,role,backup,backup_offsite,vcpus,memory,disk,hostname,ip_address,extra_tags\n"
|
||||||
|
self.comments += "{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},\"{14}\"".format(
|
||||||
|
self.status,
|
||||||
|
self.tenant.slug,
|
||||||
|
self.cluster,
|
||||||
|
self.datazone.name.split('datazone_')[1],
|
||||||
|
self.env.name.split('env_')[1],
|
||||||
|
self.platform,
|
||||||
|
self.role,
|
||||||
|
self.backup.name,
|
||||||
|
self.backup_offsite.name if self.backup_offsite is not None else "",
|
||||||
|
self.vcpus,
|
||||||
|
self.memory,
|
||||||
|
self.disk,
|
||||||
|
self.hostname,
|
||||||
|
self.csv_ip_address,
|
||||||
|
extra_tags[:-1],
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.comments = "status,tenant,cluster,datazone,env,platform,role,backup,vcpus,memory,disk,hostname,ip_address,extra_tags\n"
|
||||||
|
self.comments += "{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},\"{13}\"".format(
|
||||||
|
self.status,
|
||||||
|
self.tenant.slug,
|
||||||
|
self.cluster,
|
||||||
|
self.datazone.name.split('datazone_')[1],
|
||||||
|
self.env.name.split('env_')[1],
|
||||||
|
self.platform,
|
||||||
|
self.role,
|
||||||
|
self.backup.name,
|
||||||
|
self.vcpus,
|
||||||
|
self.memory,
|
||||||
|
self.disk,
|
||||||
|
self.hostname,
|
||||||
|
self.csv_ip_address,
|
||||||
|
extra_tags[:-1],
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception("Comments - {0}".format(e))
|
||||||
|
|
||||||
|
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:
|
||||||
|
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')
|
||||||
|
|
||||||
|
if not isinstance(self.vlan, VLAN):
|
||||||
|
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_backup_offsite_tag(self, backup_offsite):
|
||||||
|
try:
|
||||||
|
if isinstance(backup_offsite, Tag):
|
||||||
|
self.backup_offsite = backup_offsite
|
||||||
|
else:
|
||||||
|
self.backup_offsite = Tag.objects.filter(name="{0}".format(backup_offsite))[0]
|
||||||
|
except Exception:
|
||||||
|
self.backup_offsite = None
|
||||||
|
|
||||||
|
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)
|
||||||
|
if(self.backup_offsite is not None):
|
||||||
|
vm.tags.add(self.backup_offsite)
|
||||||
|
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,
|
||||||
|
comments=self.comments,
|
||||||
|
)
|
||||||
|
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: backup_offsite - Adds offsite 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 = "vcpus,memory,disk,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_backup_offsite', 'default_role']
|
||||||
|
field_order = ['vms', 'default_status', 'default_tenant', 'default_datazone', 'default_backup', 'default_backup_offsite', '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'),
|
||||||
|
)
|
||||||
|
|
||||||
|
default_backup_offsite = ObjectVar(
|
||||||
|
label="Default Offsite Backup",
|
||||||
|
description="Default CSV field `backup_offsite` if none given",
|
||||||
|
required=False,
|
||||||
|
queryset=Tag.objects.filter(
|
||||||
|
name__startswith='backup_off',
|
||||||
|
).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'],
|
||||||
|
backup_offsite=raw_vm.get('backup_offsite') if raw_vm.get('backup_offsite') is not None else data['default_backup_offsite'],
|
||||||
|
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']
|
592
scripts/bulk.py.backup
Normal file
592
scripts/bulk.py.backup
Normal file
|
@ -0,0 +1,592 @@
|
||||||
|
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:
|
||||||
|
|
||||||
|
__staged_hostnames: []
|
||||||
|
status: str
|
||||||
|
tenant: Tenant
|
||||||
|
cluster: Cluster
|
||||||
|
site: Site
|
||||||
|
csv_ip_address: str
|
||||||
|
ip_address: IPAddress
|
||||||
|
comment: str
|
||||||
|
|
||||||
|
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, backup_offsite, 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_backup_offsite_tag(backup_offsite)
|
||||||
|
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)
|
||||||
|
self.set_comments()
|
||||||
|
|
||||||
|
def set_comments(self):
|
||||||
|
try:
|
||||||
|
extra_tags = ""
|
||||||
|
for tag in self.extra_tags:
|
||||||
|
extra_tags += tag + ","
|
||||||
|
|
||||||
|
if self.backup_offsite is not None:
|
||||||
|
self.comments = "status,tenant,cluster,datazone,env,platform,role,backup,backup_offsite,vcpus,memory,disk,hostname,ip_address,extra_tags\n"
|
||||||
|
self.comments += "{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},\"{14}\"".format(
|
||||||
|
self.status,
|
||||||
|
self.tenant.slug,
|
||||||
|
self.cluster,
|
||||||
|
self.datazone.name.split('datazone_')[1],
|
||||||
|
self.env.name.split('env_')[1],
|
||||||
|
self.platform,
|
||||||
|
self.role,
|
||||||
|
self.backup.name.split('vsphere_tag_')[1],
|
||||||
|
self.backup_offsite.name.split('vsphere_tag_')[1] if self.backup_offsite is not None else "",
|
||||||
|
self.vcpus,
|
||||||
|
self.memory,
|
||||||
|
self.disk,
|
||||||
|
self.hostname,
|
||||||
|
self.csv_ip_address,
|
||||||
|
extra_tags[:-1],
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.comments = "status,tenant,cluster,datazone,env,platform,role,backup,vcpus,memory,disk,hostname,ip_address,extra_tags\n"
|
||||||
|
self.comments += "{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},\"{13}\"".format(
|
||||||
|
self.status,
|
||||||
|
self.tenant.slug,
|
||||||
|
self.cluster,
|
||||||
|
self.datazone.name.split('datazone_')[1],
|
||||||
|
self.env.name.split('env_')[1],
|
||||||
|
self.platform,
|
||||||
|
self.role,
|
||||||
|
self.backup.name.split('vsphere_tag_')[1],
|
||||||
|
self.vcpus,
|
||||||
|
self.memory,
|
||||||
|
self.disk,
|
||||||
|
self.hostname,
|
||||||
|
self.csv_ip_address,
|
||||||
|
extra_tags[:-1],
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception("Comments - {0}".format(e))
|
||||||
|
|
||||||
|
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)
|
||||||
|
# self.__staged_hostnames.append("{0}{1}".format(search_for, 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:
|
||||||
|
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')
|
||||||
|
|
||||||
|
if not isinstance(self.vlan, VLAN):
|
||||||
|
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="vsphere_tag_{0}".format(backup))[0]
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception("Tag backup {0} does not exist, {1}".format(backup, e))
|
||||||
|
|
||||||
|
def set_backup_offsite_tag(self, backup_offsite):
|
||||||
|
try:
|
||||||
|
if isinstance(backup_offsite, Tag):
|
||||||
|
self.backup_offsite = backup_offsite
|
||||||
|
else:
|
||||||
|
self.backup_offsite = Tag.objects.filter(name="vsphere_tag_{0}".format(backup_offsite))[0]
|
||||||
|
except Exception:
|
||||||
|
self.backup_offsite = None
|
||||||
|
|
||||||
|
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)
|
||||||
|
if(self.backup_offsite is not None):
|
||||||
|
vm.tags.add(self.backup_offsite)
|
||||||
|
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,
|
||||||
|
comments=self.comments,
|
||||||
|
)
|
||||||
|
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,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 'vsphere_tag_xxxx' tag
|
||||||
|
Param: backup_offsite - Adds 'vsphere_tag_xxxx' 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 = "vcpus,memory,disk,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_backup_offsite', 'default_role']
|
||||||
|
field_order = ['vms', 'default_status', 'default_tenant', 'default_datazone', 'default_backup', 'default_backup_offsite', '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='vsphere_tag_',
|
||||||
|
).order_by('name'),
|
||||||
|
)
|
||||||
|
|
||||||
|
default_backup_offsite = ObjectVar(
|
||||||
|
label="Default Offsite Backup",
|
||||||
|
description="Default CSV field `backup_offsite` if none given",
|
||||||
|
required=False,
|
||||||
|
queryset=Tag.objects.filter(
|
||||||
|
name__startswith='vsphere_tag_backup_off',
|
||||||
|
).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'],
|
||||||
|
backup_offsite=raw_vm.get('backup_offsite') if raw_vm.get('backup_offsite') is not None else data['default_backup_offsite'],
|
||||||
|
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']
|
||||||
|
|
516
scripts/bulk.py.new
Normal file
516
scripts/bulk.py.new
Normal 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']
|
||||||
|
|
|
@ -1,469 +0,0 @@
|
||||||
import netaddr
|
|
||||||
|
|
||||||
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, ObjectVar, ChoiceVar, TextVar, IntegerVar
|
|
||||||
from extras.models import Tag
|
|
||||||
from utilities.forms import APISelect
|
|
||||||
|
|
||||||
|
|
||||||
class DeployVM(Script):
|
|
||||||
|
|
||||||
env = ""
|
|
||||||
interfaces = []
|
|
||||||
time_zone = ""
|
|
||||||
dns_domain_private = ""
|
|
||||||
dns_domain_public = ""
|
|
||||||
dns_servers = []
|
|
||||||
ntp_servers = []
|
|
||||||
ssh_authorized_keys = []
|
|
||||||
ssh_port = ""
|
|
||||||
_default_interface = {}
|
|
||||||
tags = []
|
|
||||||
output = []
|
|
||||||
success_log = ""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
name = "Deploy new VMs"
|
|
||||||
description = "Deploy new virtual machines from existing platforms and roles using AWX"
|
|
||||||
fields = ['persist_disk', 'status', 'health_check', 'serial', 'tenant', 'cluster', 'env', 'untagged_vlan', 'backup', 'ip_addresses', 'vm_count', 'vcpus', 'memory', 'platform', 'role', 'disk', 'ssh_authorized_keys', 'hostnames']
|
|
||||||
field_order = ['status', 'tenant', 'cluster', 'env', 'platform', 'role', 'health_check', 'serial', 'persist_disk', 'backup', 'vm_count', 'vcpus', 'memory', 'disk', 'hostnames', 'untagged_vlan', 'ip_addresses', 'ssh_authorized_keys']
|
|
||||||
commit_default = False
|
|
||||||
|
|
||||||
health_check = ChoiceVar(
|
|
||||||
label="Health checks on deployment",
|
|
||||||
description="Deployment will fail if server does not pass Consul health checks",
|
|
||||||
required=True,
|
|
||||||
choices=(
|
|
||||||
('True', 'Yes'),
|
|
||||||
('False', 'No')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
serial = ChoiceVar(
|
|
||||||
label="Serial deployment",
|
|
||||||
description="VM will not be parallelized in deployment",
|
|
||||||
required=True,
|
|
||||||
choices=(
|
|
||||||
('False', 'No'),
|
|
||||||
('True', 'Yes'),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
persist_disk = ChoiceVar(
|
|
||||||
label="Persist volume on redeploy",
|
|
||||||
description="VM will persist disk2 in vSphere on redeploys",
|
|
||||||
required=True,
|
|
||||||
choices=(
|
|
||||||
('False', 'No'),
|
|
||||||
('True', 'Yes'),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
status = ChoiceVar(
|
|
||||||
label="VM Status",
|
|
||||||
description="Deploy VM now or later?",
|
|
||||||
required=True,
|
|
||||||
choices=(
|
|
||||||
(VirtualMachineStatusChoices.STATUS_STAGED, 'Staged (Deploy now)'),
|
|
||||||
(VirtualMachineStatusChoices.STATUS_PLANNED, 'Planned (Save for later)')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
tenant = ObjectVar(
|
|
||||||
default="patientsky-hosting",
|
|
||||||
description="Name of the tenant the VMs beloing to",
|
|
||||||
queryset=Tenant.objects.filter()
|
|
||||||
)
|
|
||||||
|
|
||||||
cluster = ObjectVar(
|
|
||||||
default="odn1",
|
|
||||||
description="Name of the vSphere cluster you are deploying to",
|
|
||||||
queryset=Cluster.objects.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
env = ChoiceVar(
|
|
||||||
label="Environment",
|
|
||||||
description="Environment to deploy VM",
|
|
||||||
default="vlb",
|
|
||||||
choices=(
|
|
||||||
('pno', 'pno'),
|
|
||||||
('inf', 'inf'),
|
|
||||||
('stg', 'stg'),
|
|
||||||
('dev', 'dev'),
|
|
||||||
('hem', 'hem'),
|
|
||||||
('hov', 'hov'),
|
|
||||||
('hpl', 'hpl'),
|
|
||||||
('mgt', 'mgt'),
|
|
||||||
('cse', 'cse'),
|
|
||||||
('qua', 'qua'),
|
|
||||||
('dmo', 'dmo'),
|
|
||||||
('vlb', 'vlb'),
|
|
||||||
('cmi', 'cmi')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
platform = ObjectVar(
|
|
||||||
description="Host OS to deploy",
|
|
||||||
queryset=Platform.objects.filter(
|
|
||||||
name__regex=r'^(base_.*)'
|
|
||||||
).order_by('name')
|
|
||||||
)
|
|
||||||
|
|
||||||
role = ObjectVar(
|
|
||||||
label="VM Role",
|
|
||||||
description="VM Role",
|
|
||||||
queryset=DeviceRole.objects.filter(
|
|
||||||
vm_role=True
|
|
||||||
).order_by('name')
|
|
||||||
)
|
|
||||||
|
|
||||||
backup = ChoiceVar(
|
|
||||||
label="Backup strategy",
|
|
||||||
description="The backup strategy deployed to this VM with Veeam",
|
|
||||||
required=True,
|
|
||||||
choices=(
|
|
||||||
('nobackup', 'Never'),
|
|
||||||
('backup_general_1', 'Daily'),
|
|
||||||
('backup_general_4', 'Monthly')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
ip_addresses = TextVar(
|
|
||||||
required=False,
|
|
||||||
label="IP Addresses",
|
|
||||||
description="List of IP addresses to create w. prefix e.g 192.168.0.10/24. If none given, hosts will be assigned IPs 'automagically'"
|
|
||||||
)
|
|
||||||
|
|
||||||
untagged_vlan = ObjectVar(
|
|
||||||
required=False,
|
|
||||||
label="VLAN",
|
|
||||||
widget=APISelect(api_url='/api/ipam/vlans/', display_field='display_name'),
|
|
||||||
queryset=VLAN.objects.all(),
|
|
||||||
description="Choose VLAN for IP-addresses",
|
|
||||||
)
|
|
||||||
|
|
||||||
hostnames = TextVar(
|
|
||||||
required=True,
|
|
||||||
label="Hostnames",
|
|
||||||
description="List of hostnames to create."
|
|
||||||
)
|
|
||||||
|
|
||||||
ssh_authorized_keys = TextVar(
|
|
||||||
label="SSH Authorized Keys",
|
|
||||||
required=False,
|
|
||||||
description="List of accepted SSH keys - defaults to site config context 'ssh_authorized_keys'"
|
|
||||||
)
|
|
||||||
|
|
||||||
vm_count = ChoiceVar(
|
|
||||||
label="Number of VMs",
|
|
||||||
description="Number of VMs to deploy",
|
|
||||||
choices=(
|
|
||||||
('1', '1'),
|
|
||||||
('2', '2'),
|
|
||||||
('3', '3'),
|
|
||||||
('4', '4'),
|
|
||||||
('5', '5'),
|
|
||||||
('6', '6'),
|
|
||||||
('7', '7'),
|
|
||||||
('8', '8'),
|
|
||||||
('9', '9'),
|
|
||||||
('10', '10')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
vcpus = ChoiceVar(
|
|
||||||
label="Number of CPUs",
|
|
||||||
description="Number of virtual CPUs",
|
|
||||||
default="2",
|
|
||||||
choices=(
|
|
||||||
('1', '1'),
|
|
||||||
('2', '2'),
|
|
||||||
('3', '3'),
|
|
||||||
('4', '4'),
|
|
||||||
('5', '5'),
|
|
||||||
('6', '6'),
|
|
||||||
('7', '7'),
|
|
||||||
('8', '8')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
memory = ChoiceVar(
|
|
||||||
description="Amount of VM memory",
|
|
||||||
default="4096",
|
|
||||||
choices=(
|
|
||||||
('1024', '1024'),
|
|
||||||
('2048', '2048'),
|
|
||||||
('4096', '4096'),
|
|
||||||
('8192', '8192'),
|
|
||||||
('16384', '16384'),
|
|
||||||
('32768', '32768')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
disk = IntegerVar(
|
|
||||||
label="Disk size",
|
|
||||||
description="Disk size in GB",
|
|
||||||
default="20"
|
|
||||||
)
|
|
||||||
|
|
||||||
def appendLogSuccess(self, log: str, obj=None):
|
|
||||||
self.success_log += " {} `\n{}\n`".format(log, obj)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def flushLogSuccess(self):
|
|
||||||
self.log_success(self.success_log)
|
|
||||||
self.success_log = ""
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _generateHostname(self, cluster, env, descriptor):
|
|
||||||
|
|
||||||
# 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"
|
|
||||||
|
|
||||||
search_for = str(cluster) + '-' + env + '-' + descriptor + '-'
|
|
||||||
vms = VirtualMachine.objects.filter(
|
|
||||||
name__startswith=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)
|
|
||||||
|
|
||||||
hostname = str(cluster) + '-' + env + '-' + descriptor + '-' + vm_index
|
|
||||||
|
|
||||||
return hostname
|
|
||||||
|
|
||||||
def __validateInput(self, data, base_context_data):
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.interfaces = base_context_data['interfaces']
|
|
||||||
self.time_zone = base_context_data['time_zone']
|
|
||||||
self.tags = base_context_data['tags']
|
|
||||||
self.dns_domain_private = base_context_data['dns_domain_private']
|
|
||||||
self.dns_domain_public = base_context_data['dns_domain_public']
|
|
||||||
self.dns_servers = base_context_data['dns_servers']
|
|
||||||
self.ntp_servers = base_context_data['ntp_servers']
|
|
||||||
self.ssh_authorized_keys = base_context_data['ssh_authorized_keys']
|
|
||||||
self.ssh_port = base_context_data['ssh_port']
|
|
||||||
self.tags.update({'env_' + self.env: {'comments': 'Environment', 'color': '009688'}})
|
|
||||||
self.tags.update({'vsphere_' + data['backup']: {'comments': 'Backup strategy', 'color': '009688'}})
|
|
||||||
if (data['health_check'] == 'True'):
|
|
||||||
self.tags.update({'health_check': {'comments': 'Do health checks in deployment', 'color': '4caf50'}})
|
|
||||||
if (data['serial'] == 'True'):
|
|
||||||
self.tags.update({'serial': {'comments': 'Do health checks in deployment', 'color': '4caf50'}})
|
|
||||||
except Exception as error:
|
|
||||||
self.log_failure("Error when parsing context_data! Error: " + str(error))
|
|
||||||
return False
|
|
||||||
|
|
||||||
if self.interfaces is None:
|
|
||||||
self.log_failure("No interfaces object in context data!")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if data['ip_addresses'] != "" and len(data['ip_addresses'].splitlines()) != int(data['vm_count']):
|
|
||||||
self.log_failure("The number of IP addresses and VMs does not match!")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if data['hostnames'] != "" and len(data['hostnames'].splitlines()) != int(data['vm_count']):
|
|
||||||
self.log_failure("The number of hostnames and VMs does not match!")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if self.interfaces is None:
|
|
||||||
self.log_failure("No interfaces object in context data!")
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def run(self, data):
|
|
||||||
|
|
||||||
vrf = VRF.objects.get(
|
|
||||||
name="global"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.env = data['env']
|
|
||||||
|
|
||||||
# Setup base virtual machine for copying config_context
|
|
||||||
base_vm = VirtualMachine(
|
|
||||||
cluster=data['cluster'],
|
|
||||||
platform=data['platform'],
|
|
||||||
role=data['role'],
|
|
||||||
tenant=data['tenant'],
|
|
||||||
name="script_temp"
|
|
||||||
)
|
|
||||||
|
|
||||||
base_vm.save()
|
|
||||||
|
|
||||||
if self.__validateInput(data=data, base_context_data=base_vm.get_config_context()) is False:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Delete base virtual machine for copying config_context
|
|
||||||
vm_delete = VirtualMachine.objects.get(
|
|
||||||
name="script_temp"
|
|
||||||
)
|
|
||||||
vm_delete.delete()
|
|
||||||
|
|
||||||
for i in range(0, int(data['vm_count'])):
|
|
||||||
|
|
||||||
hostnames = data['hostnames'].splitlines()
|
|
||||||
ip_addresses = data['ip_addresses'].splitlines()
|
|
||||||
|
|
||||||
if len(hostnames) > 0:
|
|
||||||
hostname = hostnames[i]
|
|
||||||
else:
|
|
||||||
hostname = self._generateHostname(
|
|
||||||
cluster=data['cluster'].name,
|
|
||||||
env=self.env,
|
|
||||||
descriptor="na"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check if VM exists
|
|
||||||
if len(VirtualMachine.objects.filter(name=hostname)) > 0:
|
|
||||||
self.log_failure("VM with hostname " + hostname + " already exists!")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if len(ip_addresses) > 0:
|
|
||||||
|
|
||||||
# Check if IP address exists
|
|
||||||
ip_check = IPAddress.objects.filter(
|
|
||||||
address=ip_addresses[i]
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(ip_check) > 0:
|
|
||||||
self.log_failure(str(ip_check[0].address) + ' is already assigned to ' + str(ip_check[0].interface.name))
|
|
||||||
return False
|
|
||||||
|
|
||||||
domain = self.dns_domain_private if netaddr.IPNetwork(ip_addresses[i]).is_private() is True else self.dns_domain_public
|
|
||||||
ip_address = IPAddress(
|
|
||||||
address=ip_addresses[i],
|
|
||||||
vrf=vrf,
|
|
||||||
tenant=data['tenant'],
|
|
||||||
family=4,
|
|
||||||
dns_name=hostname + '.' + domain,
|
|
||||||
)
|
|
||||||
|
|
||||||
ip_address.save()
|
|
||||||
self.appendLogSuccess(log="Created IP", obj=ip_addresses[i]).appendLogSuccess(log="DNS", obj=hostname + '.' + domain)
|
|
||||||
else:
|
|
||||||
if data['untagged_vlan'] is not None:
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
# Auto assign IPs from vsphere_port_group
|
|
||||||
prefix = Prefix.objects.get(
|
|
||||||
vlan=data['untagged_vlan'],
|
|
||||||
site=Site.objects.get(name=data['cluster'].site.name),
|
|
||||||
is_pool=True
|
|
||||||
)
|
|
||||||
|
|
||||||
ip = prefix.get_first_available_ip()
|
|
||||||
|
|
||||||
domain = self.dns_domain_private if netaddr.IPNetwork(ip).is_private() is True else self.dns_domain_public
|
|
||||||
ip_address = IPAddress(
|
|
||||||
address=ip,
|
|
||||||
vrf=vrf,
|
|
||||||
tenant=data['tenant'],
|
|
||||||
family=4,
|
|
||||||
dns_name=hostname + '.' + domain,
|
|
||||||
)
|
|
||||||
|
|
||||||
ip_address.save()
|
|
||||||
self.appendLogSuccess(log="Auto-assigned IP", obj=ip).appendLogSuccess(log="DNS", obj=hostname + '.' + domain)
|
|
||||||
except Exception:
|
|
||||||
self.log_failure("An error occurred while auto-assigning IP address. VLAN or Prefix not found!")
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
self.log_failure("No IP address choice was made")
|
|
||||||
return False
|
|
||||||
|
|
||||||
vm = VirtualMachine(
|
|
||||||
status=data['status'],
|
|
||||||
cluster=data['cluster'],
|
|
||||||
platform=data['platform'],
|
|
||||||
role=data['role'],
|
|
||||||
tenant=data['tenant'],
|
|
||||||
name=hostname,
|
|
||||||
disk=data['disk'],
|
|
||||||
memory=data['memory'],
|
|
||||||
vcpus=data['vcpus']
|
|
||||||
)
|
|
||||||
|
|
||||||
vm.primary_ip4 = ip_address
|
|
||||||
vm.save()
|
|
||||||
self.appendLogSuccess(log="for VM in ", obj=vm.cluster)
|
|
||||||
|
|
||||||
self.__assignTags(vm=vm)
|
|
||||||
|
|
||||||
# Assign IP to interface
|
|
||||||
if self.__setupInterface(ip_address=ip_address, data=data, vm=vm) is False:
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.flushLogSuccess()
|
|
||||||
|
|
||||||
return self.success_log
|
|
||||||
|
|
||||||
def __assignTags(self, vm: VirtualMachine):
|
|
||||||
"""
|
|
||||||
Assign tags from context data
|
|
||||||
"""
|
|
||||||
for tag in self.tags:
|
|
||||||
if len(Tag.objects.filter(name=tag)) == 0:
|
|
||||||
color = self.tags[tag]['color'] if 'color' in self.tags[tag] else '9e9e9e'
|
|
||||||
comments = self.tags[tag]['comments'] if 'comments' in self.tags[tag] else 'No comments'
|
|
||||||
newTag = Tag(comments=comments, name=tag, color=color)
|
|
||||||
newTag.save()
|
|
||||||
|
|
||||||
vm.tags.add(tag)
|
|
||||||
|
|
||||||
vm.save()
|
|
||||||
|
|
||||||
def __setupInterface(self, ip_address: IPAddress, vm: VirtualMachine, data):
|
|
||||||
"""
|
|
||||||
Setup interface and add IP address
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Get net address tools
|
|
||||||
ip = netaddr.IPNetwork(ip_address.address)
|
|
||||||
prefix_search = str(ip.network) + '/' + str(ip.prefixlen)
|
|
||||||
|
|
||||||
try:
|
|
||||||
prefix = Prefix.objects.get(
|
|
||||||
prefix=prefix_search,
|
|
||||||
is_pool=True
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
self.log_failure("Prefix for IP " + ip_address.address + " was not found")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Right now we dont support multiple nics. But the data model supports it
|
|
||||||
interface = Interface(
|
|
||||||
name=self.interfaces['nic0']['name'],
|
|
||||||
mtu=self.interfaces['nic0']['mtu'],
|
|
||||||
virtual_machine=vm,
|
|
||||||
type=InterfaceTypeChoices.TYPE_VIRTUAL
|
|
||||||
)
|
|
||||||
|
|
||||||
# If we need anything other than Access, here is were to change it
|
|
||||||
if self.interfaces['nic0']['mode'] == "Access":
|
|
||||||
interface.mode = InterfaceModeChoices.MODE_ACCESS
|
|
||||||
interface.untagged_vlan = prefix.vlan
|
|
||||||
self.interfaces['nic0']['vsphere_port_group'] = prefix.vlan.name
|
|
||||||
|
|
||||||
interface.save()
|
|
||||||
|
|
||||||
# Add interface to IP address
|
|
||||||
ip_address.interface = interface
|
|
||||||
ip_address.save()
|
|
||||||
|
|
||||||
self.appendLogSuccess(log="with interface ", obj=interface).appendLogSuccess(log=", vlan ", obj=prefix.vlan.name)
|
|
||||||
|
|
||||||
return True
|
|
Loading…
Add table
Add a link
Reference in a new issue