fuel-web/nailgun/nailgun/utils/fake_generator.py

447 lines
14 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2015 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the 'License'); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
from itertools import cycle
from itertools import product
from netaddr import EUI
from netaddr import IPNetwork
from netaddr import mac_unix_expanded
import random
import six
import string
MANUFACTURERS = {
'real': (
'Supermicro',
'80AD',
'Dell',
'Samsung',
),
'virtual': (
'VirtualBox',
)
}
SAMPLE_CPUS = {
'real': (
{
'model': 'Intel(R) Xeon(R) CPU E5-2620 0 @ 2.00GHz',
'frequency': 2001
},
{
'model': 'Intel(R) Xeon(R) CPU E31230 @ 3.20GHz',
'frequency': 3201
},
{
'model': 'Intel(R) Core(TM) i7-2670QM CPU @ 2.20GHz',
'frequency': 2185
}
),
'virtual': (
{
'model': 'QEMU Virtual CPU version 1.0',
'frequency': 1999
},
)
}
SAMPLE_INTERFACE_OFFLOADING_MODES = [
{
'name': 'tx-checksumming',
'state': True, # True, false, null
'sub': [
{
'name': 'tx-checksum-sctp',
'state': False, # True, false, null
'sub': []
},
{
'name': 'tx-checksum-ipv6',
'state': True, # True, false, null
'sub': []
},
{
'name': 'tx-checksum-ipv4',
'state': None, # True, false, null
'sub': []
}
]
},
{
'name': 'rx-checksumming',
'state': None,
'sub': []
}
]
DISK_SAMPLES = [
{
'model': 'TOSHIBA MK1002TS',
'name': 'sda',
'disk': 'sda',
'size': 1000204886016
},
{
'model': 'Virtual Floppy0',
'name': 'sde',
'disk': 'sde',
'size': 0
},
{
'model': 'Virtual HDisk0',
'name': 'sdf',
'disk': 'sdf',
'size': 0
},
{
'model': 'Silicon-Power16G',
'name': 'sdb',
'disk': 'sdb',
'size': 155692564480
},
{
'model': 'WDC WD3200BPVT-7',
'name': 'sda',
'disk': 'sda',
'size': 320072933376
},
{
'model': 'QEMU HARDDISK',
'name': 'sda',
'disk': 'sda',
'size': 68719476736
}
]
MEMORY_DEVICE_SAMPLES = [
{
'frequency': 1333,
'type': 'DDR3',
'size': 8589934592
},
{
'frequency': 1333,
'type': 'DDR3',
'size': 4294967296
},
{
'type': 'RAM',
'size': 10737418240
},
{
'type': 'Flash',
'size': 16777216
}
]
NETWORK_1 = '10.20.0.0/16'
NETWORK_2 = '172.18.67.0/24'
class FakeNodesGenerator(object):
"""This class uses to generate fake nodes"""
def __init__(self):
self.net1 = IPNetwork(NETWORK_1)
self.net1_ip_pool = cycle(self.net1.iter_hosts())
self.net2 = IPNetwork(NETWORK_2)
self.net2_ip_pool = cycle(self.net2.iter_hosts())
self.mcounter = dict()
self.mac_counter = 0
def _get_network_data(self, net_name):
if net_name == 'net1':
return str(next(self.net1_ip_pool)), str(self.net1.netmask)
if net_name == 'net2':
return str(next(self.net2_ip_pool)), str(self.net2.netmask)
return None, None
def _generate_mac(self):
# MAC's starts from FF:FF:FF:FF:FF:FE counting down
mac = str(EUI(281474976710654 - self.mac_counter,
dialect=mac_unix_expanded))
self.mac_counter += 1
return mac
@staticmethod
def _get_disk_suffixes(amount):
length = 1
counter = 0
while counter < amount:
for item in product(string.ascii_lowercase, repeat=length):
if counter == amount:
break
counter += 1
yield ''.join(item)
length += 1
def _generate_disks_meta(self, amount):
disks = []
total_size = 0
for i, disk_suffix in enumerate(self._get_disk_suffixes(amount)):
new_disk = copy.deepcopy(random.choice(DISK_SAMPLES))
# disks total size shouldn't be 0
if i == amount - 1 and total_size == 0:
while new_disk['size'] == 0:
new_disk = copy.deepcopy(random.choice(DISK_SAMPLES))
new_disk.update({
'name': 'sd{0}'.format(disk_suffix),
'disk': 'sd{0}'.format(disk_suffix)
})
total_size += new_disk['size']
disks.append(new_disk)
return disks
@staticmethod
def _get_random_iface_offloading_modes():
offloading_modes = copy.deepcopy(SAMPLE_INTERFACE_OFFLOADING_MODES)
for mode in offloading_modes:
mode['state'] = random.choice([True, False, None])
for sub in mode.get('sub', []):
sub['state'] = random.choice([True, False, None])
return offloading_modes
def _generate_interfaces_meta(self, known_mac, known_ip, known_ip_mask,
use_offload_iface, amount):
ifaces = []
driver = random.choice(['igb'] * 9 + ['mlx4_en', 'eth_ipoib', 'e1000'])
name = random.choice(['eth'] * 8 + ['wlan', 'p2p'])
know_interface_num = random.randint(0, amount - 1)
for i in six.moves.range(amount):
max_speed = random.choice([100, 1000, 56000])
current_speed_set = [
random.randint(max_speed * 0.5, max_speed) for _ in range(3)]
current_speed_set.append(None)
new_iface = {
'name': '{0}{1}'.format(name, i),
'driver': driver,
'bus_info': '0000:0{0}:00.0'.format(i),
'max_speed': max_speed,
'current_speed': random.choice(current_speed_set),
'pxe': random.choice([True, False])
}
if i == know_interface_num:
new_iface.update({
'mac': known_mac,
'ip': known_ip,
'netmask': known_ip_mask
})
else:
new_iface['mac'] = self._generate_mac()
net = random.choice(['net1', 'net2', None])
if net:
ip, netmask = self._get_network_data(net)
new_iface.update({
'ip': ip,
'netmask': netmask
})
if use_offload_iface:
new_iface['offloading_modes'] = \
self._get_random_iface_offloading_modes()
ifaces.append(new_iface)
return ifaces
@staticmethod
def _generate_systems_meta(hostname, manufacture, platform_name):
return {
'manufacturer': manufacture,
'version': '{0}.{0}'.format(random.randint(0, 10),
random.randint(0, 9)),
'serial': ''.join(
[str(random.randint(0, 9)) for _ in six.moves.range(10)]),
'fqdn': '{0}.some-where.net'.format(hostname),
'product': platform_name,
'family': 'To be filled by O.E.M.'
}
@staticmethod
def _generate_cpu_meta(kind):
real_proc = random.choice([0, 1, 2, 4])
total_proc = real_proc * random.choice([1, 2, 4]) or 1
proc = random.choice(SAMPLE_CPUS[kind])
return {
'real': real_proc,
'total': total_proc,
'spec': [copy.deepcopy(proc) for _ in six.moves.range(total_proc)]
}
@staticmethod
def _generate_memory_meta(amount):
max_capacity = 1024 ** 3 * random.choice([8, 16, 32, 64])
total_capacity = 0
devices = []
for _ in six.moves.range(amount):
new_memory = copy.deepcopy(
random.choice(MEMORY_DEVICE_SAMPLES))
if (total_capacity + new_memory['size']) > max_capacity:
if total_capacity == 0:
new_memory['size'] = max_capacity
else:
break
total_capacity += new_memory['size']
devices.append(new_memory)
return {
'slots': len(devices),
'total': total_capacity,
'maximum_capacity': max_capacity,
'devices': devices
}
@staticmethod
def _generate_numa_topology(memory, amount=2):
dist = []
for i in range(amount):
row = []
for j in range(amount):
row.append('1.0' if i == j else '2.1')
dist.append(row)
cpus_count = random.choice([8, 16, 32]) * amount
all_cpus = {i for i in range(cpus_count)}
def gen_numa(id_):
if id_ + 1 == cpus_count:
cpus = all_cpus
else:
remain = list(all_cpus)
random.shuffle(remain)
cpus = remain[:cpus_count / amount]
all_cpus.difference_update(cpus)
return {
'id': id_,
'cpus': sorted(cpus),
'memory': memory // amount
}
return {
'distances': dist,
'numa_nodes': [gen_numa(i) for i in range(amount)],
'supported_hugepages': [2048, 1048576]
}
def generate_fake_node(self, pk, is_online=True, is_error=False,
use_offload_iface=False, min_ifaces_num=1):
"""Generate one fake node
:param int pk: node's database primary key
:param bool is_online: node's online status
:param bool is_error: node's error status
:param bool use_offload_iface: use offloading_modes data for
node's interfaces or not
:returns: kwargs dict that represents fake node
"""
kind = random.choice(['real', 'virtual'])
manufacture = random.choice(MANUFACTURERS[kind])
self.mcounter[manufacture] = self.mcounter.get(manufacture, 0) + 1
hostname = 'node-{0}'.format(pk)
platform_name = random.choice(['', 'X9SCD', 'N5110', 'X9DRW'])
mac = self._generate_mac()
net = random.choice(['net1', 'net2'])
ip, netmask = self._get_network_data(net)
memory_meta = self._generate_memory_meta(random.randint(1, 8))
return {
'pk': pk,
'model': 'nailgun.node',
'fields': {
'status': 'error' if is_error else 'discover',
'manufacturer': manufacture,
'name': manufacture + ' {0}({1})'.format(
platform_name, self.mcounter.get(manufacture)),
'hostname': hostname,
'ip': ip,
'mac': mac,
'online': is_online,
'labels': {},
'pending_addition': False,
'pending_deletion': False,
'platform_name': platform_name,
'os_platform': 'ubuntu',
'progress': 0,
'timestamp': '',
'meta': {
'cpu': self._generate_cpu_meta(kind),
'interfaces': self._generate_interfaces_meta(
mac, ip, netmask, use_offload_iface,
random.randrange(min_ifaces_num, 7)),
'disks': self._generate_disks_meta(random.randint(1, 7)),
'system': self._generate_systems_meta(
hostname, manufacture, platform_name),
'memory': memory_meta,
'numa_topology': self._generate_numa_topology(
memory_meta['total'],
random.randint(1, 3)
),
},
}
}
def generate_fake_nodes(self, total_nodes_count, error_nodes_count=None,
offline_nodes_count=None,
offloading_ifaces_nodes_count=None,
min_ifaces_num=1):
"""Generate list of fake nodes
:param int total_nodes_count: total count of nodes to generate
:param int error_nodes_count: count of error nodes (optional)
:param int offline_nodes_count: count of offline nodes (optional)
:param int offloading_ifaces_nodes_count: count of nodes with interface
using offloading (optional)
:returns: list of dicts, each of which represents node
"""
if error_nodes_count is None:
error_nodes_count = int(0.09 * total_nodes_count)
if offline_nodes_count is None:
offline_nodes_count = int(0.08 * total_nodes_count)
if error_nodes_count + offline_nodes_count > total_nodes_count:
error_nodes_count = int(0.09 * total_nodes_count)
offline_nodes_count = int(0.08 * total_nodes_count)
if offloading_ifaces_nodes_count is None:
offloading_ifaces_nodes_count = int(0.2 * total_nodes_count)
total_nodes_range = six.moves.range(total_nodes_count)
# Making error and offline random sets non intersecting
error_nodes_indexes = set(random.sample(
total_nodes_range, error_nodes_count))
offline_nodes_indexes = set(random.sample(
set(total_nodes_range) - error_nodes_indexes,
offline_nodes_count
))
offloading_ifaces_nodes_indexes = set(random.sample(
total_nodes_range, offloading_ifaces_nodes_count))
res = []
for i in total_nodes_range:
node = self.generate_fake_node(
i + 1,
is_online=i not in offline_nodes_indexes,
is_error=i in error_nodes_indexes,
use_offload_iface=i in offloading_ifaces_nodes_indexes,
min_ifaces_num=min_ifaces_num
)
res.append(node)
return res