teuthology 1.2.1__py3-none-any.whl → 1.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. scripts/node_cleanup.py +18 -2
  2. scripts/suite.py +2 -0
  3. teuthology/__init__.py +0 -1
  4. teuthology/config.py +28 -7
  5. teuthology/dispatcher/supervisor.py +9 -6
  6. teuthology/lock/cli.py +4 -2
  7. teuthology/lock/ops.py +10 -9
  8. teuthology/lock/query.py +28 -4
  9. teuthology/lock/util.py +1 -1
  10. teuthology/misc.py +13 -58
  11. teuthology/openstack/__init__.py +202 -176
  12. teuthology/openstack/setup-openstack.sh +52 -27
  13. teuthology/orchestra/opsys.py +15 -0
  14. teuthology/orchestra/remote.py +54 -2
  15. teuthology/orchestra/run.py +8 -2
  16. teuthology/provision/downburst.py +84 -43
  17. teuthology/provision/fog.py +2 -2
  18. teuthology/repo_utils.py +3 -1
  19. teuthology/run.py +1 -1
  20. teuthology/scrape.py +5 -2
  21. teuthology/suite/merge.py +3 -1
  22. teuthology/suite/run.py +51 -37
  23. teuthology/suite/util.py +2 -2
  24. teuthology/task/install/rpm.py +8 -16
  25. teuthology/task/internal/__init__.py +2 -1
  26. teuthology/task/internal/syslog.py +17 -13
  27. teuthology/task/kernel.py +1 -1
  28. {teuthology-1.2.1.dist-info → teuthology-1.2.2.dist-info}/METADATA +10 -8
  29. {teuthology-1.2.1.dist-info → teuthology-1.2.2.dist-info}/RECORD +36 -36
  30. {teuthology-1.2.1.dist-info → teuthology-1.2.2.dist-info}/WHEEL +1 -1
  31. {teuthology-1.2.1.data → teuthology-1.2.2.data}/scripts/adjust-ulimits +0 -0
  32. {teuthology-1.2.1.data → teuthology-1.2.2.data}/scripts/daemon-helper +0 -0
  33. {teuthology-1.2.1.data → teuthology-1.2.2.data}/scripts/stdin-killer +0 -0
  34. {teuthology-1.2.1.dist-info → teuthology-1.2.2.dist-info}/entry_points.txt +0 -0
  35. {teuthology-1.2.1.dist-info → teuthology-1.2.2.dist-info/licenses}/LICENSE +0 -0
  36. {teuthology-1.2.1.dist-info → teuthology-1.2.2.dist-info}/top_level.txt +0 -0
@@ -35,7 +35,6 @@ import subprocess
35
35
  import tempfile
36
36
  import teuthology
37
37
  import time
38
- import types
39
38
  import yaml
40
39
  import base64
41
40
 
@@ -46,6 +45,7 @@ from teuthology.config import config as teuth_config
46
45
  from teuthology.config import set_config_attr
47
46
  from teuthology.orchestra import connection
48
47
  from teuthology import misc
48
+ from openstack import connection as openstack_connection
49
49
 
50
50
  from yaml.representer import SafeRepresenter
51
51
 
@@ -78,6 +78,8 @@ class OpenStackInstance(object):
78
78
  self.name_or_id = name_or_id
79
79
  self.private_or_floating_ip = None
80
80
  self.private_ip = None
81
+ self.info = info
82
+ self.conn = self._create_connection()
81
83
  if info is None:
82
84
  self.set_info()
83
85
  else:
@@ -88,11 +90,14 @@ class OpenStackInstance(object):
88
90
  errmsg = '{}: {}'.format(errmsg, self.info['message'])
89
91
  raise Exception(errmsg)
90
92
 
93
+ def _create_connection(self):
94
+ return openstack_connection.from_config(cloud=None)
95
+
91
96
  def set_info(self):
92
97
  try:
93
- self.info = json.loads(
94
- OpenStack().run("server show -f json " + self.name_or_id))
95
- enforce_json_dictionary(self.info)
98
+ server = self.conn.compute.find_server(self.name_or_id)
99
+ if server:
100
+ self.info = {k.lower(): v for k, v in server.to_dict().items()}
96
101
  except CalledProcessError:
97
102
  self.info = None
98
103
 
@@ -129,32 +134,17 @@ class OpenStackInstance(object):
129
134
  self.set_info()
130
135
 
131
136
  def get_ip_neutron(self):
132
- subnets = json.loads(misc.sh("unset OS_AUTH_TYPE OS_TOKEN ; "
133
- "neutron subnet-list -f json -c id -c ip_version"))
134
- subnet_ids = []
135
- for subnet in subnets:
136
- if subnet['ip_version'] == 4:
137
- subnet_ids.append(subnet['id'])
138
- if not subnet_ids:
139
- raise Exception("no subnet with ip_version == 4")
140
- ports = json.loads(misc.sh("unset OS_AUTH_TYPE OS_TOKEN ; "
141
- "neutron port-list -f json -c fixed_ips -c device_id"))
142
- fixed_ips = None
137
+ conn = OpenStack().conn
138
+ subnets = [subnet.id for subnet in conn.network.subnets() if subnet.ip_version == 4]
139
+ if not subnets:
140
+ raise Exception("No subnet with ip_version == 4 found")
141
+ ports = conn.network.ports(device_id=self['id'])
143
142
  for port in ports:
144
- if port['device_id'] == self['id']:
145
- fixed_ips = port['fixed_ips'].split("\n")
146
- break
147
- if not fixed_ips:
148
- raise Exception("no fixed ip record found")
149
- ip = None
150
- for fixed_ip in fixed_ips:
151
- record = json.loads(fixed_ip)
152
- if record['subnet_id'] in subnet_ids:
153
- ip = record['ip_address']
154
- break
155
- if not ip:
156
- raise Exception("no ip")
157
- return ip
143
+ for fixed_ip in port.fixed_ips:
144
+ if fixed_ip.get('subnet_id') in subnets:
145
+ return fixed_ip['ip_address']
146
+
147
+ raise Exception("No IP found for instance")
158
148
 
159
149
  def get_ip(self, network):
160
150
  """
@@ -212,6 +202,7 @@ class OpenStack(object):
212
202
  image2url = {
213
203
  'centos-7.2-x86_64': 'http://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud-1511.qcow2',
214
204
  'centos-7.3-x86_64': 'http://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud-1701.qcow2',
205
+ 'centos-9.stream-x86_64': 'https://cloud.centos.org/centos/9-stream/x86_64/images/CentOS-Stream-GenericCloud-9-20240703.1.x86_64.qcow2',
215
206
  'opensuse-42.1-x86_64': 'http://download.opensuse.org/repositories/Cloud:/Images:/Leap_42.1/images/openSUSE-Leap-42.1-OpenStack.x86_64.qcow2',
216
207
  'opensuse-42.2-x86_64': 'http://download.opensuse.org/repositories/Cloud:/Images:/Leap_42.2/images/openSUSE-Leap-42.2-OpenStack.x86_64.qcow2',
217
208
  'opensuse-42.3-x86_64': 'http://download.opensuse.org/repositories/Cloud:/Images:/Leap_42.3/images/openSUSE-Leap-42.3-OpenStack.x86_64.qcow2',
@@ -221,6 +212,14 @@ class OpenStack(object):
221
212
  'ubuntu-16.04-x86_64': 'https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-amd64-disk1.img',
222
213
  'ubuntu-16.04-aarch64': 'https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-arm64-disk1.img',
223
214
  'ubuntu-16.04-i686': 'https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-i386-disk1.img',
215
+ 'ubuntu-18.04-x86_64': 'https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img',
216
+ 'ubuntu-18.04-aarch64': 'https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-arm64.img',
217
+ 'ubuntu-18.04-i686': 'https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-i386.img',
218
+ 'ubuntu-20.04-x86_64': 'https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img',
219
+ 'ubuntu-20.04-aarch64': 'https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-arm64.img',
220
+ 'ubuntu-22.04-x86_64': 'https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img',
221
+ 'ubuntu-22.04-aarch64': 'https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-arm64.img',
222
+ 'ubuntu-24.04-x86_64': 'https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img',
224
223
  'debian-8.0-x86_64': 'http://cdimage.debian.org/cdimage/openstack/current/debian-8.7.1-20170215-openstack-amd64.qcow2',
225
224
  }
226
225
 
@@ -230,11 +229,15 @@ class OpenStack(object):
230
229
  self.username = 'ubuntu'
231
230
  self.up_string = "UNKNOWN"
232
231
  self.teuthology_suite = 'teuthology-suite'
232
+ self.conn = self._create_connection()
233
233
 
234
234
  token = None
235
235
  token_expires = None
236
236
  token_cache_duration = 3600
237
237
 
238
+ def _create_connection(self):
239
+ return openstack_connection.from_config(cloud=None)
240
+
238
241
  def cache_token(self):
239
242
  if self.provider != 'ovh':
240
243
  return False
@@ -348,9 +351,10 @@ class OpenStack(object):
348
351
  """
349
352
  Return the uuid of the network in OpenStack.
350
353
  """
351
- r = json.loads(self.run("network show -f json " +
352
- network))
353
- return self.get_value(r, 'id')
354
+ conn = self.conn
355
+ network = conn.network.find_network(network)
356
+ if network:
357
+ return network.id
354
358
 
355
359
  def type_version_arch(self, os_type, os_version, arch):
356
360
  """
@@ -409,11 +413,10 @@ class OpenStack(object):
409
413
 
410
414
  @staticmethod
411
415
  def sort_flavors(flavors):
412
- def sort_flavor(a, b):
413
- return (a['VCPUs'] - b['VCPUs'] or
414
- a['RAM'] - b['RAM'] or
415
- a['Disk'] - b['Disk'])
416
- return sorted(flavors, cmp=sort_flavor)
416
+ def sort_key(flavor):
417
+ # Create a tuple for sorting: (VCPUs, RAM, Disk)
418
+ return (flavor['VCPUs'], flavor['RAM'], flavor['Disk'])
419
+ return sorted(flavors, key=sort_key)
417
420
 
418
421
  def get_os_flavors(self):
419
422
  flavors = json.loads(self.run("flavor list -f json"))
@@ -519,7 +522,7 @@ class OpenStack(object):
519
522
  result = copy.deepcopy(defaults)
520
523
  if not hints:
521
524
  return result
522
- if type(hints) is types.DictType:
525
+ if isinstance(hints, dict):
523
526
  raise TypeError("openstack: " + str(hints) +
524
527
  " must be an array, not a dict")
525
528
  for hint in hints:
@@ -533,19 +536,21 @@ class OpenStack(object):
533
536
 
534
537
  @staticmethod
535
538
  def list_instances():
539
+ conn = OpenStack().conn
536
540
  ownedby = "ownedby='" + teuth_config.openstack['ip'] + "'"
537
- all = json.loads(OpenStack().run(
538
- "server list -f json --long --name 'target'"))
539
- return filter(lambda instance: ownedby in instance['Properties'], all)
541
+ instances = conn.compute.servers(all_projects=True)
542
+ return [inst for inst in instances if ownedby in (getattr(inst, 'metadata', {}) or {}).get('Properties', '')]
540
543
 
541
544
  @staticmethod
542
545
  def list_volumes():
546
+ conn = OpenStack().conn
543
547
  ownedby = "ownedby='" + teuth_config.openstack['ip'] + "'"
544
- all = json.loads(OpenStack().run("volume list -f json --long"))
548
+ volumes = conn.block_storage.volumes()
545
549
  def select(volume):
546
- return (ownedby in volume['Properties'] and
547
- volume['Display Name'].startswith('target'))
548
- return filter(select, all)
550
+ props = volume.metadata or {}
551
+ return (ownedby in props.get('Properties', '') and
552
+ props.get('display_name', '').startswith('target'))
553
+ return filter(select, volumes)
549
554
 
550
555
  def cloud_init_wait(self, instance):
551
556
  """
@@ -564,12 +569,11 @@ class OpenStack(object):
564
569
  with safe_while(sleep=30, tries=30,
565
570
  action="cloud_init_wait " + ip) as proceed:
566
571
  success = False
567
- # CentOS 6.6 logs in /var/log/clout-init-output.log
568
- # CentOS 7.0 logs in /var/log/clout-init.log
569
572
  tail = ("tail --follow=name --retry"
570
573
  " /var/log/cloud-init*.log /tmp/init.out")
571
574
  while proceed():
572
575
  try:
576
+ log.debug("Attempting to connect to instance at IP: " + ip)
573
577
  client = connection.connect(**client_args)
574
578
  except paramiko.PasswordRequiredException:
575
579
  raise Exception(
@@ -611,15 +615,17 @@ class OpenStack(object):
611
615
  break
612
616
  except socket.timeout:
613
617
  client.close()
614
- log.debug('cloud_init_wait socket.timeout ' + tail)
615
618
  continue
616
- except socket.error as e:
619
+ except socket.error:
617
620
  client.close()
618
- log.debug('cloud_init_wait socket.error ' + str(e) + ' ' + tail)
619
621
  continue
620
- client.close()
622
+ finally:
623
+ client.close()
621
624
  if success:
625
+ log.debug('Cloud-init completed successfully for IP: ' + ip)
622
626
  break
627
+ if not success:
628
+ log.debug('Cloud-init did not complete successfully within the given retries.')
623
629
  return success
624
630
 
625
631
  def get_ip(self, instance_id, network):
@@ -899,7 +905,7 @@ class TeuthologyOpenStack(OpenStack):
899
905
  if ceph_repo:
900
906
  command = (
901
907
  "perl -pi -e 's|.*{opt}.*|{opt}: {value}|'"
902
- " ~/.teuthology.yaml"
908
+ " ~/.teuthology.yaml || true"
903
909
  ).format(opt='ceph_git_url', value=ceph_repo)
904
910
  self.ssh(command)
905
911
  user_home = '/home/' + self.username
@@ -914,6 +920,7 @@ class TeuthologyOpenStack(OpenStack):
914
920
  " --machine-type openstack " +
915
921
  " ".join(map(lambda x: "'" + x + "'", argv))
916
922
  )
923
+ log.info("Running teuthology-suite: " + command)
917
924
  return self.ssh(command)
918
925
 
919
926
  def reminders(self):
@@ -951,7 +958,7 @@ ssh access : ssh {identity}{username}@{ip} # logs in /usr/share/nginx/
951
958
  logging.getLogger("paramiko.transport").setLevel(logging.DEBUG)
952
959
  teuthology.log.setLevel(loglevel)
953
960
 
954
- def ssh(self, command):
961
+ def ssh(self, command, timeout=300):
955
962
  """
956
963
  Run a command in the OpenStack instance of the teuthology cluster.
957
964
  Return the stdout / stderr of the command.
@@ -969,14 +976,27 @@ ssh access : ssh {identity}{username}@{ip} # logs in /usr/share/nginx/
969
976
  # get the I/O channel to iterate line by line
970
977
  transport = client.get_transport()
971
978
  channel = transport.open_session()
972
- channel.get_pty()
973
- channel.settimeout(900)
974
- output = channel.makefile('r', 1)
975
- log.debug(":ssh@" + ip + ":" + command)
979
+ channel.settimeout(timeout)
980
+ log.debug(f"ssh {self.instance.get_floating_ip_or_ip()}: {command}")
976
981
  channel.exec_command(command)
977
- for line in iter(output.readline, b''):
978
- log.info(line.strip())
979
- return channel.recv_exit_status()
982
+ stdout, stderr = [], []
983
+ start_time = time.time()
984
+ while True:
985
+ if channel.recv_ready():
986
+ stdout.append(channel.recv(4096).decode())
987
+ if channel.recv_stderr_ready():
988
+ stderr.append(channel.recv_stderr(4096).decode())
989
+ if channel.exit_status_ready():
990
+ break
991
+ if time.time() - start_time > timeout:
992
+ raise TimeoutError("SSH command timed out!")
993
+ time.sleep(0.1) # Small sleep to avoid busy waiting
994
+ exit_status = channel.recv_exit_status()
995
+ stdout_txt, stderr_txt = ''.join(stdout), ''.join(stderr)
996
+ if exit_status != 0:
997
+ log.warning(f"SSH command failed with exit status {exit_status}")
998
+ return exit_status, stdout_txt, stderr_txt
999
+
980
1000
 
981
1001
  def verify_openstack(self):
982
1002
  """
@@ -1029,32 +1049,48 @@ ssh access : ssh {identity}{username}@{ip} # logs in /usr/share/nginx/
1029
1049
  fd, path = tempfile.mkstemp()
1030
1050
  os.close(fd)
1031
1051
 
1032
- with open(os.path.dirname(__file__) + '/bootstrap-teuthology.sh', 'rb') as f:
1052
+ bootstrap_path = os.getcwd() + "/teuthology/openstack" + '/bootstrap-teuthology.sh'
1053
+ with open(bootstrap_path, 'rb') as f:
1033
1054
  b64_bootstrap = base64.b64encode(f.read())
1034
1055
  bootstrap_content = str(b64_bootstrap.decode())
1035
1056
 
1036
1057
  openrc_sh = ''
1037
1058
  cacert_cmd = None
1038
- for (var, value) in os.environ.items():
1039
- if var in ('OS_TOKEN_VALUE', 'OS_TOKEN_EXPIRES'):
1040
- continue
1041
- if var == 'OS_CACERT':
1042
- cacert_path = '/home/%s/.openstack.crt' % self.username
1043
- cacert_file = value
1044
- openrc_sh += 'export %s=%s\n' % (var, cacert_path)
1045
- cacert_cmd = (
1046
- "su - -c 'cat > {path}' {user} <<EOF\n"
1047
- "{data}\n"
1048
- "EOF\n").format(
1049
- path=cacert_path,
1050
- user=self.username,
1051
- data=open(cacert_file).read())
1052
- elif var.startswith('OS_'):
1053
- openrc_sh += 'export %s=%s\n' % (var, value)
1059
+ clouds_yaml_path = os.path.expanduser('~/.config/openstack/clouds.yaml')
1060
+ if os.path.exists(clouds_yaml_path):
1061
+ log.debug(f"clouds.yaml found at {clouds_yaml_path}, processing for openrc.sh")
1062
+ with open(clouds_yaml_path, 'r') as f:
1063
+ clouds_data = yaml.safe_load(f)
1064
+ cloud_name = os.environ.get('OS_CLOUD', 'default')
1065
+ cloud_config = clouds_data.get('clouds', {}).get(cloud_name, {})
1066
+ if not cloud_config:
1067
+ raise Exception(f"Cloud '{cloud_name}' not found in clouds.yaml")
1068
+ auth = cloud_config.get('auth', {})
1069
+ for key, value in {**auth, **cloud_config}.items():
1070
+ if isinstance(value, str):
1071
+ openrc_sh += f"export OS_{key.upper()}={value}\n"
1072
+ else:
1073
+ for (var, value) in os.environ.items():
1074
+ if var in ('OS_TOKEN_VALUE', 'OS_TOKEN_EXPIRES'):
1075
+ continue
1076
+ if var == 'OS_CACERT':
1077
+ cacert_path = '/home/%s/.openstack.crt' % self.username
1078
+ cacert_file = value
1079
+ openrc_sh += 'export %s=%s\n' % (var, cacert_path)
1080
+ cacert_cmd = (
1081
+ "su - -c 'cat > {path}' {user} <<EOF\n"
1082
+ "{data}\n"
1083
+ "EOF\n").format(
1084
+ path=cacert_path,
1085
+ user=self.username,
1086
+ data=open(cacert_file).read())
1087
+ elif var.startswith('OS_'):
1088
+ openrc_sh += 'export %s=%s\n' % (var, value)
1054
1089
  b64_openrc_sh = base64.b64encode(openrc_sh.encode())
1055
1090
  openrc_sh_content = str(b64_openrc_sh.decode())
1056
1091
 
1057
1092
  network = OpenStack().get_network()
1093
+ log.debug(f"Network to be used: {network}")
1058
1094
  ceph_workbench = ''
1059
1095
  if self.args.ceph_workbench_git_url:
1060
1096
  ceph_workbench += (" --ceph-workbench-branch " +
@@ -1069,21 +1105,19 @@ ssh access : ssh {identity}{username}@{ip} # logs in /usr/share/nginx/
1069
1105
  '--server-group %s' % self.server_group(),
1070
1106
  '--worker-group %s' % self.worker_group(),
1071
1107
  '--package-repo %s' % self.packages_repository(),
1072
- #'--setup-all',
1073
1108
  ]
1109
+ log.debug(f"Setup options: {setup_options}")
1110
+
1074
1111
  all_options = [
1075
- '--install', #do_install_packages=true
1076
- #'--setup-ceph-workbench', #do_ceph_workbench=true
1077
- '--config', #do_create_config=true
1078
- '--setup-keypair', #do_setup_keypair=true
1079
- #'', #do_apt_get_update=true
1080
- '--setup-docker', #do_setup_docker=true
1081
- '--setup-salt-master', #do_setup_salt_master=true
1082
- '--setup-dnsmasq', #do_setup_dnsmasq=true
1083
- '--setup-fail2ban', #do_setup_fail2ban=true
1084
- '--setup-paddles', #do_setup_paddles=true
1085
- '--setup-pulpito', #do_setup_pulpito=true
1086
- '--populate-paddles', #do_populate_paddles=true
1112
+ '--install',
1113
+ '--config',
1114
+ '--setup-docker',
1115
+ '--setup-salt-master',
1116
+ '--setup-dnsmasq',
1117
+ '--setup-fail2ban',
1118
+ '--setup-paddles',
1119
+ '--setup-pulpito',
1120
+ '--populate-paddles',
1087
1121
  ]
1088
1122
 
1089
1123
  if self.args.ceph_workbench_git_url:
@@ -1122,8 +1156,6 @@ ssh access : ssh {identity}{username}@{ip} # logs in /usr/share/nginx/
1122
1156
  "{user} >> /tmp/init.out "
1123
1157
  "2>&1".format(user=self.username,
1124
1158
  opts=' '.join(setup_options + all_options))),
1125
- # wa: we want to stop paddles and pulpito started by
1126
- # setup-openstack before starting teuthology service
1127
1159
  "pkill -f 'pecan serve'",
1128
1160
  "pkill -f 'python run.py'",
1129
1161
  "systemctl enable teuthology",
@@ -1143,11 +1175,6 @@ ssh access : ssh {identity}{username}@{ip} # logs in /usr/share/nginx/
1143
1175
  'name': self.username
1144
1176
  }
1145
1177
  },
1146
- 'packages': [
1147
- 'python-virtualenv',
1148
- 'git',
1149
- 'rsync',
1150
- ],
1151
1178
  'write_files': [
1152
1179
  {
1153
1180
  'path': '/tmp/bootstrap-teuthology.sh',
@@ -1163,7 +1190,9 @@ ssh access : ssh {identity}{username}@{ip} # logs in /usr/share/nginx/
1163
1190
  'permissions': '0644',
1164
1191
  }
1165
1192
  ],
1166
- 'runcmd': cmds,
1193
+ 'runcmd': [
1194
+ 'apt-get update && apt-get install -y python3-virtualenv git rsync >> /tmp/init.out 2>&1'
1195
+ ] + cmds,
1167
1196
  'final_message': 'teuthology is up and running after $UPTIME seconds'
1168
1197
  }
1169
1198
  user_data = "#cloud-config\n%s" % \
@@ -1186,36 +1215,41 @@ ssh access : ssh {identity}{username}@{ip} # logs in /usr/share/nginx/
1186
1215
  return "teuth-%s-worker" % self.args.name
1187
1216
 
1188
1217
  def create_security_group(self):
1189
- """
1190
- Create a security group that will be used by all teuthology
1191
- created instances. This should not be necessary in most cases
1192
- but some OpenStack providers enforce firewall restrictions even
1193
- among instances created within the same tenant.
1194
- """
1195
- groups = misc.sh('openstack security group list -c Name -f value').split('\n')
1196
- if all(g in groups for g in [self.server_group(), self.worker_group()]):
1197
- return
1198
- misc.sh("""
1199
- openstack security group delete {server} || true
1200
- openstack security group delete {worker} || true
1201
- openstack security group create {server}
1202
- openstack security group create {worker}
1203
- # access to teuthology VM from the outside
1204
- openstack security group rule create --proto tcp --dst-port 22 {server} # ssh
1205
- openstack security group rule create --proto tcp --dst-port 80 {server} # for log access
1206
- openstack security group rule create --proto tcp --dst-port 8080 {server} # pulpito
1207
- openstack security group rule create --proto tcp --dst-port 8081 {server} # paddles
1208
- # access between teuthology and workers
1209
- openstack security group rule create --src-group {worker} --dst-port 1:65535 {server}
1210
- openstack security group rule create --protocol udp --src-group {worker} --dst-port 1:65535 {server}
1211
- openstack security group rule create --src-group {server} --dst-port 1:65535 {worker}
1212
- openstack security group rule create --protocol udp --src-group {server} --dst-port 1:65535 {worker}
1213
- # access between members of one group
1214
- openstack security group rule create --src-group {worker} --dst-port 1:65535 {worker}
1215
- openstack security group rule create --protocol udp --src-group {worker} --dst-port 1:65535 {worker}
1216
- openstack security group rule create --src-group {server} --dst-port 1:65535 {server}
1217
- openstack security group rule create --protocol udp --src-group {server} --dst-port 1:65535 {server}
1218
- """.format(server=self.server_group(), worker=self.worker_group()))
1218
+ conn = OpenStack().conn
1219
+ server_sg = conn.network.find_security_group(self.server_group())
1220
+ worker_sg = conn.network.find_security_group(self.worker_group())
1221
+ if not server_sg:
1222
+ server_sg = conn.network.create_security_group(name=self.server_group())
1223
+ if not worker_sg:
1224
+ worker_sg = conn.network.create_security_group(name=self.worker_group())
1225
+ def add_rule(sg_id, protocol, port=None, remote_group_id=None):
1226
+ rule_args = {
1227
+ 'security_group_id': sg_id,
1228
+ 'direction': 'ingress',
1229
+ 'protocol': protocol,
1230
+ 'ethertype': 'IPv4',
1231
+ }
1232
+ if port is not None:
1233
+ rule_args['port_range_min'] = rule_args['port_range_max'] = port
1234
+ if remote_group_id:
1235
+ rule_args['remote_group_id'] = remote_group_id
1236
+ else:
1237
+ rule_args['remote_ip_prefix'] = '0.0.0.0/0'
1238
+ try:
1239
+ conn.network.create_security_group_rule(**rule_args)
1240
+ except Exception as e:
1241
+ log.warning(f"Security group rule creation skipped or failed: {e}")
1242
+ # tcp access to enable reliable inter-node communication
1243
+ for sg in (server_sg, worker_sg):
1244
+ add_rule(sg.id, 'tcp')
1245
+ # access between teuthology and workers
1246
+ for port in (65535,):
1247
+ add_rule(worker_sg.id, 'udp', port=port, remote_group_id=server_sg.id)
1248
+ add_rule(server_sg.id, 'udp', port=port, remote_group_id=worker_sg.id)
1249
+ # access between members of one group
1250
+ add_rule(server_sg.id, 'udp', port=65535, remote_group_id=server_sg.id)
1251
+ # access within worker group
1252
+ add_rule(worker_sg.id, 'udp', port=65535, remote_group_id=worker_sg.id)
1219
1253
 
1220
1254
  @staticmethod
1221
1255
  def get_unassociated_floating_ip():
@@ -1230,32 +1264,14 @@ openstack security group rule create --protocol udp --src-group {server} --dst-p
1230
1264
 
1231
1265
  @staticmethod
1232
1266
  def create_floating_ip():
1233
- try:
1234
- pools = json.loads(OpenStack().run("ip floating pool list -f json"))
1235
- except subprocess.CalledProcessError as e:
1236
- if 'Floating ip pool operations are only available for Compute v2 network.' \
1237
- in e.output:
1238
- log.debug(e.output)
1239
- log.debug('Trying newer API than Compute v2')
1240
- try:
1241
- network = 'floating'
1242
- ip = json.loads(misc.sh("openstack --quiet floating ip create -f json '%s'" % network))
1243
- return ip['floating_ip_address']
1244
- except subprocess.CalledProcessError:
1245
- log.debug("Can't create floating ip for network '%s'" % network)
1246
-
1247
- log.debug("create_floating_ip: ip floating pool list failed")
1248
- return None
1249
- if not pools:
1267
+ conn = OpenStack().conn
1268
+ network_name = 'floating'
1269
+ network = conn.network.find_network(network_name)
1270
+ if not network:
1271
+ log.debug(f"Floating network {network_name} not found.")
1250
1272
  return None
1251
- pool = pools[0]['Name']
1252
- try:
1253
- ip = json.loads(OpenStack().run(
1254
- "ip floating create -f json '" + pool + "'"))
1255
- return ip['ip']
1256
- except subprocess.CalledProcessError:
1257
- log.debug("create_floating_ip: not creating a floating ip")
1258
- return None
1273
+ floating_ip = conn.network.create_ip(floating_network_id=network.id)
1274
+ return floating_ip.floating_ip_address
1259
1275
 
1260
1276
  @staticmethod
1261
1277
  def associate_floating_ip(name_or_id):
@@ -1263,23 +1279,18 @@ openstack security group rule create --protocol udp --src-group {server} --dst-p
1263
1279
  Associate a floating IP to the OpenStack instance
1264
1280
  or do nothing if no floating ip can be created.
1265
1281
  """
1266
- ip = TeuthologyOpenStack.get_unassociated_floating_ip()
1267
- if not ip:
1268
- ip = TeuthologyOpenStack.create_floating_ip()
1269
- if ip:
1270
- OpenStack().run("ip floating add " + ip + " " + name_or_id)
1282
+ conn = OpenStack().conn
1283
+ server = conn.compute.find_server(name_or_id)
1284
+ ip_address = TeuthologyOpenStack.get_unassociated_floating_ip()
1285
+ if not ip_address:
1286
+ ip_address = TeuthologyOpenStack.create_floating_ip()
1287
+ if ip_address:
1288
+ conn.compute.add_floating_ip_to_server(server, ip_address)
1271
1289
 
1272
1290
  @staticmethod
1273
1291
  def get_os_floating_ips():
1274
- try:
1275
- ips = json.loads(OpenStack().run("ip floating list -f json"))
1276
- except subprocess.CalledProcessError as e:
1277
- log.warning(e)
1278
- if e.returncode == 1:
1279
- return []
1280
- else:
1281
- raise e
1282
- return ips
1292
+ conn = OpenStack().conn
1293
+ return list(conn.network.ips())
1283
1294
 
1284
1295
  @staticmethod
1285
1296
  def get_floating_ip_id(ip):
@@ -1307,12 +1318,17 @@ openstack security group rule create --protocol udp --src-group {server} --dst-p
1307
1318
  """
1308
1319
  Remove the floating ip from instance_id and delete it.
1309
1320
  """
1310
- ip = OpenStackInstance(instance_id).get_floating_ip()
1311
- if not ip:
1321
+ conn = OpenStack().conn
1322
+ server = conn.compute.find_server(instance_id)
1323
+ if not server:
1324
+ return
1325
+ ip_address = OpenStackInstance(instance_id).get_floating_ip()
1326
+ if not ip_address:
1312
1327
  return
1313
- OpenStack().run("ip floating remove " + ip + " " + instance_id)
1314
- ip_id = TeuthologyOpenStack.get_floating_ip_id(ip)
1315
- OpenStack().run("ip floating delete " + ip_id)
1328
+ conn.compute.remove_floating_ip_from_server(server, ip_address)
1329
+ floating_ip_obj = conn.network.find_ip(ip_address)
1330
+ if floating_ip_obj:
1331
+ conn.network.delete_ip(floating_ip_obj)
1316
1332
 
1317
1333
  def create_cluster(self):
1318
1334
  user_data = self.get_user_data()
@@ -1329,19 +1345,29 @@ openstack security group rule create --protocol udp --src-group {server} --dst-p
1329
1345
  if not key_name:
1330
1346
  raise Exception('No key name provided, use --key-name option')
1331
1347
  log.debug('Using key name: %s' % self.args.key_name)
1332
- self.run(
1348
+ image_name = self.image('ubuntu', '22.04', arch)
1349
+ log.debug("Using image: %s" % image_name)
1350
+ net_config = self.net()
1351
+ try:
1352
+ self.run(
1333
1353
  "server create " +
1334
- " --image '" + self.image('ubuntu', '16.04', arch) + "' " +
1354
+ " --image '" + image_name + "' " +
1335
1355
  " --flavor '" + flavor + "' " +
1336
- " " + self.net() +
1356
+ " " + net_config +
1337
1357
  " --key-name " + key_name +
1338
1358
  " --user-data " + user_data +
1339
1359
  security_group +
1340
1360
  " --wait " + self.server_name() +
1341
1361
  " -f json")
1342
- os.unlink(user_data)
1362
+ except Exception as e:
1363
+ log.error("Error during server creation: %s" % str(e))
1364
+ raise
1365
+ finally:
1366
+ os.unlink(user_data)
1343
1367
  self.instance = OpenStackInstance(self.server_name())
1368
+ log.debug("OpenStackInstance created for server: %s" % self.server_name())
1344
1369
  self.associate_floating_ip(self.instance['id'])
1370
+ log.debug("Floating IP associated for instance ID: %s" % self.instance.get('id'))
1345
1371
  return self.cloud_init_wait(self.instance)
1346
1372
 
1347
1373
  def packages_repository(self):