forked from juju/python-libjuju
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbase.py
148 lines (118 loc) · 4.35 KB
/
base.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import inspect
import subprocess
import uuid
from contextlib import contextmanager
from pathlib import Path
import mock
import pytest
from juju.client.jujudata import FileJujuData
from juju.controller import Controller
def is_bootstrapped():
try:
result = subprocess.run(['juju', 'switch'], stdout=subprocess.PIPE)
return (
result.returncode == 0 and
len(result.stdout.decode().strip()) > 0)
except FileNotFoundError:
return False
bootstrapped = pytest.mark.skipif(
not is_bootstrapped(),
reason='bootstrapped Juju environment required')
test_run_nonce = uuid.uuid4().hex[-4:]
class CleanController():
"""
Context manager that automatically connects and disconnects from
the currently active controller.
Note: Unlike CleanModel, this will not create a new controller for you,
and an active controller must already be available.
"""
def __init__(self):
self._controller = None
async def __aenter__(self):
self._controller = Controller()
await self._controller.connect()
return self._controller
async def __aexit__(self, exc_type, exc, tb):
await self._controller.disconnect()
class CleanModel():
"""
Context manager that automatically connects to the currently active
controller, adds a fresh model, returns the connection to that model,
and automatically disconnects and cleans up the model.
The new model is also set as the current default for the controller
connection.
"""
def __init__(self, bakery_client=None):
self._controller = None
self._model = None
self._model_uuid = None
self._bakery_client = bakery_client
async def __aenter__(self):
model_nonce = uuid.uuid4().hex[-4:]
frame = inspect.stack()[1]
test_name = frame.function.replace('_', '-')
jujudata = TestJujuData()
self._controller = Controller(
jujudata=jujudata,
bakery_client=self._bakery_client,
)
controller_name = jujudata.current_controller()
user_name = jujudata.accounts()[controller_name]['user']
await self._controller.connect(controller_name)
model_name = 'test-{}-{}-{}'.format(
test_run_nonce,
test_name,
model_nonce,
)
self._model = await self._controller.add_model(model_name)
# Change the JujuData instance so that it will return the new
# model as the current model name, so that we'll connect
# to it by default.
jujudata.set_model(
controller_name,
user_name + "/" + model_name,
self._model.info.uuid,
)
# save the model UUID in case test closes model
self._model_uuid = self._model.info.uuid
return self._model
async def __aexit__(self, exc_type, exc, tb):
await self._model.disconnect()
await self._controller.destroy_model(self._model_uuid, force=True)
await self._controller.disconnect()
class TestJujuData(FileJujuData):
def __init__(self):
self.__controller_name = None
self.__model_name = None
self.__model_uuid = None
super().__init__()
def set_model(self, controller_name, model_name, model_uuid):
self.__controller_name = controller_name
self.__model_name = model_name
self.__model_uuid = model_uuid
def current_model(self, *args, **kwargs):
return self.__model_name or super().current_model(*args, **kwargs)
def models(self):
all_models = super().models()
if self.__model_name is None:
return all_models
all_models.setdefault(self.__controller_name, {})
all_models[self.__controller_name].setdefault('models', {})
cmodels = all_models[self.__controller_name]['models']
cmodels[self.__model_name] = {'uuid': self.__model_uuid}
return all_models
class AsyncMock(mock.MagicMock):
async def __call__(self, *args, **kwargs):
return super().__call__(*args, **kwargs)
@contextmanager
def patch_file(filename):
"""
"Patch" a file so that its current contents are automatically restored
when the context is exited.
"""
filepath = Path(filename).expanduser()
data = filepath.read_bytes()
try:
yield
finally:
filepath.write_bytes(data)