Skip to content

Commit 49afb52

Browse files
authored
Merge pull request #3 from cloudify-incubator/backup_workflow
Add backup/restore workflow
2 parents 5d9c683 + 196f670 commit 49afb52

30 files changed

+1801
-210
lines changed

cloudify_libvirt/common.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
########
2-
# Copyright (c) 2016 GigaSpaces Technologies Ltd. All rights reserved
2+
# Copyright (c) 2016-2018 GigaSpaces Technologies Ltd. All rights reserved
33
#
44
# Licensed under the Apache License, Version 2.0 (the "License");
55
# you may not use this file except in compliance with the License.
@@ -9,9 +9,9 @@
99
#
1010
# Unless required by applicable law or agreed to in writing, software
1111
# distributed under the License is distributed on an "AS IS" BASIS,
12-
# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
# * See the License for the specific language governing permissions and
14-
# * limitations under the License.
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
1515
from cloudify import ctx
1616

1717

cloudify_libvirt/domain_tasks.py

Lines changed: 228 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
########
2-
# Copyright (c) 2016 GigaSpaces Technologies Ltd. All rights reserved
2+
# Copyright (c) 2016-2018 GigaSpaces Technologies Ltd. All rights reserved
33
#
44
# Licensed under the Apache License, Version 2.0 (the "License");
55
# you may not use this file except in compliance with the License.
@@ -9,9 +9,9 @@
99
#
1010
# Unless required by applicable law or agreed to in writing, software
1111
# distributed under the License is distributed on an "AS IS" BASIS,
12-
# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
# * See the License for the specific language governing permissions and
14-
# * limitations under the License.
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
1515

1616
import libvirt
1717
import time
@@ -80,25 +80,19 @@ def configure(**kwargs):
8080
params.update(template_params)
8181
xmlconfig = template_engine.render(params)
8282

83-
ctx.logger.info(repr(xmlconfig))
83+
ctx.logger.debug(repr(xmlconfig))
8484

85-
dom = conn.defineXML(xmlconfig)
86-
if dom is None:
87-
raise cfy_exc.NonRecoverableError(
88-
'Failed to define a domain from an XML definition.'
89-
)
90-
91-
ctx.instance.runtime_properties['resource_id'] = dom.name()
92-
93-
if dom.create() < 0:
94-
raise cfy_exc.NonRecoverableError(
95-
'Can not boot guest domain.'
96-
)
97-
conn.close()
85+
try:
86+
dom = conn.defineXML(xmlconfig)
87+
if dom is None:
88+
raise cfy_exc.NonRecoverableError(
89+
'Failed to define a domain from an XML definition.'
90+
)
9891

99-
ctx.logger.info('Guest ' + dom.name() + ' has booted')
100-
ctx.instance.runtime_properties['resource_id'] = dom.name()
101-
ctx.instance.runtime_properties['params'] = template_params
92+
ctx.instance.runtime_properties['resource_id'] = dom.name()
93+
ctx.instance.runtime_properties['params'] = template_params
94+
finally:
95+
conn.close()
10296

10397

10498
@operation
@@ -130,10 +124,9 @@ def start(**kwargs):
130124
'Failed to find the domain'
131125
)
132126

133-
state, reason = dom.state()
134-
for i in xrange(10):
135-
state, reason = dom.state()
127+
state, _ = dom.state()
136128

129+
for i in xrange(10):
137130
if state == libvirt.VIR_DOMAIN_RUNNING:
138131
ctx.logger.info("Looks as running.")
139132
return
@@ -144,7 +137,7 @@ def start(**kwargs):
144137
'Can not start guest domain.'
145138
)
146139
time.sleep(30)
147-
state, reason = dom.state()
140+
state, _ = dom.state()
148141
finally:
149142
conn.close()
150143

@@ -178,10 +171,8 @@ def stop(**kwargs):
178171
'Failed to find the domain'
179172
)
180173

181-
state, reason = dom.state()
174+
state, _ = dom.state()
182175
for i in xrange(10):
183-
state, reason = dom.state()
184-
185176
if state != libvirt.VIR_DOMAIN_RUNNING:
186177
ctx.logger.info("Looks as not run.")
187178
return
@@ -192,7 +183,7 @@ def stop(**kwargs):
192183
'Can not shutdown guest domain.'
193184
)
194185
time.sleep(30)
195-
state, reason = dom.state()
186+
state, _ = dom.state()
196187
finally:
197188
conn.close()
198189

@@ -226,10 +217,8 @@ def resume(**kwargs):
226217
'Failed to find the domain'
227218
)
228219

229-
state, reason = dom.state()
220+
state, _ = dom.state()
230221
for i in xrange(10):
231-
state, reason = dom.state()
232-
233222
if state == libvirt.VIR_DOMAIN_RUNNING:
234223
ctx.logger.info("Looks as running.")
235224
return
@@ -240,7 +229,7 @@ def resume(**kwargs):
240229
'Can not suspend guest domain.'
241230
)
242231
time.sleep(30)
243-
state, reason = dom.state()
232+
state, _ = dom.state()
244233
finally:
245234
conn.close()
246235

@@ -274,10 +263,8 @@ def suspend(**kwargs):
274263
'Failed to find the domain'
275264
)
276265

277-
state, reason = dom.state()
266+
state, _ = dom.state()
278267
for i in xrange(10):
279-
state, reason = dom.state()
280-
281268
if state != libvirt.VIR_DOMAIN_RUNNING:
282269
ctx.logger.info("Looks as not run.")
283270
return
@@ -288,11 +275,33 @@ def suspend(**kwargs):
288275
'Can not suspend guest domain.'
289276
)
290277
time.sleep(30)
291-
state, reason = dom.state()
278+
state, _ = dom.state()
292279
finally:
293280
conn.close()
294281

295282

283+
def _cleanup_snapshots(ctx, dom):
284+
snapshots = dom.listAllSnapshots()
285+
snapshots_count = len(snapshots)
286+
287+
for _ in xrange(snapshots_count):
288+
for snapshot in snapshots:
289+
# we can delete only snapshot without child
290+
if not snapshot.numChildren():
291+
ctx.logger.info("Remove {} snapshot."
292+
.format(snapshot.getName()))
293+
snapshot.delete()
294+
snapshots = dom.listAllSnapshots()
295+
296+
if len(snapshots):
297+
subsnapshots = [
298+
snap.getName() for snap in snapshots
299+
]
300+
raise cfy_exc.RecoverableError(
301+
"Still have several snapshots: {subsnapshots}."
302+
.format(subsnapshots=repr(subsnapshots)))
303+
304+
296305
@operation
297306
def delete(**kwargs):
298307
ctx.logger.info("delete")
@@ -322,25 +331,203 @@ def delete(**kwargs):
322331
'Failed to find the domain'
323332
)
324333

325-
state, reason = dom.state()
334+
if dom.snapshotNum():
335+
ctx.logger.info("Domain has {} snapshots."
336+
.format(dom.snapshotNum()))
337+
_cleanup_snapshots(ctx, dom)
338+
339+
state, _ = dom.state()
326340

327341
if state != libvirt.VIR_DOMAIN_SHUTOFF:
328342
if dom.destroy() < 0:
329-
raise cfy_exc.NonRecoverableError(
343+
raise cfy_exc.RecoverableError(
330344
'Can not destroy guest domain.'
331345
)
332346

333347
try:
334348
if dom.undefineFlags(libvirt.VIR_DOMAIN_UNDEFINE_NVRAM) < 0:
335-
raise cfy_exc.NonRecoverableError(
349+
raise cfy_exc.RecoverableError(
336350
'Can not undefine guest domain with NVRAM.'
337351
)
338352
except AttributeError as e:
339353
ctx.logger.info("Non critical error: {}".format(str(e)))
340354
if dom.undefine() < 0:
341-
raise cfy_exc.NonRecoverableError(
355+
raise cfy_exc.RecoverableError(
342356
'Can not undefine guest domain.'
343357
)
358+
ctx.instance.runtime_properties['resource_id'] = None
359+
finally:
360+
conn.close()
361+
362+
363+
def _get_backupname(kwargs):
364+
if not kwargs.get("snapshot_name"):
365+
raise cfy_exc.NonRecoverableError(
366+
'Backup name must be provided.'
367+
)
368+
return "vm-{}".format(kwargs["snapshot_name"])
369+
370+
371+
@operation
372+
def snapshot_create(**kwargs):
373+
ctx.logger.info("backup")
374+
375+
resource_id = ctx.instance.runtime_properties.get('resource_id')
376+
377+
if not resource_id:
378+
ctx.logger.info("No servers for backup.")
379+
return
380+
381+
snapshot_name = _get_backupname(kwargs)
382+
if not kwargs.get("snapshot_incremental"):
383+
ctx.logger.info("Create backup for VM is unsupported.")
384+
return
385+
386+
libvirt_auth, template_params = get_libvirt_params(**kwargs)
387+
conn = libvirt.open(libvirt_auth)
388+
if conn is None:
389+
raise cfy_exc.NonRecoverableError(
390+
'Failed to open connection to the hypervisor'
391+
)
392+
393+
backup_file = kwargs.get('backup_file')
394+
backup_template = kwargs.get('backup_template')
395+
396+
if backup_file:
397+
backup_template = ctx.get_resource(backup_file)
398+
399+
if not backup_file and not backup_template:
400+
resource_dir = resource_filename(__name__, 'templates')
401+
backup_file = '{}/snapshot.xml'.format(resource_dir)
402+
ctx.logger.info("Will be used internal: %s" % backup_file)
403+
404+
if not backup_template:
405+
domain_desc = open(backup_file)
406+
with domain_desc:
407+
backup_template = domain_desc.read()
408+
409+
template_engine = Template(backup_template)
410+
if not template_params:
411+
template_params = {}
412+
413+
params = {"ctx": ctx, 'snapshot_name': snapshot_name}
414+
params.update(template_params)
415+
xmlconfig = template_engine.render(params)
416+
417+
ctx.logger.debug(repr(xmlconfig))
418+
419+
try:
420+
try:
421+
dom = conn.lookupByName(resource_id)
422+
except Exception as e:
423+
dom = None
424+
ctx.logger.info("Non critical error: {}".format(str(e)))
425+
426+
if dom is None:
427+
raise cfy_exc.NonRecoverableError(
428+
'Failed to find the domain'
429+
)
430+
try:
431+
# will raise exception if unexist
432+
snapshot = dom.snapshotLookupByName(snapshot_name)
433+
raise cfy_exc.NonRecoverableError(
434+
"Snapshot {snapshot_name} already exists."
435+
.format(snapshot_name=snapshot.getName(),))
436+
except libvirt.libvirtError:
437+
pass
438+
snapshot = dom.snapshotCreateXML(xmlconfig)
439+
ctx.logger.info("Snapshot name: {}".format(snapshot.getName()))
440+
finally:
441+
conn.close()
442+
443+
444+
@operation
445+
def snapshot_delete(**kwargs):
446+
ctx.logger.info("remove_backup")
447+
resource_id = ctx.instance.runtime_properties.get('resource_id')
448+
449+
if not resource_id:
450+
ctx.logger.info("No servers for remove_backup.")
451+
return
452+
453+
snapshot_name = _get_backupname(kwargs)
454+
if not kwargs.get("snapshot_incremental"):
455+
ctx.logger.info("Delete backup for VM is unsupported.")
456+
return
457+
458+
libvirt_auth, template_params = get_libvirt_params(**kwargs)
459+
conn = libvirt.open(libvirt_auth)
460+
if conn is None:
461+
raise cfy_exc.NonRecoverableError(
462+
'Failed to open connection to the hypervisor'
463+
)
464+
465+
try:
466+
try:
467+
dom = conn.lookupByName(resource_id)
468+
except Exception as e:
469+
dom = None
470+
ctx.logger.info("Non critical error: {}".format(str(e)))
471+
472+
if dom is None:
473+
raise cfy_exc.NonRecoverableError(
474+
'Failed to find the domain'
475+
)
476+
477+
# raised exception if libvirt has not found any
478+
snapshot = dom.snapshotLookupByName(snapshot_name)
479+
if snapshot.numChildren():
480+
subsnapshots = [
481+
snap.getName() for snap in snapshot.listAllChildren()
482+
]
483+
raise cfy_exc.NonRecoverableError(
484+
"Sub snapshots {subsnapshots} found for {snapshot_name}. "
485+
"You should remove subsnaphots before remove current."
486+
.format(snapshot_name=snapshot_name,
487+
subsnapshots=repr(subsnapshots)))
488+
snapshot.delete()
489+
ctx.logger.info("Backup deleted: {}".format(snapshot_name))
490+
finally:
491+
conn.close()
492+
493+
494+
@operation
495+
def snapshot_apply(**kwargs):
496+
ctx.logger.info("restore")
497+
resource_id = ctx.instance.runtime_properties.get('resource_id')
498+
499+
if not resource_id:
500+
ctx.logger.info("No servers for restore.")
501+
return
502+
503+
snapshot_name = _get_backupname(kwargs)
504+
if not kwargs.get("snapshot_incremental"):
505+
ctx.logger.info("Restore from backup for VM is unsupported.")
506+
return
507+
508+
libvirt_auth, template_params = get_libvirt_params(**kwargs)
509+
conn = libvirt.open(libvirt_auth)
510+
if conn is None:
511+
raise cfy_exc.NonRecoverableError(
512+
'Failed to open connection to the hypervisor'
513+
)
514+
515+
try:
516+
try:
517+
dom = conn.lookupByName(resource_id)
518+
except Exception as e:
519+
dom = None
520+
ctx.logger.info("Non critical error: {}".format(str(e)))
521+
522+
if dom is None:
523+
raise cfy_exc.NonRecoverableError(
524+
'Failed to find the domain'
525+
)
526+
527+
# raised exception if libvirt has not found any
528+
snapshot = dom.snapshotLookupByName(snapshot_name)
529+
dom.revertToSnapshot(snapshot)
530+
ctx.logger.info("Reverted to: {}".format(snapshot.getName()))
344531
finally:
345532
conn.close()
346533

0 commit comments

Comments
 (0)