Add creation date column to snapshot list

* Added wrapper around snapshots returned by libvirt to make whole code
  more pythonic
* Snapshots are sorted by creation date by default
* Added lxml as runtime dependency
* Added tabulate as runtime dependency and used to print snapshot
  tabular data

DocImpact
Closes-Bug: #1463361

Change-Id: Ia2c2fab4ac763865ab3f837c63a87101e567d6e8
This commit is contained in:
Sebastian Kalinowski 2015-06-10 08:51:36 +02:00 committed by Sebastian Kalinowski
parent 8c26cab916
commit be2b9e77dd
6 changed files with 141 additions and 26 deletions

View File

@ -17,6 +17,6 @@ from os import environ
if __name__ == "__main__":
environ.setdefault("DJANGO_SETTINGS_MODULE", "devops.settings")
from devops.shell import Shell
from devops.shell import main
Shell().execute()
main()

View File

@ -12,12 +12,14 @@
# License for the specific language governing permissions and limitations
# under the License.
import ipaddr
import libvirt
import datetime
from time import sleep
import xml.etree.ElementTree as ET
import ipaddr
import libvirt
from lxml import etree
from devops.driver.libvirt.libvirt_xml_builder import LibvirtXMLBuilder
from devops.helpers.helpers import _get_file_size
from devops.helpers.retry import retry
@ -27,6 +29,31 @@ from devops import logger
from django.conf import settings
class Snapshot(object):
def __init__(self, snapshot):
self._snapshot = snapshot
self._xml = snapshot.getXMLDesc(0)
self._repr = ""
@property
def created(self):
xml_tree = etree.fromstring(self._xml)
timestamp = xml_tree.xpath('/domainsnapshot/creationTime/text()')[0]
return datetime.datetime.utcfromtimestamp(float(timestamp))
@property
def name(self):
return self._snapshot.getName()
def __repr__(self):
if not self._repr:
self._repr = "<{0} {1}/{2}>".format(
self.__class__.__name__, self.name, self.created)
return self._repr
class DevopsDriver(object):
def __init__(self,
connection_string="qemu:///system",
@ -347,7 +374,9 @@ class DevopsDriver(object):
:rtype : List
:type node: Node
"""
return self.conn.lookupByUUIDString(node.uuid).snapshotListNames(0)
snapshots = self.conn.lookupByUUIDString(node.uuid).listAllSnapshots(0)
return [Snapshot(snap) for snap in snapshots]
@retry()
def node_create_snapshot(self, node, name=None, description=None):

View File

@ -152,6 +152,7 @@ class Node(DriverModel):
' name {1}'.format(self.name, name))
def get_snapshots(self):
"""Return full snapshots objects"""
return self.driver.node_get_snapshots(node=self)
def erase_snapshot(self, name):

View File

@ -13,10 +13,12 @@
# under the License.
import argparse
import collections
import os
import sys
import ipaddr
import tabulate
import devops
from devops.helpers.helpers import _get_file_size
@ -28,7 +30,8 @@ from devops import settings
class Shell(object):
def __init__(self):
def __init__(self, args):
self.args = args
self.params = self.get_params()
if (getattr(self.params, 'name', None) and
getattr(self.params, 'command', None) != 'create'):
@ -37,6 +40,10 @@ class Shell(object):
def execute(self):
self.commands.get(self.params.command)(self)
def print_table(self, headers, columns):
print(tabulate.tabulate(columns, headers=headers,
tablefmt="simple"))
def do_list(self):
env_list = Environment.list().values('name', 'created')
for env in env_list:
@ -89,26 +96,34 @@ class Shell(object):
Environment.synchronize_all()
def do_snapshot_list(self):
snap_nodes = {}
max_len = 0
for node in self.env.get_nodes():
snaps = sorted(node.get_snapshots())
for snap in snaps:
if len(snap) > max_len:
max_len = len(snap)
if snap in snap_nodes:
snap_nodes[snap].append(node.name)
else:
snap_nodes[snap] = [node.name, ]
snapshots = collections.OrderedDict()
print("%*s %50s" % (max_len, "SNAPSHOT", "NODES-NAME"))
for snap in snap_nodes:
print("%*s %50s" % (max_len, snap,
', '.join(snap_nodes[snap])))
Snap = collections.namedtuple('Snap', ['info', 'nodes'])
for node in self.env.get_nodes():
for snap in node.get_snapshots():
if snap.name in snapshots:
snapshots[snap.name].nodes.append(node.name)
else:
snapshots[snap.name] = Snap(snap, [node.name, ])
snapshots = sorted(snapshots.values(), key=lambda x: x.info.created)
headers = ('SNAPSHOT', 'CREATED', 'NODES-NAMES')
columns = []
for info, nodes in snapshots:
nodes.sort()
columns.append((
info.name,
info.created.strftime('%Y-%m-%d %H:%M:%S'),
', '.join(nodes),
))
self.print_table(columns=columns, headers=headers)
def do_snapshot_delete(self):
for node in self.env.get_nodes():
snaps = sorted(node.get_snapshots())
snaps = map(lambda x: x.name, node.get_snapshots())
if self.params.snapshot_name in snaps:
node.erase_snapshot(name=self.params.snapshot_name)
@ -472,6 +487,12 @@ class Shell(object):
help="Reset (restart) node in environment",
description="Reset a separate node in "
"environment")
if len(sys.argv) == 1:
sys.argv.append("-h")
return parser.parse_args()
if len(self.args) == 0:
self.args = ['-h']
return parser.parse_args(self.args)
def main(args=None):
if args is None:
args = sys.argv[1:]
Shell(args).execute()

View File

@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
# Copyright 2015 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import unittest
import mock
from devops import models
from devops import shell
class BaseShellTestCase(unittest.TestCase):
def execute(self, *args):
return shell.main(args)
class TestSnaphotList(BaseShellTestCase):
@mock.patch.object(shell.Shell, 'print_table')
@mock.patch.object(models.Environment, 'get')
def test_snapshot_list_order(self, mock_get_env, mock_print):
snaps = []
base_date = datetime.datetime(2015, 12, 1)
for i in range(4):
snap = mock.Mock()
snap.name = "snap_{0}".format(i)
snap.created = base_date - datetime.timedelta(days=i)
snaps.append(snap)
node = mock.Mock()
node.name = "node"
node.get_snapshots.return_value = snaps
env = mock_get_env.return_value
env.get_nodes.return_value = [node, node]
self.execute('snapshot-list', 'some-env')
mock_print.assert_called(
columns=[
('snap_3', '2015-11-28 00:00:00', 'node, node'),
('snap_2', '2015-11-29 00:00:00', 'node, node'),
('snap_1', '2015-11-30 00:00:00', 'node, node'),
('snap_0', '2015-12-01 00:00:00', 'node, node')
],
headers=('SNAPSHOT', 'CREATED', 'NODES-NAMES')
)

View File

@ -42,5 +42,7 @@ setup(
'south',
'PyYAML',
'libvirt-python',
'lxml',
'tabulate',
]
)