Path: blob/main/singlestoredb/tests/test_management.py
469 views
#!/usr/bin/env python1# type: ignore2"""SingleStoreDB Management API testing."""3import os4import pathlib5import random6import re7import secrets8import unittest910import pytest1112import singlestoredb as s213from singlestoredb.management.job import Status14from singlestoredb.management.job import TargetType15from singlestoredb.management.region import Region16from singlestoredb.management.utils import NamedList171819TEST_DIR = pathlib.Path(os.path.dirname(__file__))202122def clean_name(s):23"""Change all non-word characters to -."""24return re.sub(r'[^\w]', r'-', s).replace('_', '-').lower()252627def shared_database_name(s):28"""Return a shared database name. Cannot contain special characters except -"""29return re.sub(r'[^\w]', '', s).replace('-', '_').lower()303132@pytest.mark.management33class TestCluster(unittest.TestCase):3435manager = None36cluster = None37password = None3839@classmethod40def setUpClass(cls):41cls.manager = s2.manage_cluster()4243us_regions = [x for x in cls.manager.regions if 'US' in x.name]44cls.password = secrets.token_urlsafe(20) + '-x&$'4546cls.cluster = cls.manager.create_cluster(47clean_name('cm-test-{}'.format(secrets.token_urlsafe(20)[:20])),48region=random.choice(us_regions).id,49admin_password=cls.password,50firewall_ranges=['0.0.0.0/0'],51expires_at='1h',52size='S-00',53wait_on_active=True,54)5556@classmethod57def tearDownClass(cls):58if cls.cluster is not None:59cls.cluster.terminate()60cls.cluster = None61cls.manager = None62cls.password = None6364def test_str(self):65assert self.cluster.name in str(self.cluster.name)6667def test_repr(self):68assert repr(self.cluster) == str(self.cluster)6970def test_region_str(self):71s = str(self.cluster.region)72assert 'Azure' in s or 'GCP' in s or 'AWS' in s, s7374def test_region_repr(self):75assert repr(self.cluster.region) == str(self.cluster.region)7677def test_regions(self):78out = self.manager.regions79providers = {x.provider for x in out}80names = [x.name for x in out]81assert 'Azure' in providers, providers82assert 'GCP' in providers, providers83assert 'AWS' in providers, providers8485objs = {}86ids = []87for item in out:88ids.append(item.id)89objs[item.id] = item90if item.name not in objs:91objs[item.name] = item9293name = random.choice(names)94assert out[name] == objs[name]95id = random.choice(ids)96assert out[id] == objs[id]9798def test_clusters(self):99clusters = self.manager.clusters100ids = [x.id for x in clusters]101assert self.cluster.id in ids, ids102103def test_get_cluster(self):104clus = self.manager.get_cluster(self.cluster.id)105assert clus.id == self.cluster.id, clus.id106107with self.assertRaises(s2.ManagementError) as cm:108clus = self.manager.get_cluster('bad id')109110assert 'UUID' in cm.exception.msg, cm.exception.msg111112def test_update(self):113assert self.cluster.name.startswith('cm-test-')114115name = self.cluster.name.replace('cm-test-', 'cm-foo-')116self.cluster.update(name=name)117118clus = self.manager.get_cluster(self.cluster.id)119assert clus.name == name, clus.name120121def test_suspend_resume(self):122trues = ['1', 'on', 'true']123do_test = os.environ.get('SINGLESTOREDB_TEST_SUSPEND', '0').lower() in trues124125if not do_test:126self.skipTest(127'Suspend / resume tests skipped by default due to '128'being time consuming; set SINGLESTOREDB_TEST_SUSPEND=1 '129'to enable',130)131132assert self.cluster.state != 'Suspended', self.cluster.state133134self.cluster.suspend(wait_on_suspended=True)135assert self.cluster.state == 'Suspended', self.cluster.state136137self.cluster.resume(wait_on_resumed=True)138assert self.cluster.state == 'Active', self.cluster.state139140def test_no_manager(self):141clus = self.manager.get_cluster(self.cluster.id)142clus._manager = None143144with self.assertRaises(s2.ManagementError) as cm:145clus.refresh()146147assert 'No cluster manager' in cm.exception.msg, cm.exception.msg148149with self.assertRaises(s2.ManagementError) as cm:150clus.update()151152assert 'No cluster manager' in cm.exception.msg, cm.exception.msg153154with self.assertRaises(s2.ManagementError) as cm:155clus.suspend()156157assert 'No cluster manager' in cm.exception.msg, cm.exception.msg158159with self.assertRaises(s2.ManagementError) as cm:160clus.resume()161162assert 'No cluster manager' in cm.exception.msg, cm.exception.msg163164with self.assertRaises(s2.ManagementError) as cm:165clus.terminate()166167assert 'No cluster manager' in cm.exception.msg, cm.exception.msg168169def test_connect(self):170trues = ['1', 'on', 'true']171pure_python = os.environ.get('SINGLESTOREDB_PURE_PYTHON', '0').lower() in trues172173self.skipTest('Connection test is disable due to flakey server')174175if pure_python:176self.skipTest('Connections through managed service are disabled')177178try:179with self.cluster.connect(user='admin', password=self.password) as conn:180with conn.cursor() as cur:181cur.execute('show databases')182assert 'cluster' in [x[0] for x in list(cur)]183except s2.ManagementError as exc:184if 'endpoint has not been set' not in str(exc):185self.skipTest('No endpoint in response. Skipping connection test.')186187# Test missing endpoint188clus = self.manager.get_cluster(self.cluster.id)189clus.endpoint = None190191with self.assertRaises(s2.ManagementError) as cm:192clus.connect(user='admin', password=self.password)193194assert 'endpoint' in cm.exception.msg, cm.exception.msg195196197@pytest.mark.management198class TestWorkspace(unittest.TestCase):199200manager = None201workspace_group = None202workspace = None203password = None204205@classmethod206def setUpClass(cls):207cls.manager = s2.manage_workspaces()208209us_regions = [x for x in cls.manager.regions if 'US' in x.name]210cls.password = secrets.token_urlsafe(20) + '-x&$'211212name = clean_name(secrets.token_urlsafe(20)[:20])213214cls.workspace_group = cls.manager.create_workspace_group(215f'wg-test-{name}',216region=random.choice(us_regions).id,217admin_password=cls.password,218firewall_ranges=['0.0.0.0/0'],219)220221try:222cls.workspace = cls.workspace_group.create_workspace(223f'ws-test-{name}-x',224wait_on_active=True,225)226except Exception:227cls.workspace_group.terminate(force=True)228raise229230@classmethod231def tearDownClass(cls):232if cls.workspace_group is not None:233cls.workspace_group.terminate(force=True)234cls.workspace_group = None235cls.workspace = None236cls.manager = None237cls.password = None238239def test_str(self):240assert self.workspace.name in str(self.workspace.name)241assert self.workspace_group.name in str(self.workspace_group.name)242243def test_repr(self):244assert repr(self.workspace) == str(self.workspace)245assert repr(self.workspace_group) == str(self.workspace_group)246247def test_region_str(self):248s = str(self.workspace_group.region)249assert 'Azure' in s or 'GCP' in s or 'AWS' in s, s250251def test_region_repr(self):252assert repr(self.workspace_group.region) == str(self.workspace_group.region)253254def test_regions(self):255out = self.manager.regions256providers = {x.provider for x in out}257names = [x.name for x in out]258assert 'Azure' in providers, providers259assert 'GCP' in providers, providers260assert 'AWS' in providers, providers261262objs = {}263ids = []264for item in out:265ids.append(item.id)266objs[item.id] = item267if item.name not in objs:268objs[item.name] = item269270name = random.choice(names)271assert out[name] == objs[name]272id = random.choice(ids)273assert out[id] == objs[id]274275def test_workspace_groups(self):276workspace_groups = self.manager.workspace_groups277ids = [x.id for x in workspace_groups]278names = [x.name for x in workspace_groups]279assert self.workspace_group.id in ids280assert self.workspace_group.name in names281282assert workspace_groups.ids() == ids283assert workspace_groups.names() == names284285objs = {}286for item in workspace_groups:287objs[item.id] = item288objs[item.name] = item289290name = random.choice(names)291assert workspace_groups[name] == objs[name]292id = random.choice(ids)293assert workspace_groups[id] == objs[id]294295def test_workspaces(self):296spaces = self.workspace_group.workspaces297ids = [x.id for x in spaces]298names = [x.name for x in spaces]299assert self.workspace.id in ids300assert self.workspace.name in names301302assert spaces.ids() == ids303assert spaces.names() == names304305objs = {}306for item in spaces:307objs[item.id] = item308objs[item.name] = item309310name = random.choice(names)311assert spaces[name] == objs[name]312id = random.choice(ids)313assert spaces[id] == objs[id]314315def test_get_workspace_group(self):316group = self.manager.get_workspace_group(self.workspace_group.id)317assert group.id == self.workspace_group.id, group.id318319with self.assertRaises(s2.ManagementError) as cm:320group = self.manager.get_workspace_group('bad id')321322assert 'UUID' in cm.exception.msg, cm.exception.msg323324def test_get_workspace(self):325space = self.manager.get_workspace(self.workspace.id)326assert space.id == self.workspace.id, space.id327328with self.assertRaises(s2.ManagementError) as cm:329space = self.manager.get_workspace('bad id')330331assert 'UUID' in cm.exception.msg, cm.exception.msg332333def test_update(self):334assert self.workspace_group.name.startswith('wg-test-')335336name = self.workspace_group.name.replace('wg-test-', 'wg-foo-')337self.workspace_group.update(name=name)338339group = self.manager.get_workspace_group(self.workspace_group.id)340assert group.name == name, group.name341342def test_no_manager(self):343space = self.manager.get_workspace(self.workspace.id)344space._manager = None345346with self.assertRaises(s2.ManagementError) as cm:347space.refresh()348349assert 'No workspace manager' in cm.exception.msg, cm.exception.msg350351with self.assertRaises(s2.ManagementError) as cm:352space.terminate()353354assert 'No workspace manager' in cm.exception.msg, cm.exception.msg355356def test_connect(self):357with self.workspace.connect(user='admin', password=self.password) as conn:358with conn.cursor() as cur:359cur.execute('show databases')360assert 'cluster' in [x[0] for x in list(cur)]361362# Test missing endpoint363space = self.manager.get_workspace(self.workspace.id)364space.endpoint = None365366with self.assertRaises(s2.ManagementError) as cm:367space.connect(user='admin', password=self.password)368369assert 'endpoint' in cm.exception.msg, cm.exception.msg370371372@pytest.mark.management373class TestStarterWorkspace(unittest.TestCase):374375manager = None376starter_workspace = None377378@classmethod379def setUpClass(cls):380cls.manager = s2.manage_workspaces()381382shared_tier_regions: NamedList[Region] = [383x for x in cls.manager.shared_tier_regions if 'US' in x.name384]385cls.starter_username = 'starter_user'386cls.password = secrets.token_urlsafe(20)387388name = shared_database_name(secrets.token_urlsafe(20)[:20])389390cls.database_name = f'starter_db_{name}'391392shared_tier_region: Region = random.choice(shared_tier_regions)393394if not shared_tier_region:395raise ValueError('No shared tier regions found')396397cls.starter_workspace = cls.manager.create_starter_workspace(398f'starter-ws-test-{name}',399database_name=cls.database_name,400provider=shared_tier_region.provider,401region_name=shared_tier_region.region_name,402)403404cls.starter_workspace.create_user(405username=cls.starter_username,406password=cls.password,407)408409@classmethod410def tearDownClass(cls):411if cls.starter_workspace is not None:412cls.starter_workspace.terminate()413cls.manager = None414cls.password = None415416def test_str(self):417assert self.starter_workspace.name in str(self.starter_workspace.name)418419def test_repr(self):420assert repr(self.starter_workspace) == str(self.starter_workspace)421422def test_get_starter_workspace(self):423workspace = self.manager.get_starter_workspace(self.starter_workspace.id)424assert workspace.id == self.starter_workspace.id, workspace.id425426with self.assertRaises(s2.ManagementError) as cm:427workspace = self.manager.get_starter_workspace('bad id')428429assert 'UUID' in cm.exception.msg, cm.exception.msg430431def test_starter_workspaces(self):432workspaces = self.manager.starter_workspaces433ids = [x.id for x in workspaces]434names = [x.name for x in workspaces]435assert self.starter_workspace.id in ids436assert self.starter_workspace.name in names437438objs = {}439for item in workspaces:440objs[item.id] = item441objs[item.name] = item442443name = random.choice(names)444assert workspaces[name] == objs[name]445id = random.choice(ids)446assert workspaces[id] == objs[id]447448def test_no_manager(self):449workspace = self.manager.get_starter_workspace(self.starter_workspace.id)450workspace._manager = None451452with self.assertRaises(s2.ManagementError) as cm:453workspace.refresh()454455assert 'No workspace manager' in cm.exception.msg, cm.exception.msg456457with self.assertRaises(s2.ManagementError) as cm:458workspace.terminate()459460assert 'No workspace manager' in cm.exception.msg, cm.exception.msg461462def test_connect(self):463with self.starter_workspace.connect(464user=self.starter_username,465password=self.password,466) as conn:467with conn.cursor() as cur:468cur.execute('show databases')469assert self.database_name in [x[0] for x in list(cur)]470471# Test missing endpoint472workspace = self.manager.get_starter_workspace(self.starter_workspace.id)473workspace.endpoint = None474475with self.assertRaises(s2.ManagementError) as cm:476workspace.connect(user=self.starter_username, password=self.password)477478assert 'endpoint' in cm.exception.msg, cm.exception.msg479480481@pytest.mark.management482class TestStage(unittest.TestCase):483484manager = None485wg = None486password = None487488@classmethod489def setUpClass(cls):490cls.manager = s2.manage_workspaces()491492us_regions = [x for x in cls.manager.regions if 'US' in x.name]493cls.password = secrets.token_urlsafe(20) + '-x&$'494495name = clean_name(secrets.token_urlsafe(20)[:20])496497cls.wg = cls.manager.create_workspace_group(498f'wg-test-{name}',499region=random.choice(us_regions).id,500admin_password=cls.password,501firewall_ranges=['0.0.0.0/0'],502)503504@classmethod505def tearDownClass(cls):506if cls.wg is not None:507cls.wg.terminate(force=True)508cls.wg = None509cls.manager = None510cls.password = None511512def test_upload_file(self):513st = self.wg.stage514515upload_test_sql = f'upload_test_{id(self)}.sql'516upload_test2_sql = f'upload_test2_{id(self)}.sql'517518root = st.info('/')519assert str(root.path) == '/'520assert root.type == 'directory'521522# Upload file523f = st.upload_file(TEST_DIR / 'test.sql', upload_test_sql)524assert str(f.path) == upload_test_sql525assert f.type == 'file'526527# Download and compare to original528txt = f.download(encoding='utf-8')529assert txt == open(TEST_DIR / 'test.sql').read()530531# Make sure we can't overwrite532with self.assertRaises(OSError):533st.upload_file(TEST_DIR / 'test.sql', upload_test_sql)534535# Force overwrite with new content; use file object this time536f = st.upload_file(537open(TEST_DIR / 'test2.sql', 'r'),538upload_test_sql,539overwrite=True,540)541assert str(f.path) == upload_test_sql542assert f.type == 'file'543544# Verify new content545txt = f.download(encoding='utf-8')546assert txt == open(TEST_DIR / 'test2.sql').read()547548# Try to upload folder549with self.assertRaises(IsADirectoryError):550st.upload_file(TEST_DIR, 'test3.sql')551552lib = st.mkdir('/lib/')553assert str(lib.path) == 'lib/'554assert lib.type == 'directory'555556# Try to overwrite stage folder with file557with self.assertRaises(IsADirectoryError):558st.upload_file(TEST_DIR / 'test2.sql', lib.path, overwrite=True)559560# Write file into folder561f = st.upload_file(562TEST_DIR / 'test2.sql',563os.path.join(lib.path, upload_test2_sql),564)565assert str(f.path) == 'lib/' + upload_test2_sql566assert f.type == 'file'567568def test_open(self):569st = self.wg.stage570571open_test_sql = f'open_test_{id(self)}.sql'572573# See if error is raised for non-existent file574with self.assertRaises(s2.ManagementError):575st.open(open_test_sql, 'r')576577# Load test file578st.upload_file(TEST_DIR / 'test.sql', open_test_sql)579580# Read file using `open`581with st.open(open_test_sql, 'r') as rfile:582assert rfile.read() == open(TEST_DIR / 'test.sql').read()583584# Read file using `open` with 'rt' mode585with st.open(open_test_sql, 'rt') as rfile:586assert rfile.read() == open(TEST_DIR / 'test.sql').read()587588# Read file using `open` with 'rb' mode589with st.open(open_test_sql, 'rb') as rfile:590assert rfile.read() == open(TEST_DIR / 'test.sql', 'rb').read()591592# Read file using `open` with 'rb' mode593with self.assertRaises(ValueError):594with st.open(open_test_sql, 'b') as rfile:595pass596597# Attempt overwrite file using `open` with mode 'x'598with self.assertRaises(OSError):599with st.open(open_test_sql, 'x') as wfile:600pass601602# Attempt overwrite file using `open` with mode 'w'603with st.open(open_test_sql, 'w') as wfile:604wfile.write(open(TEST_DIR / 'test2.sql').read())605606txt = st.download_file(open_test_sql, encoding='utf-8')607608assert txt == open(TEST_DIR / 'test2.sql').read()609610open_raw_test_sql = f'open_raw_test_{id(self)}.sql'611612# Test writer without context manager613wfile = st.open(open_raw_test_sql, 'w')614for line in open(TEST_DIR / 'test.sql'):615wfile.write(line)616wfile.close()617618txt = st.download_file(open_raw_test_sql, encoding='utf-8')619620assert txt == open(TEST_DIR / 'test.sql').read()621622# Test reader without context manager623rfile = st.open(open_raw_test_sql, 'r')624txt = ''625for line in rfile:626txt += line627rfile.close()628629assert txt == open(TEST_DIR / 'test.sql').read()630631def test_obj_open(self):632st = self.wg.stage633634obj_open_test_sql = f'obj_open_test_{id(self)}.sql'635obj_open_dir = f'obj_open_dir_{id(self)}'636637# Load test file638f = st.upload_file(TEST_DIR / 'test.sql', obj_open_test_sql)639640# Read file using `open`641with f.open() as rfile:642assert rfile.read() == open(TEST_DIR / 'test.sql').read()643644# Make sure directories error out645d = st.mkdir(obj_open_dir)646with self.assertRaises(IsADirectoryError):647d.open()648649# Write file using `open`650with f.open('w', encoding='utf-8') as wfile:651wfile.write(open(TEST_DIR / 'test2.sql').read())652653assert f.download(encoding='utf-8') == open(TEST_DIR / 'test2.sql').read()654655# Test writer without context manager656wfile = f.open('w')657for line in open(TEST_DIR / 'test.sql'):658wfile.write(line)659wfile.close()660661txt = st.download_file(f.path, encoding='utf-8')662663assert txt == open(TEST_DIR / 'test.sql').read()664665# Test reader without context manager666rfile = f.open('r')667txt = ''668for line in rfile:669txt += line670rfile.close()671672assert txt == open(TEST_DIR / 'test.sql').read()673674def test_os_directories(self):675st = self.wg.stage676677# mkdir678st.mkdir('mkdir_test_1')679st.mkdir('mkdir_test_2')680with self.assertRaises(s2.ManagementError):681st.mkdir('mkdir_test_2/nest_1/nest_2')682st.mkdir('mkdir_test_2/nest_1')683st.mkdir('mkdir_test_2/nest_1/nest_2')684st.mkdir('mkdir_test_3')685686assert st.exists('mkdir_test_1/')687assert st.exists('mkdir_test_2/')688assert st.exists('mkdir_test_2/nest_1/')689assert st.exists('mkdir_test_2/nest_1/nest_2/')690assert not st.exists('foo/')691assert not st.exists('foo/bar/')692693assert st.is_dir('mkdir_test_1/')694assert st.is_dir('mkdir_test_2/')695assert st.is_dir('mkdir_test_2/nest_1/')696assert st.is_dir('mkdir_test_2/nest_1/nest_2/')697698assert not st.is_file('mkdir_test_1/')699assert not st.is_file('mkdir_test_2/')700assert not st.is_file('mkdir_test_2/nest_1/')701assert not st.is_file('mkdir_test_2/nest_1/nest_2/')702703out = st.listdir('/')704assert 'mkdir_test_1/' in out705assert 'mkdir_test_2/' in out706assert 'mkdir_test_2/nest_1/nest_2/' not in out707708out = st.listdir('/', recursive=True)709assert 'mkdir_test_1/' in out710assert 'mkdir_test_2/' in out711assert 'mkdir_test_2/nest_1/nest_2/' in out712713out = st.listdir('mkdir_test_2')714assert 'mkdir_test_1/' not in out715assert 'nest_1/' in out716assert 'nest_2/' not in out717assert 'nest_1/nest_2/' not in out718719out = st.listdir('mkdir_test_2', recursive=True)720assert 'mkdir_test_1/' not in out721assert 'nest_1/' in out722assert 'nest_2/' not in out723assert 'nest_1/nest_2/' in out724725# rmdir726before = st.listdir('/', recursive=True)727st.rmdir('mkdir_test_1/')728after = st.listdir('/', recursive=True)729assert 'mkdir_test_1/' in before730assert 'mkdir_test_1/' not in after731assert list(sorted(before)) == list(sorted(after + ['mkdir_test_1/']))732733with self.assertRaises(OSError):734st.rmdir('mkdir_test_2/')735736st.upload_file(TEST_DIR / 'test.sql', 'mkdir_test.sql')737738with self.assertRaises(NotADirectoryError):739st.rmdir('mkdir_test.sql')740741# removedirs742before = st.listdir('/')743st.removedirs('mkdir_test_2/')744after = st.listdir('/')745assert 'mkdir_test_2/' in before746assert 'mkdir_test_2/' not in after747assert list(sorted(before)) == list(sorted(after + ['mkdir_test_2/']))748749with self.assertRaises(s2.ManagementError):750st.removedirs('mkdir_test.sql')751752def test_os_files(self):753st = self.wg.stage754755st.mkdir('files_test_1')756st.mkdir('files_test_1/nest_1')757758st.upload_file(TEST_DIR / 'test.sql', 'files_test.sql')759st.upload_file(TEST_DIR / 'test.sql', 'files_test_1/nest_1/nested_files_test.sql')760st.upload_file(761TEST_DIR / 'test.sql',762'files_test_1/nest_1/nested_files_test_2.sql',763)764765# remove766with self.assertRaises(IsADirectoryError):767st.remove('files_test_1/')768769before = st.listdir('/')770st.remove('files_test.sql')771after = st.listdir('/')772assert 'files_test.sql' in before773assert 'files_test.sql' not in after774assert list(sorted(before)) == list(sorted(after + ['files_test.sql']))775776before = st.listdir('files_test_1/nest_1/')777st.remove('files_test_1/nest_1/nested_files_test.sql')778after = st.listdir('files_test_1/nest_1/')779assert 'nested_files_test.sql' in before780assert 'nested_files_test.sql' not in after781assert st.is_dir('files_test_1/nest_1/')782783# Removing the last file does not remove empty directories784st.remove('files_test_1/nest_1/nested_files_test_2.sql')785assert not st.is_file('files_test_1/nest_1/nested_files_test_2.sql')786assert st.is_dir('files_test_1/nest_1/')787assert st.is_dir('files_test_1/')788789st.removedirs('files_test_1')790assert not st.is_dir('files_test_1/nest_1/')791assert not st.is_dir('files_test_1/')792793def test_os_rename(self):794st = self.wg.stage795796st.upload_file(TEST_DIR / 'test.sql', 'rename_test.sql')797798with self.assertRaises(s2.ManagementError):799st.upload_file(800TEST_DIR / 'test.sql',801'rename_test_1/nest_1/nested_rename_test.sql',802)803804st.mkdir('rename_test_1')805st.mkdir('rename_test_1/nest_1')806807assert st.exists('/rename_test_1/nest_1/')808809st.upload_file(810TEST_DIR / 'test.sql',811'rename_test_1/nest_1/nested_rename_test.sql',812)813814st.upload_file(815TEST_DIR / 'test.sql',816'rename_test_1/nest_1/nested_rename_test_2.sql',817)818819# rename file820assert 'rename_test.sql' in st.listdir('/')821assert 'rename_test_2.sql' not in st.listdir('/')822st.rename('rename_test.sql', 'rename_test_2.sql')823assert 'rename_test.sql' not in st.listdir('/')824assert 'rename_test_2.sql' in st.listdir('/')825826# rename directory827assert 'rename_test_1/' in st.listdir('/')828assert 'rename_test_2/' not in st.listdir('/')829st.rename('rename_test_1/', 'rename_test_2/')830assert 'rename_test_1/' not in st.listdir('/')831assert 'rename_test_2/' in st.listdir('/')832assert st.is_file('rename_test_2/nest_1/nested_rename_test.sql')833assert st.is_file('rename_test_2/nest_1/nested_rename_test_2.sql')834835# rename nested836assert 'rename_test_2/nest_1/nested_rename_test.sql' in st.listdir(837'/', recursive=True,838)839assert 'rename_test_2/nest_1/nested_rename_test_3.sql' not in st.listdir(840'/', recursive=True,841)842st.rename(843'rename_test_2/nest_1/nested_rename_test.sql',844'rename_test_2/nest_1/nested_rename_test_3.sql',845)846assert 'rename_test_2/nest_1/nested_rename_test.sql' not in st.listdir(847'/', recursive=True,848)849assert 'rename_test_2/nest_1/nested_rename_test_3.sql' in st.listdir(850'/', recursive=True,851)852assert not st.is_file('rename_test_2/nest_1/nested_rename_test.sql')853assert st.is_file('rename_test_2/nest_1/nested_rename_test_2.sql')854assert st.is_file('rename_test_2/nest_1/nested_rename_test_3.sql')855856# non-existent file857with self.assertRaises(OSError):858st.rename('rename_foo.sql', 'rename_foo_2.sql')859860# overwrite861with self.assertRaises(OSError):862st.rename(863'rename_test_2.sql',864'rename_test_2/nest_1/nested_rename_test_3.sql',865)866867st.rename(868'rename_test_2.sql',869'rename_test_2/nest_1/nested_rename_test_3.sql', overwrite=True,870)871872def test_file_object(self):873st = self.wg.stage874875st.mkdir('obj_test')876st.mkdir('obj_test/nest_1')877878f1 = st.upload_file(TEST_DIR / 'test.sql', 'obj_test.sql')879f2 = st.upload_file(TEST_DIR / 'test.sql', 'obj_test/nest_1/obj_test.sql')880d2 = st.info('obj_test/nest_1/')881882# is_file / is_dir883assert not f1.is_dir()884assert f1.is_file()885assert not f2.is_dir()886assert f2.is_file()887assert d2.is_dir()888assert not d2.is_file()889890# abspath / basename / dirname / exists891assert f1.abspath() == 'obj_test.sql'892assert f1.basename() == 'obj_test.sql'893assert f1.dirname() == '/'894assert f1.exists()895assert f2.abspath() == 'obj_test/nest_1/obj_test.sql'896assert f2.basename() == 'obj_test.sql'897assert f2.dirname() == 'obj_test/nest_1/'898assert f2.exists()899assert d2.abspath() == 'obj_test/nest_1/'900assert d2.basename() == 'nest_1'901assert d2.dirname() == 'obj_test/'902assert d2.exists()903904# download905assert f1.download(encoding='utf-8') == open(TEST_DIR / 'test.sql', 'r').read()906assert f1.download() == open(TEST_DIR / 'test.sql', 'rb').read()907908# remove909with self.assertRaises(IsADirectoryError):910d2.remove()911912assert st.is_file('obj_test.sql')913f1.remove()914assert not st.is_file('obj_test.sql')915916# removedirs917with self.assertRaises(NotADirectoryError):918f2.removedirs()919920assert st.exists(d2.path)921d2.removedirs()922assert not st.exists(d2.path)923924# rmdir925f1 = st.upload_file(TEST_DIR / 'test.sql', 'obj_test.sql')926d2 = st.mkdir('obj_test/nest_1')927928assert st.exists(f1.path)929assert st.exists(d2.path)930931with self.assertRaises(NotADirectoryError):932f1.rmdir()933934assert st.exists(f1.path)935assert st.exists(d2.path)936937d2.rmdir()938939assert not st.exists('obj_test/nest_1/')940assert not st.exists('obj_test')941942# mtime / ctime943assert f1.getmtime() > 0944assert f1.getctime() > 0945946# rename947assert st.exists('obj_test.sql')948assert not st.exists('obj_test_2.sql')949f1.rename('obj_test_2.sql')950assert not st.exists('obj_test.sql')951assert st.exists('obj_test_2.sql')952assert f1.abspath() == 'obj_test_2.sql'953954955@pytest.mark.management956class TestSecrets(unittest.TestCase):957958manager = None959wg = None960password = None961962@classmethod963def setUpClass(cls):964cls.manager = s2.manage_workspaces()965966us_regions = [x for x in cls.manager.regions if 'US' in x.name]967cls.password = secrets.token_urlsafe(20) + '-x&$'968969name = clean_name(secrets.token_urlsafe(20)[:20])970971cls.wg = cls.manager.create_workspace_group(972f'wg-test-{name}',973region=random.choice(us_regions).id,974admin_password=cls.password,975firewall_ranges=['0.0.0.0/0'],976)977978@classmethod979def tearDownClass(cls):980if cls.wg is not None:981cls.wg.terminate(force=True)982cls.wg = None983cls.manager = None984cls.password = None985986def test_get_secret(self):987# manually create secret and then get secret988# try to delete the secret if it exists989try:990secret = self.manager.organizations.current.get_secret('secret_name')991992secret_id = secret.id993994self.manager._delete(f'secrets/{secret_id}')995except s2.ManagementError:996pass997998self.manager._post(999'secrets',1000json=dict(1001name='secret_name',1002value='secret_value',1003),1004)10051006secret = self.manager.organizations.current.get_secret('secret_name')10071008assert secret.name == 'secret_name'1009assert secret.value == 'secret_value'101010111012@pytest.mark.management1013class TestJob(unittest.TestCase):10141015manager = None1016workspace_group = None1017workspace = None1018password = None1019job_ids = []10201021@classmethod1022def setUpClass(cls):1023cls.manager = s2.manage_workspaces()10241025us_regions = [x for x in cls.manager.regions if 'US' in x.name]1026cls.password = secrets.token_urlsafe(20) + '-x&$'10271028name = clean_name(secrets.token_urlsafe(20)[:20])10291030cls.workspace_group = cls.manager.create_workspace_group(1031f'wg-test-{name}',1032region=random.choice(us_regions).id,1033admin_password=cls.password,1034firewall_ranges=['0.0.0.0/0'],1035)10361037try:1038cls.workspace = cls.workspace_group.create_workspace(1039f'ws-test-{name}-x',1040wait_on_active=True,1041)1042except Exception:1043cls.workspace_group.terminate(force=True)1044raise10451046@classmethod1047def tearDownClass(cls):1048for job_id in cls.job_ids:1049try:1050cls.manager.organizations.current.jobs.delete(job_id)1051except Exception:1052pass1053if cls.workspace_group is not None:1054cls.workspace_group.terminate(force=True)1055cls.workspace_group = None1056cls.workspace = None1057cls.manager = None1058cls.password = None1059if os.environ.get('SINGLESTOREDB_WORKSPACE', None) is not None:1060del os.environ['SINGLESTOREDB_WORKSPACE']1061if os.environ.get('SINGLESTOREDB_DEFAULT_DATABASE', None) is not None:1062del os.environ['SINGLESTOREDB_DEFAULT_DATABASE']10631064def test_job_without_database_target(self):1065"""1066Creates job without target database on a specific runtime1067Waits for job to finish1068Gets the job1069Deletes the job1070"""1071if os.environ.get('SINGLESTOREDB_WORKSPACE', None) is not None:1072del os.environ['SINGLESTOREDB_WORKSPACE']1073if os.environ.get('SINGLESTOREDB_DEFAULT_DATABASE', None) is not None:1074del os.environ['SINGLESTOREDB_DEFAULT_DATABASE']10751076job_manager = self.manager.organizations.current.jobs1077job = job_manager.run(1078'Scheduling Test.ipynb',1079'notebooks-cpu-small',1080{'strParam': 'string', 'intParam': 1, 'floatParam': 1.0, 'boolParam': True},1081)1082self.job_ids.append(job.job_id)1083assert job.execution_config.notebook_path == 'Scheduling Test.ipynb'1084assert job.schedule.mode == job_manager.modes().ONCE1085assert not job.execution_config.create_snapshot1086assert job.completed_executions_count == 01087assert job.name is None1088assert job.description is None1089assert job.job_metadata == []1090assert job.terminated_at is None1091assert job.target_config is None1092job.wait()1093job = job_manager.get(job.job_id)1094assert job.execution_config.notebook_path == 'Scheduling Test.ipynb'1095assert job.schedule.mode == job_manager.modes().ONCE1096assert not job.execution_config.create_snapshot1097assert job.completed_executions_count == 11098assert job.name is None1099assert job.description is None1100assert job.job_metadata != []1101assert len(job.job_metadata) == 11102assert job.job_metadata[0].count == 11103assert job.job_metadata[0].status == Status.COMPLETED1104assert job.terminated_at is None1105assert job.target_config is None1106deleted = job.delete()1107assert deleted1108job = job_manager.get(job.job_id)1109assert job.terminated_at is not None11101111def test_job_with_database_target(self):1112"""1113Creates job with target database on a specific runtime1114Waits for job to finish1115Gets the job1116Deletes the job1117"""1118os.environ['SINGLESTOREDB_DEFAULT_DATABASE'] = 'information_schema'1119os.environ['SINGLESTOREDB_WORKSPACE'] = self.workspace.id11201121job_manager = self.manager.organizations.current.jobs1122job = job_manager.run(1123'Scheduling Test.ipynb',1124'notebooks-cpu-small',1125{'strParam': 'string', 'intParam': 1, 'floatParam': 1.0, 'boolParam': True},1126)1127self.job_ids.append(job.job_id)1128assert job.execution_config.notebook_path == 'Scheduling Test.ipynb'1129assert job.schedule.mode == job_manager.modes().ONCE1130assert not job.execution_config.create_snapshot1131assert job.completed_executions_count == 01132assert job.name is None1133assert job.description is None1134assert job.job_metadata == []1135assert job.terminated_at is None1136assert job.target_config is not None1137assert job.target_config.database_name == 'information_schema'1138assert job.target_config.target_id == self.workspace.id1139assert job.target_config.target_type == TargetType.WORKSPACE1140assert not job.target_config.resume_target1141job.wait()1142job = job_manager.get(job.job_id)1143assert job.execution_config.notebook_path == 'Scheduling Test.ipynb'1144assert job.schedule.mode == job_manager.modes().ONCE1145assert not job.execution_config.create_snapshot1146assert job.completed_executions_count == 11147assert job.name is None1148assert job.description is None1149assert job.job_metadata != []1150assert len(job.job_metadata) == 11151assert job.job_metadata[0].count == 11152assert job.job_metadata[0].status == Status.COMPLETED1153assert job.terminated_at is None1154assert job.target_config is not None1155assert job.target_config.database_name == 'information_schema'1156assert job.target_config.target_id == self.workspace.id1157assert job.target_config.target_type == TargetType.WORKSPACE1158assert not job.target_config.resume_target1159deleted = job.delete()1160assert deleted1161job = job_manager.get(job.job_id)1162assert job.terminated_at is not None116311641165@pytest.mark.management1166class TestFileSpaces(unittest.TestCase):11671168manager = None1169personal_space = None1170shared_space = None11711172@classmethod1173def setUpClass(cls):1174cls.manager = s2.manage_files()1175cls.personal_space = cls.manager.personal_space1176cls.shared_space = cls.manager.shared_space11771178@classmethod1179def tearDownClass(cls):1180cls.manager = None1181cls.personal_space = None1182cls.shared_space = None11831184def test_upload_file(self):1185upload_test_ipynb = f'upload_test_{id(self)}.ipynb'11861187for space in [self.personal_space, self.shared_space]:1188root = space.info('/')1189assert str(root.path) == '/'1190assert root.type == 'directory'11911192# Upload files1193f = space.upload_file(1194TEST_DIR / 'test.ipynb',1195upload_test_ipynb,1196)1197assert str(f.path) == upload_test_ipynb1198assert f.type == 'notebook'11991200# Download and compare to original1201txt = f.download(encoding='utf-8')1202assert txt == open(TEST_DIR / 'test.ipynb').read()12031204# Make sure we can't overwrite1205with self.assertRaises(OSError):1206space.upload_file(1207TEST_DIR / 'test.ipynb',1208upload_test_ipynb,1209)12101211# Force overwrite with new content1212f = space.upload_file(1213TEST_DIR / 'test2.ipynb',1214upload_test_ipynb, overwrite=True,1215)1216assert str(f.path) == upload_test_ipynb1217assert f.type == 'notebook'12181219# Verify new content1220txt = f.download(encoding='utf-8')1221assert txt == open(TEST_DIR / 'test2.ipynb').read()12221223# Make sure we can't upload a folder1224with self.assertRaises(s2.ManagementError):1225space.upload_folder(TEST_DIR, 'test')12261227# Cleanup1228space.remove(upload_test_ipynb)12291230def test_upload_file_io(self):1231upload_test_ipynb = f'upload_test_{id(self)}.ipynb'12321233for space in [self.personal_space, self.shared_space]:1234root = space.info('/')1235assert str(root.path) == '/'1236assert root.type == 'directory'12371238# Upload files1239f = space.upload_file(1240open(TEST_DIR / 'test.ipynb', 'r'),1241upload_test_ipynb,1242)1243assert str(f.path) == upload_test_ipynb1244assert f.type == 'notebook'12451246# Download and compare to original1247txt = f.download(encoding='utf-8')1248assert txt == open(TEST_DIR / 'test.ipynb').read()12491250# Make sure we can't overwrite1251with self.assertRaises(OSError):1252space.upload_file(1253open(TEST_DIR / 'test.ipynb', 'r'),1254upload_test_ipynb,1255)12561257# Force overwrite with new content1258f = space.upload_file(1259open(TEST_DIR / 'test2.ipynb', 'r'),1260upload_test_ipynb, overwrite=True,1261)1262assert str(f.path) == upload_test_ipynb1263assert f.type == 'notebook'12641265# Verify new content1266txt = f.download(encoding='utf-8')1267assert txt == open(TEST_DIR / 'test2.ipynb').read()12681269# Make sure we can't upload a folder1270with self.assertRaises(s2.ManagementError):1271space.upload_folder(TEST_DIR, 'test')12721273# Cleanup1274space.remove(upload_test_ipynb)12751276def test_open(self):1277for space in [self.personal_space, self.shared_space]:1278open_test_ipynb = f'open_test_ipynb_{id(self)}.ipynb'12791280# See if error is raised for non-existent file1281with self.assertRaises(s2.ManagementError):1282space.open(open_test_ipynb, 'r')12831284# Load test file1285space.upload_file(TEST_DIR / 'test.ipynb', open_test_ipynb)12861287# Read file using `open`1288with space.open(open_test_ipynb, 'r') as rfile:1289assert rfile.read() == open(TEST_DIR / 'test.ipynb').read()12901291# Read file using `open` with 'rt' mode1292with space.open(open_test_ipynb, 'rt') as rfile:1293assert rfile.read() == open(TEST_DIR / 'test.ipynb').read()12941295# Read file using `open` with 'rb' mode1296with space.open(open_test_ipynb, 'rb') as rfile:1297assert rfile.read() == open(TEST_DIR / 'test.ipynb', 'rb').read()12981299# Read file using `open` with 'rb' mode1300with self.assertRaises(ValueError):1301with space.open(open_test_ipynb, 'b') as rfile:1302pass13031304# Attempt overwrite file using `open` with mode 'x'1305with self.assertRaises(OSError):1306with space.open(open_test_ipynb, 'x') as wfile:1307pass13081309# Attempt overwrite file using `open` with mode 'w'1310with space.open(open_test_ipynb, 'w') as wfile:1311wfile.write(open(TEST_DIR / 'test2.ipynb').read())13121313txt = space.download_file(open_test_ipynb, encoding='utf-8')13141315assert txt == open(TEST_DIR / 'test2.ipynb').read()13161317open_raw_test_ipynb = f'open_raw_test_{id(self)}.ipynb'13181319# Test writer without context manager1320wfile = space.open(open_raw_test_ipynb, 'w')1321for line in open(TEST_DIR / 'test.ipynb'):1322wfile.write(line)1323wfile.close()13241325txt = space.download_file(1326open_raw_test_ipynb,1327encoding='utf-8',1328)13291330assert txt == open(TEST_DIR / 'test.ipynb').read()13311332# Test reader without context manager1333rfile = space.open(open_raw_test_ipynb, 'r')1334txt = ''1335for line in rfile:1336txt += line1337rfile.close()13381339assert txt == open(TEST_DIR / 'test.ipynb').read()13401341# Cleanup1342space.remove(open_test_ipynb)1343space.remove(open_raw_test_ipynb)13441345def test_obj_open(self):1346for space in [self.personal_space, self.shared_space]:1347obj_open_test_ipynb = f'obj_open_test_{id(self)}.ipynb'1348obj_open_dir = f'obj_open_dir_{id(self)}'13491350# Load test file1351f = space.upload_file(1352TEST_DIR / 'test.ipynb',1353obj_open_test_ipynb,1354)13551356# Read file using `open`1357with f.open() as rfile:1358assert rfile.read() == open(TEST_DIR / 'test.ipynb').read()13591360# Make sure directories error out1361with self.assertRaises(s2.ManagementError):1362space.mkdir(obj_open_dir)13631364# Write file using `open`1365with f.open('w', encoding='utf-8') as wfile:1366wfile.write(open(TEST_DIR / 'test2.ipynb').read())13671368assert f.download(encoding='utf-8') == open(TEST_DIR / 'test2.ipynb').read()13691370# Test writer without context manager1371wfile = f.open('w')1372for line in open(TEST_DIR / 'test.ipynb'):1373wfile.write(line)1374wfile.close()13751376txt = space.download_file(f.path, encoding='utf-8')13771378assert txt == open(TEST_DIR / 'test.ipynb').read()13791380# Test reader without context manager1381rfile = f.open('r')1382txt = ''1383for line in rfile:1384txt += line1385rfile.close()13861387assert txt == open(TEST_DIR / 'test.ipynb').read()13881389# Cleanup1390space.remove(obj_open_test_ipynb)13911392def test_os_directories(self):1393for space in [self.personal_space, self.shared_space]:1394# Make sure directories error out1395with self.assertRaises(s2.ManagementError):1396space.mkdir('mkdir_test_1')13971398with self.assertRaises(s2.ManagementError):1399space.exists('mkdir_test_1/')14001401out = space.listdir('/')1402assert 'mkdir_test_1/' not in out14031404with self.assertRaises(s2.ManagementError):1405space.rmdir('mkdir_test_1/')14061407def test_os_rename(self):1408for space in [self.personal_space, self.shared_space]:1409space.upload_file(1410TEST_DIR / 'test.ipynb',1411'rename_test.ipynb',1412)1413assert 'rename_test.ipynb' in space.listdir('/')1414assert 'rename_test_2.ipynb' not in space.listdir('/')14151416space.rename(1417'rename_test.ipynb',1418'rename_test_2.ipynb',1419)1420assert 'rename_test.ipynb' not in space.listdir('/')1421assert 'rename_test_2.ipynb' in space.listdir('/')14221423# non-existent file1424with self.assertRaises(OSError):1425space.rename('rename_foo.ipynb', 'rename_foo_2.ipynb')14261427space.upload_file(1428TEST_DIR / 'test.ipynb',1429'rename_test_3.ipynb',1430)14311432# overwrite1433with self.assertRaises(OSError):1434space.rename(1435'rename_test_2.ipynb',1436'rename_test_3.ipynb',1437)14381439space.rename(1440'rename_test_2.ipynb',1441'rename_test_3.ipynb', overwrite=True,1442)14431444# Cleanup1445space.remove('rename_test_3.ipynb')14461447def test_file_object(self):1448for space in [self.personal_space, self.shared_space]:1449f = space.upload_file(1450TEST_DIR / 'test.ipynb',1451'obj_test.ipynb',1452)14531454assert not f.is_dir()1455assert f.is_file()14561457# abspath / basename / dirname / exists1458assert f.abspath() == 'obj_test.ipynb'1459assert f.basename() == 'obj_test.ipynb'1460assert f.dirname() == '/'1461assert f.exists()14621463# download1464assert f.download(encoding='utf-8') == \1465open(TEST_DIR / 'test.ipynb', 'r').read()1466assert f.download() == open(TEST_DIR / 'test.ipynb', 'rb').read()14671468assert space.is_file('obj_test.ipynb')1469f.remove()1470assert not space.is_file('obj_test.ipynb')14711472# mtime / ctime1473assert f.getmtime() > 01474assert f.getctime() > 014751476# rename1477f = space.upload_file(1478TEST_DIR / 'test.ipynb',1479'obj_test.ipynb',1480)1481assert space.exists('obj_test.ipynb')1482assert not space.exists('obj_test_2.ipynb')1483f.rename('obj_test_2.ipynb')1484assert not space.exists('obj_test.ipynb')1485assert space.exists('obj_test_2.ipynb')1486assert f.abspath() == 'obj_test_2.ipynb'14871488# Cleanup1489space.remove('obj_test_2.ipynb')149014911492@pytest.mark.management1493class TestRegions(unittest.TestCase):1494"""Test cases for region management."""14951496manager = None14971498@classmethod1499def setUpClass(cls):1500"""Set up the test environment."""1501cls.manager = s2.manage_regions()15021503@classmethod1504def tearDownClass(cls):1505"""Clean up the test environment."""1506cls.manager = None15071508def test_list_regions(self):1509"""Test listing all regions."""1510regions = self.manager.list_regions()15111512# Verify we get a NamedList1513assert isinstance(regions, NamedList)15141515# Verify we have at least one region1516assert len(regions) > 015171518# Verify region properties1519region = regions[0]1520assert isinstance(region, Region)1521assert hasattr(region, 'id')1522assert hasattr(region, 'name')1523assert hasattr(region, 'provider')15241525# Verify provider values1526providers = {x.provider for x in regions}1527assert 'Azure' in providers or 'GCP' in providers or 'AWS' in providers15281529def test_list_shared_tier_regions(self):1530"""Test listing shared tier regions."""1531regions = self.manager.list_shared_tier_regions()15321533# Verify we get a NamedList1534assert isinstance(regions, NamedList)15351536# Verify region properties if we have any shared tier regions1537if regions:1538region = regions[0]1539assert isinstance(region, Region)1540assert hasattr(region, 'name')1541assert hasattr(region, 'provider')1542assert hasattr(region, 'region_name')15431544# Verify provider values1545providers = {x.provider for x in regions}1546assert any(p in providers for p in ['Azure', 'GCP', 'AWS'])15471548def test_str_repr(self):1549"""Test string representation of regions."""1550regions = self.manager.list_regions()1551if not regions:1552self.skipTest('No regions available for testing')15531554region = regions[0]15551556# Test __str__1557s = str(region)1558assert region.id in s1559assert region.name in s1560assert region.provider in s15611562# Test __repr__1563assert repr(region) == str(region)156415651566