Dmitriy Kruglov | 6146cb4 | 2023-04-10 00:13:59 +0200 | [diff] [blame] | 1 | import os |
| 2 | import sys |
| 3 | from typing import Dict, Final, List |
| 4 | |
| 5 | import connection as conn |
| 6 | import openstack |
| 7 | from openstack.exceptions import ResourceFailure |
| 8 | |
| 9 | |
| 10 | compute = conn.cloud.compute |
| 11 | network = conn.cloud.network |
| 12 | volume = conn.cloud.volume |
| 13 | |
| 14 | CLIENTS_COUNT: Final[int] = conn.FIO_CLIENTS_COUNT |
| 15 | CLIENT_NAME_MASK: Final[str] = conn.FIO_CLIENT_NAME_MASK |
| 16 | UBUNTU_IMAGE_NAME: Final[str] = conn.UBUNTU_IMAGE_NAME |
| 17 | |
| 18 | VOL_NAME_MASK: Final[str] = conn.FIO_VOL_NAME_MASK |
| 19 | VOL_SIZE: Final[int] = conn.FIO_VOL_SIZE |
| 20 | VOL_TYPE: Final[str] = conn.FIO_VOL_TYPE |
| 21 | VOL_MOUNTPOINT: Final[str] = conn.FIO_VOL_MOUNTPOINT |
| 22 | |
| 23 | FLAVOR_NAME: Final[str] = conn.FIO_FLAVOR_NAME |
| 24 | FLAVOR_RAM: Final[int] = conn.FIO_FLAVOR_RAM |
| 25 | FLAVOR_CPUS: Final[int] = conn.FIO_FLAVOR_CPUS |
| 26 | FLAVOR_DISK: Final[int] = conn.FIO_FLAVOR_DISK |
| 27 | |
| 28 | NET_NAME: Final[str] = conn.FIO_NET_NAME |
| 29 | ROUTER_NAME: Final[str] = conn.FIO_ROUTER_NAME |
| 30 | FLOATING_NET_NAME = conn.FLOATING_NET_NAME |
| 31 | SUBNET_NAME = conn.FIO_SUBNET_NAME |
| 32 | SUBNET_RANGE = conn.FIO_SUBNET_RANGE |
| 33 | MTU_SIZE = conn.MTU_SIZE |
| 34 | NET_IPV4 = conn.NET_IPV4 |
| 35 | |
| 36 | KEYPAIR_NAME: Final[str] = conn.FIO_KEYPAIR_NAME |
| 37 | PRIVATE_KEYPAIR_FILE: Final[str] = conn.PRIVATE_KEYPAIR_FILE |
| 38 | |
| 39 | SG_NAME: Final[str] = conn.FIO_SG_NAME |
| 40 | HV_SUFFIX: Final[str] = conn.HV_SUFFIX |
| 41 | CLOUD_NAME: Final[str] = conn.CLOUD_NAME |
| 42 | |
| 43 | NODES: Final[List[str]] = [] |
| 44 | SKIP_NODES: Final[List[str]] = [] |
| 45 | |
| 46 | |
| 47 | SG_ALLOW_ALL_RULES: Final[List[Dict]] = [ |
| 48 | { |
| 49 | 'remote_ip_prefix': '0.0.0.0/0', |
| 50 | 'protocol': 'icmp', |
| 51 | 'port_range_max': None, |
| 52 | 'port_range_min': None, |
| 53 | 'ethertype': 'IPv4' |
| 54 | }, |
| 55 | { |
| 56 | 'remote_ip_prefix': '0.0.0.0/0', |
| 57 | 'protocol': 'tcp', |
| 58 | 'port_range_max': 65535, |
| 59 | 'port_range_min': 1, |
| 60 | 'ethertype': 'IPv4' |
| 61 | }, |
| 62 | { |
| 63 | 'remote_ip_prefix': '0.0.0.0/0', |
| 64 | 'protocol': 'udp', |
| 65 | 'port_range_max': 65535, |
| 66 | 'port_range_min': 1, |
| 67 | 'ethertype': 'IPv4' |
| 68 | } |
| 69 | ] |
| 70 | |
| 71 | |
| 72 | def create_server( |
| 73 | name, image_id, flavor_id, networks, |
| 74 | key_name, security_groups, availability_zone |
| 75 | ) -> openstack.connection.Connection: |
| 76 | srv = compute.create_server( |
| 77 | name=name, image_id=image_id, flavor_id=flavor_id, networks=networks, |
| 78 | key_name=key_name, security_groups=security_groups, |
| 79 | availability_zone=availability_zone) |
| 80 | return srv |
| 81 | |
| 82 | |
| 83 | if __name__ == "__main__": |
| 84 | # Check if any fio servers already exist on the cloud |
| 85 | servers = compute.servers(details=False, name=CLIENT_NAME_MASK) |
| 86 | srvrs = list(servers) |
| 87 | if srvrs: |
| 88 | names = [s.name for s in srvrs] |
| 89 | print("The following servers already exist in the cloud:") |
| 90 | print(*names, sep='\n') |
| 91 | sys.exit(0) |
| 92 | |
| 93 | # Create fio sg if needed |
| 94 | sg = network.find_security_group(SG_NAME) |
| 95 | if not sg: |
| 96 | sg = network.create_security_group(name=SG_NAME) |
| 97 | # Add 'allow-all' kind of rules to the security group |
| 98 | pairs = [ |
| 99 | (r, d) for r in SG_ALLOW_ALL_RULES for d in ('ingress', 'egress')] |
| 100 | for (rule, direction) in pairs: |
| 101 | network.create_security_group_rule( |
| 102 | security_group_id=sg.id, direction=direction, **rule) |
| 103 | |
| 104 | # Create fio keypair if needed |
| 105 | kp = compute.find_keypair(KEYPAIR_NAME) |
| 106 | if not kp: |
| 107 | kp = compute.create_keypair(name=KEYPAIR_NAME) |
| 108 | with open(PRIVATE_KEYPAIR_FILE, 'w') as f: |
| 109 | f.write("{}".format(kp.private_key)) |
| 110 | |
| 111 | os.chmod(PRIVATE_KEYPAIR_FILE, 0o400) |
| 112 | |
| 113 | # Create fio flavor if needed |
| 114 | flavor = compute.find_flavor(FLAVOR_NAME) |
| 115 | if not flavor: |
| 116 | flavor = compute.create_flavor( |
| 117 | name=FLAVOR_NAME, ram=FLAVOR_RAM, |
| 118 | vcpus=FLAVOR_CPUS, disk=FLAVOR_DISK) |
| 119 | |
| 120 | # Set image property to enable virtio-net multique in created servers |
| 121 | img = compute.find_image(UBUNTU_IMAGE_NAME) |
| 122 | compute.set_image_metadata(img.id, hw_vif_multiqueue_enabled='true') |
| 123 | |
| 124 | # Create fio router if needed |
| 125 | fip_net = network.find_network(FLOATING_NET_NAME) |
| 126 | router = network.find_router(ROUTER_NAME) |
| 127 | if not router: |
| 128 | router = network.create_router( |
| 129 | name=ROUTER_NAME, external_gateway_info={'network_id': fip_net.id}) |
| 130 | |
| 131 | # Create fio net/subnet if needed |
| 132 | fio_net = network.find_network(NET_NAME) |
| 133 | if not fio_net: |
| 134 | fio_net = network.create_network( |
| 135 | name=NET_NAME, |
| 136 | availability_zone_hints=['nova'], |
| 137 | # mtu=MTU_SIZE, |
| 138 | shared=False, |
| 139 | port_security_enabled=True) |
| 140 | fio_subnet = network.create_subnet( |
| 141 | name=SUBNET_NAME, |
| 142 | network_id=fio_net.id, |
| 143 | cidr=SUBNET_RANGE, |
| 144 | ip_version=NET_IPV4) |
| 145 | # Add fio net to fio router |
| 146 | fio_net_port = network.add_interface_to_router( |
| 147 | router.id, subnet_id=fio_subnet.id) |
| 148 | |
| 149 | # Get list of running computes with enabled 'nova-compute' service |
| 150 | cmp_services = compute.services(binary='nova-compute') |
| 151 | computes = [s for s in cmp_services if |
| 152 | s.host in NODES and |
| 153 | s.host not in SKIP_NODES and |
| 154 | s.state == 'up' and s.status == 'enabled'] |
| 155 | |
| 156 | # Prepare list of hypervisors to be used for running fio servers |
| 157 | hypervisors = [] |
| 158 | computes_num = len(computes) |
| 159 | for i in range(CLIENTS_COUNT): |
| 160 | hypervisors.append( |
| 161 | ".".join([computes[i % computes_num].host, HV_SUFFIX])) |
| 162 | |
| 163 | # Create <CLIENTS_COUNT> clients, attached to fio private network |
| 164 | vms = [] |
| 165 | for i in range(CLIENTS_COUNT): |
| 166 | name = f"{CLIENT_NAME_MASK}{i}" |
| 167 | az = f"::{hypervisors[i]}" |
| 168 | flavor_id = flavor.id |
| 169 | vm = create_server( |
| 170 | name=name, |
| 171 | image_id=img.id, |
| 172 | flavor_id=flavor_id, |
| 173 | networks=[{'uuid': fio_net.id}], |
| 174 | key_name=KEYPAIR_NAME, |
| 175 | security_groups=[{'name': SG_NAME}], |
| 176 | availability_zone=az) |
| 177 | try: |
| 178 | vm = compute.wait_for_server(vm, wait=180) |
| 179 | node = hypervisors[i].split('.')[0] |
| 180 | print(f"Fio client VM '{vm.name}' is created on '{node}' node") |
| 181 | # Stop and exit if any of the servers creation failed (for any reason) |
| 182 | except ResourceFailure as e: |
| 183 | print( |
| 184 | f"Fio client VM '{vm.name}' creation failed with '{e.message}'" |
| 185 | " error.") |
| 186 | conn.delete_server(vm) |
| 187 | sys.exit(0) |
| 188 | vms.append(vm) |
| 189 | |
| 190 | # Create a volume of the given type |
| 191 | vol_name = f"{VOL_NAME_MASK}{i}" |
| 192 | vol = volume.create_volume( |
| 193 | name=vol_name, size=VOL_SIZE, volume_type=VOL_TYPE) |
| 194 | try: |
| 195 | vol = volume.wait_for_status(vol, status='available') |
| 196 | print(f"Volume '{vol.name}' is created") |
| 197 | # Delete a volume if its creation failed and switch to next |
| 198 | # fio client VM |
| 199 | except ResourceFailure as e: |
| 200 | print( |
| 201 | f"Volume '{vol.name}' creation failed with '{e.message}' " |
| 202 | "error.") |
| 203 | conn.delete_volume(vol) |
| 204 | continue |
| 205 | |
| 206 | # Attach the volume to the fio client |
| 207 | compute.create_volume_attachment(vm, volume=vol) |
| 208 | try: |
| 209 | vol = volume.wait_for_status(vol, status='in-use') |
| 210 | print(f"Volume '{vol.name}' is attached to '{vm.name}' fio client") |
| 211 | # Delete a volume if attachment failed and switch to next |
| 212 | # fio client VM |
| 213 | except ResourceFailure as e: |
| 214 | print( |
| 215 | f"Volume '{vol.name}' attachment failed with '{e.message}' " |
| 216 | "error.") |
| 217 | conn.delete_volume(vol) |
| 218 | continue |