Skip to content

Commit 49d4311

Browse files
committed
Support for reuse external vm
1 parent 3a7f53f commit 49d4311

9 files changed

Lines changed: 206 additions & 60 deletions

File tree

CHANGELOG.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@ releases:
2121
v0.6.0:
2222
* Support for dump full vm snapshot to fs on backup
2323
* Update start action for fill in `network` in vm runtime properties
24+
* Cluster Example: Automatically add libvirt host to trusted
25+
* Support for reuse external vm

README.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,13 @@ Description for VM
4242
* `libvirt_auth`: connection url, by default: `qemu:///system`
4343
* `backup_dir`: directory for save backups, by default: `./`
4444
* `params`: params used for create object, useful for embeded template.
45+
* `use_external_resource`: (optional) Use external object. The default is
46+
`false`.
47+
* `resource_id`: (optional) Used to identify the object when
48+
`use_external_resource` is true.
4549
* `vcpu`: CPU count
46-
* `memory_minsize`: (optional) recomended VM memory size in KiB for downgrade.
50+
* `memory_minsize`: (optional) recomended VM memory size in KiB for
51+
downgrade.
4752
* `memory_size`: VM memory size in KiB
4853
* `nvram`: (optional) path to nvram (useful for arm)
4954
* `disks`: list connected disks
@@ -57,7 +62,8 @@ Description for VM
5762
**Inputs for actions:**
5863
* `configure`:
5964
* `params`: list of params for template, can be empty
60-
* `domain_file`: Template for domain. Defaults is [domain.xml](cloudify_libvirt/templates/domain.xml)
65+
* `domain_file`: Template for domain. Defaults is
66+
[domain.xml](cloudify_libvirt/templates/domain.xml)
6167

6268
**Runtime properties:**
6369
* `resource_id`: resource name.
@@ -70,16 +76,19 @@ Description for Network
7076
* `libvirt_auth`: connection url, by default: `qemu:///system`
7177
* `backup_dir`: directory for save backups, by default: `./`
7278
* `params`: params used for create object.
73-
* `use_external_resource`: (optional) Use external object. The default is `false`.
74-
* `resource_id`: (optional) Used to identify the object when `use_external_resource` is true.
79+
* `use_external_resource`: (optional) Use external object. The default is
80+
`false`.
81+
* `resource_id`: (optional) Used to identify the object when
82+
`use_external_resource` is true.
7583
* `dev`: Device name
7684
* `forwards`: settings for network `forwards`.
7785
* `ips`: settings for network `ips`.
7886

7987
**Inputs for actions:**
8088
* `create`:
8189
* `params`: list of params for template, can be empty
82-
* `network_file`: Template for network. Defaults is [network.xml](cloudify_libvirt/templates/network.xml)
90+
* `network_file`: Template for network. Defaults is
91+
[network.xml](cloudify_libvirt/templates/network.xml)
8392

8493
**Runtime properties:**
8594
* `resource_id`: resource name.

cloudify_libvirt/domain_tasks.py

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -33,34 +33,8 @@ def create(**kwargs):
3333
# so we will define domain later
3434

3535

36-
@operation
37-
def configure(**kwargs):
38-
ctx.logger.info("configure")
39-
40-
libvirt_auth, template_params = common.get_libvirt_params(**kwargs)
41-
conn = libvirt.open(libvirt_auth)
42-
if conn is None:
43-
raise cfy_exc.NonRecoverableError(
44-
'Failed to open connection to the hypervisor'
45-
)
46-
47-
domain_file = kwargs.get('domain_file')
48-
domain_template = kwargs.get('domain_template')
49-
50-
if domain_file:
51-
domain_template = ctx.get_resource(domain_file)
52-
53-
if not domain_file and not domain_template:
54-
resource_dir = resource_filename(__name__, 'templates')
55-
domain_file = '{}/domain.xml'.format(resource_dir)
56-
ctx.logger.info("Will be used internal: %s" % domain_file)
57-
58-
if not domain_template:
59-
domain_desc = open(domain_file)
60-
with domain_desc:
61-
domain_template = domain_desc.read()
62-
63-
template_engine = Template(domain_template)
36+
def _update_template_params(template_params):
37+
# set all params to default values
6438
if not template_params:
6539
template_params = {}
6640

@@ -77,14 +51,66 @@ def configure(**kwargs):
7751
template_params["instance_uuid"] = str(uuid.uuid4())
7852
if not template_params.get("domain_type"):
7953
template_params["domain_type"] = "qemu"
54+
return template_params
8055

81-
params = {"ctx": ctx}
82-
params.update(template_params)
83-
xmlconfig = template_engine.render(params)
8456

85-
ctx.logger.debug(repr(xmlconfig))
57+
@operation
58+
def configure(**kwargs):
59+
ctx.logger.info("configure")
60+
61+
libvirt_auth, template_params = common.get_libvirt_params(**kwargs)
62+
conn = libvirt.open(libvirt_auth)
63+
if conn is None:
64+
raise cfy_exc.NonRecoverableError(
65+
'Failed to open connection to the hypervisor'
66+
)
67+
68+
template_params = _update_template_params(template_params)
8669

8770
try:
71+
if template_params.get("use_external_resource"):
72+
# lookup the default domain by name
73+
try:
74+
dom = conn.lookupByName(template_params["resource_id"])
75+
except Exception as e:
76+
dom = None
77+
ctx.logger.info("Non critical error: {}".format(str(e)))
78+
79+
if dom is None:
80+
raise cfy_exc.NonRecoverableError(
81+
'Failed to find the domain'
82+
)
83+
84+
# save settings
85+
ctx.instance.runtime_properties['params'] = template_params
86+
ctx.instance.runtime_properties['resource_id'] = dom.name()
87+
ctx.instance.runtime_properties['use_external_resource'] = True
88+
return
89+
90+
# templates
91+
domain_file = kwargs.get('domain_file')
92+
domain_template = kwargs.get('domain_template')
93+
94+
if domain_file:
95+
domain_template = ctx.get_resource(domain_file)
96+
97+
if not domain_file and not domain_template:
98+
resource_dir = resource_filename(__name__, 'templates')
99+
domain_file = '{}/domain.xml'.format(resource_dir)
100+
ctx.logger.info("Will be used internal: %s" % domain_file)
101+
102+
if not domain_template:
103+
domain_desc = open(domain_file)
104+
with domain_desc:
105+
domain_template = domain_desc.read()
106+
107+
template_engine = Template(domain_template)
108+
params = {"ctx": ctx}
109+
params.update(template_params)
110+
xmlconfig = template_engine.render(params)
111+
112+
ctx.logger.debug(repr(xmlconfig))
113+
88114
dom = conn.defineXML(xmlconfig)
89115
if dom is None:
90116
raise cfy_exc.NonRecoverableError(
@@ -234,6 +260,12 @@ def start(**kwargs):
234260
)
235261

236262
time.sleep(30)
263+
264+
# still no ip
265+
if wait_for_ip:
266+
raise cfy_exc.RecoverableError(
267+
'No ip for now, try later'
268+
)
237269
finally:
238270
conn.close()
239271

@@ -248,6 +280,10 @@ def stop(**kwargs):
248280
ctx.logger.info("No servers for delete")
249281
return
250282

283+
if ctx.instance.runtime_properties.get('use_external_resource'):
284+
ctx.logger.info("External resource, skip")
285+
return
286+
251287
libvirt_auth, _ = common.get_libvirt_params(**kwargs)
252288
conn = libvirt.open(libvirt_auth)
253289
if conn is None:
@@ -439,6 +475,10 @@ def delete(**kwargs):
439475
ctx.logger.info("No servers for delete")
440476
return
441477

478+
if ctx.instance.runtime_properties.get('use_external_resource'):
479+
ctx.logger.info("External resource, skip")
480+
return
481+
442482
libvirt_auth, _ = common.get_libvirt_params(**kwargs)
443483
conn = libvirt.open(libvirt_auth)
444484
if conn is None:

cloudify_libvirt/network_tasks.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,21 @@
2525
import cloudify_libvirt.common as common
2626

2727

28+
def _update_template_params(template_params):
29+
# set all params to default values
30+
if not template_params:
31+
template_params = {}
32+
33+
if not template_params:
34+
template_params = {}
35+
36+
if not template_params.get("resource_id"):
37+
template_params["resource_id"] = ctx.instance.id
38+
if not template_params.get("instance_uuid"):
39+
template_params["instance_uuid"] = str(uuid.uuid4())
40+
return template_params
41+
42+
2843
@operation
2944
def create(**kwargs):
3045
ctx.logger.info("Creating new network.")
@@ -36,13 +51,7 @@ def create(**kwargs):
3651
'Failed to open connection to the hypervisor'
3752
)
3853

39-
if not template_params:
40-
template_params = {}
41-
42-
if not template_params.get("resource_id"):
43-
template_params["resource_id"] = ctx.instance.id
44-
if not template_params.get("instance_uuid"):
45-
template_params["instance_uuid"] = str(uuid.uuid4())
54+
template_params = _update_template_params(template_params)
4655

4756
try:
4857
if template_params.get("use_external_resource"):

cloudify_libvirt/tests/test_common_base.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def _create_fake_connection(self):
116116
connect.close = mock.Mock(return_value=None)
117117
return connect
118118

119-
def _test_no_resource_id(self, func):
119+
def _create_ctx(self):
120120
_ctx = MockCloudifyContext(
121121
'node_name',
122122
properties={
@@ -128,10 +128,25 @@ def _test_no_resource_id(self, func):
128128
}
129129
)
130130
current_ctx.set(_ctx)
131+
return _ctx
131132

133+
def _test_no_resource_id(self, func):
134+
_ctx = self._create_ctx()
132135
# no initilized/no resource id
133136
func(ctx=_ctx)
134137

138+
def _test_reused_object(self, func, use_existed=True):
139+
# check use prexisted object
140+
_ctx = self._create_ctx()
141+
_ctx.instance.runtime_properties['resource_id'] = 'resource'
142+
_ctx.instance.runtime_properties['use_external_resource'] = use_existed
143+
connect = self._create_fake_connection()
144+
with mock.patch(
145+
"cloudify_libvirt.network_tasks.libvirt.open",
146+
mock.Mock(return_value=connect)
147+
):
148+
func(ctx=_ctx)
149+
135150
def _test_no_snapshot_name(self, _ctx, func):
136151
_ctx.instance.runtime_properties['resource_id'] = 'resource'
137152

cloudify_libvirt/tests/test_domain.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,40 @@ def _create_ctx(self):
4242
current_ctx.set(_ctx)
4343
return _ctx
4444

45+
def test_reuse_domain_create_not_exist(self):
46+
# check correct handle exception with empty domain
47+
_ctx = self._create_ctx()
48+
self._check_no_such_object_domain(
49+
"cloudify_libvirt.domain_tasks.libvirt.open",
50+
domain_tasks.configure, [], {'ctx': _ctx, 'params': {
51+
"resource_id": 'resource',
52+
"use_external_resource": True,
53+
}}, 'resource')
54+
55+
def test_reuse_domain_create_exist(self):
56+
# check that we can use domain
57+
_ctx = self._create_ctx()
58+
59+
domain = mock.Mock()
60+
domain.name = mock.Mock(return_value="resource")
61+
62+
connect = self._create_fake_connection()
63+
connect.lookupByName = mock.Mock(return_value=domain)
64+
with mock.patch(
65+
"cloudify_libvirt.domain_tasks.libvirt.open",
66+
mock.Mock(return_value=connect)
67+
):
68+
domain_tasks.configure(ctx=_ctx, params={
69+
"resource_id": 'resource',
70+
"use_external_resource": True})
71+
connect.lookupByName.assert_called_with('resource')
72+
self.assertEqual(
73+
_ctx.instance.runtime_properties['resource_id'], 'resource'
74+
)
75+
self.assertTrue(
76+
_ctx.instance.runtime_properties['use_external_resource']
77+
)
78+
4579
def test_create(self):
4680
"""check create call, should run get_libvirt_params, without any
4781
real logic"""
@@ -108,10 +142,11 @@ def test_start(self):
108142
self._test_action_states(
109143
domain_tasks.start,
110144
[libvirt.VIR_DOMAIN_RUNNING, libvirt.VIR_DOMAIN_RUNNING_UNKNOWN],
111-
'Can not start guest domain.')
145+
'Can not start guest domain.', 'No ip for now, try later')
112146

113147
def test_stop(self):
114148
self._test_no_resource_id(domain_tasks.stop)
149+
self._test_reused_object(domain_tasks.stop)
115150
self._test_check_correct_connect_action(domain_tasks.stop)
116151
self._test_check_correct_connect_no_object(domain_tasks.stop)
117152
self._test_action_states(
@@ -139,6 +174,7 @@ def test_suspend(self):
139174

140175
def test_delete(self):
141176
self._test_no_resource_id(domain_tasks.delete)
177+
self._test_reused_object(domain_tasks.delete)
142178
self._test_check_correct_connect_action(domain_tasks.delete)
143179
self._test_check_correct_connect_no_object(domain_tasks.delete)
144180

@@ -341,7 +377,8 @@ def test_update_network_list(self):
341377
}]
342378
)
343379

344-
def _test_action_states(self, func, states, error_text):
380+
def _test_action_states(self, func, states, error_text,
381+
error_check_ip=None):
345382
_ctx = self._create_ctx()
346383
_ctx.instance.runtime_properties['resource_id'] = 'check'
347384
_ctx.instance.runtime_properties['params']['wait_for_ip'] = True
@@ -387,6 +424,7 @@ def _fake_state():
387424
def _fake_state():
388425
return fake_states.pop(), ""
389426

427+
domain.state = _fake_state
390428
connect = self._create_fake_connection()
391429
connect.lookupByName = mock.Mock(return_value=domain)
392430
with mock.patch(
@@ -399,6 +437,25 @@ def _fake_state():
399437
):
400438
func(ctx=_ctx)
401439

440+
# no ip but running
441+
if error_check_ip:
442+
domain.state = mock.Mock(
443+
return_value=(libvirt.VIR_DOMAIN_RUNNING, ""))
444+
_ctx.instance.runtime_properties['params']['wait_for_ip'] = True
445+
with mock.patch(
446+
"cloudify_libvirt.domain_tasks.libvirt.open",
447+
mock.Mock(return_value=connect)
448+
):
449+
with self.assertRaisesRegexp(
450+
RecoverableError,
451+
error_check_ip
452+
):
453+
with mock.patch(
454+
"time.sleep",
455+
mock.Mock(return_value=None)
456+
):
457+
func(ctx=_ctx)
458+
402459
def _test_check_correct_connect_no_object(self, func):
403460
# check correct handle exception with empty connection
404461
_ctx = self._create_ctx()

0 commit comments

Comments
 (0)