[PATCH] [Kimchi] Move Kimchi specific functions from gingerbase.netinfo to Kimchi
by Aline Manera
The idea behind this patch is to eliminate the Ginger Base dependency.
Almost all the functions in gingerbase.netinfo are for only Kimchi
matters. So move them to Kimchi and when posible use ethtool module to
provide the necessary info
Signed-off-by: Aline Manera <alinefm(a)linux.vnet.ibm.com>
---
control/interfaces.py | 7 +-
model/interfaces.py | 37 ++++-
model/networks.py | 22 ++-
model/ovsbridges.py | 4 +-
network.py | 434 +++++++++++++++++++++++++++++++++++++++++++++++++-
tests/test_model.py | 2 +-
6 files changed, 478 insertions(+), 28 deletions(-)
diff --git a/control/interfaces.py b/control/interfaces.py
index 7b6127a..7aba664 100644
--- a/control/interfaces.py
+++ b/control/interfaces.py
@@ -37,9 +37,4 @@ class Interface(Resource):
@property
def data(self):
- return {'name': self.ident,
- 'type': self.info['type'],
- 'ipaddr': self.info['ipaddr'],
- 'netmask': self.info['netmask'],
- 'status': self.info['status'],
- 'module': self.info['module']}
+ return self.info
diff --git a/model/interfaces.py b/model/interfaces.py
index 6be4356..0532072 100644
--- a/model/interfaces.py
+++ b/model/interfaces.py
@@ -1,7 +1,7 @@
#
# Project Kimchi
#
-# Copyright IBM Corp, 2015-2016
+# Copyright IBM Corp, 2015-2017
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -17,11 +17,14 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+import ethtool
+
from wok.exception import InvalidParameter, NotFoundError
+from wok.stringutils import encode_value
+from wok.utils import wok_log
-from wok.plugins.gingerbase import netinfo
+from wok.plugins.kimchi import network as netinfo
from wok.plugins.kimchi.model.networks import NetworksModel
-from wok.utils import wok_log
class InterfacesModel(object):
@@ -50,7 +53,29 @@ class InterfaceModel(object):
pass
def lookup(self, name):
- try:
- return netinfo.get_interface_info(name)
- except ValueError:
+ if encode_value(name) not in map(encode_value, ethtool.get_devices()):
raise NotFoundError("KCHIFACE0001E", {'name': name})
+
+ ipaddr = ''
+ netmask = ''
+ module = 'unknown'
+ status = 'down'
+ try:
+ ipaddr = ethtool.get_ipaddr(encode_value(name))
+ netmask = ethtool.get_netmask(encode_value(name))
+ module = ethtool.get_module(encode_value(name))
+
+ flags = ethtool.get_flags(encode_value(name))
+ status = 'up' if flags & (ethtool.IFF_RUNNING | ethtool.IFF_UP) \
+ else 'down'
+ except IOError:
+ pass
+
+ iface_type = netinfo.get_interface_type(name)
+
+ return {'name': name,
+ 'type': iface_type,
+ 'status': status,
+ 'ipaddr': ipaddr,
+ 'netmask': netmask,
+ 'module': module}
diff --git a/model/networks.py b/model/networks.py
index 59b111b..722b97b 100644
--- a/model/networks.py
+++ b/model/networks.py
@@ -28,10 +28,7 @@ from wok.exception import MissingParameter, NotFoundError, OperationFailed
from wok.utils import run_command, wok_log
from wok.xmlutils.utils import xpath_get_text
-from wok.plugins.gingerbase import netinfo
-from wok.plugins.gingerbase.netinfo import get_vlan_device, is_bridge, is_vlan
-from wok.plugins.gingerbase.netinfo import ports
-from wok.plugins.kimchi import network as knetwork
+from wok.plugins.kimchi import network as netinfo
from wok.plugins.kimchi.config import kimchiPaths
from wok.plugins.kimchi.model.featuretests import FeatureTests
from wok.plugins.kimchi.osinfo import defaults as tmpl_defaults
@@ -130,8 +127,8 @@ class NetworksModel(object):
xml = network.XMLDesc(0)
subnet = NetworkModel.get_network_from_xml(xml)['subnet']
subnet and invalid_addrs.append(ipaddr.IPNetwork(subnet))
- addr_pools = addr_pools if addr_pools else knetwork.PrivateNets
- return knetwork.get_one_free_network(invalid_addrs, addr_pools)
+ addr_pools = addr_pools if addr_pools else netinfo.PrivateNets
+ return netinfo.get_one_free_network(invalid_addrs, addr_pools)
def _set_network_subnet(self, params):
netaddr = params.get('subnet', '')
@@ -281,7 +278,7 @@ class NetworksModel(object):
conn = self.conn.get()
if iface_xml is None:
try:
- mac = knetwork.get_dev_macaddr(str(interface))
+ mac = netinfo.get_dev_macaddr(str(interface))
iface_xml = get_iface_xml({'type': 'ethernet',
'name': interface,
'mac': mac,
@@ -364,7 +361,7 @@ class NetworkModel(object):
connection = 'macvtap'
# exposing the network on linux bridge or macvtap interface
- interface_subnet = knetwork.get_dev_netaddr(interface)
+ interface_subnet = netinfo.get_dev_netaddr(interface)
subnet = subnet if subnet else interface_subnet
# libvirt use format 192.168.0.1/24, standard should be 192.168.0.0/24
@@ -536,10 +533,11 @@ class NetworkModel(object):
# get target device if bridge was created by Kimchi
if connection == 'bridge':
iface = info['interfaces'][0]
- if is_bridge(iface) and iface.startswith(KIMCHI_BRIDGE_PREFIX):
- port = ports(iface)[0]
- if is_vlan(port):
- dev = get_vlan_device(port)
+ if (netinfo.is_bridge(iface) and
+ iface.startswith(KIMCHI_BRIDGE_PREFIX)):
+ port = netinfo.ports(iface)[0]
+ if netinfo.is_vlan(port):
+ dev = netinfo.get_vlan_device(port)
info['interfaces'] = original['interfaces'] = [dev]
# nic
else:
diff --git a/model/ovsbridges.py b/model/ovsbridges.py
index 212520f..f5bb3df 100644
--- a/model/ovsbridges.py
+++ b/model/ovsbridges.py
@@ -1,7 +1,7 @@
#
# Project Kimchi
#
-# Copyright IBM Corp, 2016
+# Copyright IBM Corp, 2016-2017
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -17,7 +17,7 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-from wok.plugins.gingerbase.netinfo import ovs_bridges
+from wok.plugins.kimchi.network import ovs_bridges
class OVSBridgesModel(object):
diff --git a/network.py b/network.py
index ec567ca..69944e7 100644
--- a/network.py
+++ b/network.py
@@ -1,7 +1,7 @@
#
# Project Kimchi
#
-# Copyright IBM Corp, 2015-2016
+# Copyright IBM Corp, 2015-2017
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -19,7 +19,13 @@
#
import ethtool
+import glob
import ipaddr
+import os
+from distutils.spawn import find_executable
+
+from wok.stringutils import encode_value
+from wok.utils import run_command
APrivateNets = ipaddr.IPNetwork("10.0.0.0/8")
@@ -30,6 +36,432 @@ DefaultNetsPool = [ipaddr.IPNetwork('192.168.122.0/23'),
ipaddr.IPNetwork('192.168.124.0/22'),
ipaddr.IPNetwork('192.168.128.0/17')]
+NET_PATH = '/sys/class/net'
+NIC_PATH = '/sys/class/net/*/device'
+BRIDGE_PATH = '/sys/class/net/*/bridge'
+BONDING_PATH = '/sys/class/net/*/bonding'
+WLAN_PATH = '/sys/class/net/*/wireless'
+NET_BRPORT = '/sys/class/net/%s/brport'
+NET_MASTER = '/sys/class/net/%s/master'
+PROC_NET_VLAN = '/proc/net/vlan/'
+BONDING_SLAVES = '/sys/class/net/%s/bonding/slaves'
+BRIDGE_PORTS = '/sys/class/net/%s/brif'
+
+
+def wlans():
+ """Get all wlans declared in /sys/class/net/*/wireless.
+
+ Returns:
+ List[str]: a list with the wlans found.
+
+ """
+ return [b.split('/')[-2] for b in glob.glob(WLAN_PATH)]
+
+
+def nics():
+ """Get all nics of the host.
+
+ This function returns every nic, including those
+ that might be loaded from an usb port.
+
+ Returns:
+ List[str]: a list with the nics found.
+
+ """
+ return list(set([b.split('/')[-2] for b in glob.glob(NIC_PATH)]) -
+ set(wlans()))
+
+
+def is_nic(iface):
+ """Checks if iface is a nic.
+
+ Args:
+ iface (str): name of the interface.
+
+ Returns:
+ bool: True if iface is a nic, False otherwise.
+
+ """
+ return encode_value(iface) in map(encode_value, nics())
+
+
+def bondings():
+ """Get all bondings of the host.
+
+ Returns:
+ List[str]: a list with the bonds found.
+
+ """
+ return [b.split('/')[-2] for b in glob.glob(BONDING_PATH)]
+
+
+def is_bonding(iface):
+ """Checks if iface is a bond.
+
+ Args:
+ iface (str): name of the interface.
+
+ Returns:
+ bool: True if iface is a bond, False otherwise.
+
+ """
+ return encode_value(iface) in map(encode_value, bondings())
+
+
+def vlans():
+ """Get all vlans of the host.
+
+ Returns:
+ List[str]: a list with the vlans found.
+
+ """
+ return list(set([b.split('/')[-1]
+ for b in glob.glob(NET_PATH + '/*')]) &
+ set([b.split('/')[-1]
+ for b in glob.glob(PROC_NET_VLAN + '*')]))
+
+
+def is_vlan(iface):
+ """Checks if iface is a vlan.
+
+ Args:
+ iface (str): name of the interface.
+
+ Returns:
+ bool: True if iface is a vlan, False otherwise.
+
+ """
+ return encode_value(iface) in map(encode_value, vlans())
+
+
+def bridges():
+ """Get all bridges of the host.
+
+ Returns:
+ List[str]: a list with the bridges found.
+
+ """
+ return list(set([b.split('/')[-2] for b in glob.glob(BRIDGE_PATH)] +
+ ovs_bridges()))
+
+
+def is_bridge(iface):
+ """Checks if iface is a bridge.
+
+ Args:
+ iface (str): name of the interface.
+
+ Returns:
+ bool: True if iface is a bridge, False otherwise.
+
+ """
+ return encode_value(iface) in map(encode_value, bridges())
+
+
+def is_openvswitch_running():
+ """Checks if the openvswitch service is running in the host.
+
+ Returns:
+ bool: True if openvswitch service is running, False otherwise.
+
+ """
+ cmd = ['systemctl', 'is-active', 'openvswitch', '--quiet']
+ _, _, r_code = run_command(cmd, silent=True)
+ return r_code == 0
+
+
+def ovs_bridges():
+ """Get the OVS Bridges of the host.
+
+ In some distributions, like Fedora, the files bridge and brif are
+ not created under /sys/class/net/<ovsbridge> for OVS bridges.
+ These specific functions allows one to differentiate OVS bridges
+ from other types of bridges.
+
+ Returns:
+ List[str]: a list with the OVS bridges found.
+
+ """
+ if not is_openvswitch_running():
+ return []
+
+ ovs_cmd = find_executable("ovs-vsctl")
+
+ # openvswitch not installed: there is no OVS bridge configured
+ if ovs_cmd is None:
+ return []
+
+ out, _, r_code = run_command([ovs_cmd, 'list-br'], silent=True)
+ if r_code != 0:
+ return []
+
+ return [x.strip() for x in out.rstrip('\n').split('\n') if x.strip()]
+
+
+def is_ovs_bridge(iface):
+ """Checks if iface is an OVS bridge.
+
+ In some distributions, like Fedora, the files bridge and brif are
+ not created under /sys/class/net/<ovsbridge> for OVS bridges.
+ These specific functions allows one to differentiate OVS bridges
+ from other types of bridges.
+
+ Args:
+ iface (str): name of the interface.
+
+ Returns:
+ bool: True if iface is an OVS bridge, False otherwise.
+
+ """
+ return iface in ovs_bridges()
+
+
+def ovs_bridge_ports(ovsbr):
+ """Get the ports of a OVS bridge.
+
+ In some distributions, like Fedora, the files bridge and brif are
+ not created under /sys/class/net/<ovsbridge> for OVS bridges.
+ These specific functions allows one to differentiate OVS bridges
+ from other types of bridges.
+
+ Args:
+ ovsbr (str): name of the OVS bridge
+
+ Returns:
+ List[str]: a list with the ports of this bridge.
+
+ """
+ if not is_openvswitch_running():
+ return []
+
+ ovs_cmd = find_executable("ovs-vsctl")
+
+ # openvswitch not installed: there is no OVS bridge configured
+ if ovs_cmd is None:
+ return []
+
+ out, _, r_code = run_command([ovs_cmd, 'list-ports', ovsbr], silent=True)
+ if r_code != 0:
+ return []
+
+ return [x.strip() for x in out.rstrip('\n').split('\n') if x.strip()]
+
+
+def all_interfaces():
+ """Returns all interfaces of the host.
+
+ Returns:
+ List[str]: a list with all interfaces of the host.
+
+ """
+ return [d.rsplit("/", 1)[-1] for d in glob.glob(NET_PATH + '/*')]
+
+
+def slaves(bonding):
+ """Get all slaves from a bonding.
+
+ Args:
+ bonding (str): the name of the bond.
+
+ Returns:
+ List[str]: a list with all slaves.
+
+ """
+ with open(BONDING_SLAVES % bonding) as bonding_file:
+ res = bonding_file.readline().split()
+ return res
+
+
+def ports(bridge):
+ """Get all ports from a bridge.
+
+ Args:
+ bridge (str): the name of the OVS bridge.
+
+ Returns:
+ List[str]: a list with all ports.
+
+ """
+ if bridge in ovs_bridges():
+ return ovs_bridge_ports(bridge)
+
+ return os.listdir(BRIDGE_PORTS % bridge)
+
+
+def is_brport(nic):
+ """Checks if nic is a port of a bridge.
+
+ Args:
+ iface (str): name of the interface.
+
+ Returns:
+ bool: True if iface is a port of a bridge, False otherwise.
+
+ """
+ ovs_brports = []
+
+ for ovsbr in ovs_bridges():
+ ovs_brports += ovs_bridge_ports(ovsbr)
+
+ return os.path.exists(NET_BRPORT % nic) or nic in ovs_brports
+
+
+def is_bondlave(nic):
+ """Checks if nic is a bond slave.
+
+ Args:
+ iface (str): name of the interface.
+
+ Returns:
+ bool: True if iface is a bond slave, False otherwise.
+
+ """
+ return os.path.exists(NET_MASTER % nic)
+
+
+def operstate(dev):
+ """Get the operstate status of a device.
+
+ Args:
+ dev (str): name of the device.
+
+ Returns:
+ str: "up" or "down"
+
+ """
+ flags = ethtool.get_flags(encode_value(dev))
+ return 'up' if flags & (ethtool.IFF_RUNNING | ethtool.IFF_UP) else 'down'
+
+
+def get_vlan_device(vlan):
+ """ Return the device of the given VLAN.
+
+ Args:
+ vlan (str): the vlan name.
+
+ Returns:
+ str: the device of the VLAN.
+
+ """
+ dev = None
+
+ if os.path.exists(PROC_NET_VLAN + vlan):
+ with open(PROC_NET_VLAN + vlan) as vlan_file:
+ for line in vlan_file:
+ if "Device:" in line:
+ dummy, dev = line.split()
+ break
+ return dev
+
+
+def get_bridge_port_device(bridge):
+ """Return the nics list that belongs to a port of 'bridge'.
+
+ Args:
+ bridge (str): the bridge name.
+
+ Returns:
+ List[str]: the nic list.
+
+ """
+ # br --- v --- bond --- nic1
+ if encode_value(bridge) not in map(encode_value, bridges()):
+ raise ValueError('unknown bridge %s' % bridge)
+ nics_list = []
+ for port in ports(bridge):
+ if encode_value(port) in map(encode_value, vlans()):
+ device = get_vlan_device(port)
+ if encode_value(device) in map(encode_value, bondings()):
+ nics_list.extend(slaves(device))
+ else:
+ nics_list.append(device)
+ if encode_value(port) in map(encode_value, bondings()):
+ nics_list.extend(slaves(port))
+ else:
+ nics_list.append(port)
+ return nics_list
+
+
+def aggregated_bridges():
+ """Get the list of aggregated bridges of the host.
+
+ Returns:
+ List[str]: the aggregated bridges list.
+
+ """
+ return [bridge for bridge in bridges() if
+ (set(get_bridge_port_device(bridge)) & set(nics()))]
+
+
+def bare_nics():
+ """Get the list of bare nics of the host.
+
+ A nic is called bare when it is not a port of a bridge
+ or a slave of bond.
+
+ Returns:
+ List[str]: the list of bare nics of the host.
+
+ """
+ return [nic for nic in nics() if not (is_brport(nic) or is_bondlave(nic))]
+
+
+def is_bare_nic(iface):
+ """Checks if iface is a bare nic.
+
+ Args:
+ iface (str): name of the interface.
+
+ Returns:
+ bool: True if iface is a bare nic, False otherwise.
+
+ """
+ return encode_value(iface) in map(encode_value, bare_nics())
+
+
+# The nic will not be exposed when it is a port of a bridge or
+# a slave of bond.
+# The bridge will not be exposed when all it's port are tap.
+def all_favored_interfaces():
+ """Get the list of all favored interfaces of the host.
+
+ The nic will not be exposed when it is a port of a bridge or
+ a slave of bond. The bridge will not be exposed when all its
+ port are tap.
+
+ Returns:
+ List[str]: the list of favored interfaces.
+
+ """
+ return aggregated_bridges() + bare_nics() + bondings()
+
+
+def get_interface_type(iface):
+ """Get the interface type of iface.
+
+ Types supported: nic, bonding, bridge, vlan. If the type
+ can't be verified, 'unknown' is returned.
+
+ Args:
+ iface (str): the interface name.
+
+ Returns:
+ str: the interface type.
+
+ """
+ try:
+ if is_nic(iface):
+ return "nic"
+ if is_bonding(iface):
+ return "bonding"
+ if is_bridge(iface):
+ return "bridge"
+ if is_vlan(iface):
+ return "vlan"
+ return 'unknown'
+ except IOError:
+ return 'unknown'
+
def get_dev_macaddr(dev):
info = ethtool.get_interfaces_info(dev)[0]
diff --git a/tests/test_model.py b/tests/test_model.py
index 95c9e08..5398d08 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -48,7 +48,7 @@ from wok.rollbackcontext import RollbackContext
from wok.utils import convert_data_size
from wok.xmlutils.utils import xpath_get_text
-from wok.plugins.gingerbase import netinfo
+from wok.plugins.kimchi import network as netinfo
from wok.plugins.kimchi import osinfo
from wok.plugins.kimchi.config import kimchiPaths as paths
from wok.plugins.kimchi.model import model
--
2.9.3
7 years, 7 months
[PATCH] [Kimchi] Add server arch from /host API instead of relying on Ginger Base
by Aline Manera
That is one more change related to remove Ginger Base dependency from
Kimchi.
Signed-off-by: Aline Manera <alinefm(a)linux.vnet.ibm.com>
---
control/host.py | 6 +++++-
ui/js/src/kimchi.api.js | 6 +++---
ui/js/src/kimchi.main.js | 6 +++---
3 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/control/host.py b/control/host.py
index b1204fe..7872895 100644
--- a/control/host.py
+++ b/control/host.py
@@ -17,6 +17,8 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+import platform
+
from wok.control.base import Collection
from wok.control.base import Resource, SimpleCollection
from wok.control.utils import UrlSubNode
@@ -25,6 +27,8 @@ from wok.exception import NotFoundError
from wok.plugins.kimchi.control.cpuinfo import CPUInfo
from wok.plugins.kimchi.utils import is_s390x
+ARCH = platform.machine()
+
@UrlSubNode('host', True)
class Host(Resource):
@@ -39,7 +43,7 @@ class Host(Resource):
@property
def data(self):
- return {}
+ return {'arch': ARCH}
class VolumeGroups(Collection):
diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js
index 1cb89c0..618d68b 100644
--- a/ui/js/src/kimchi.api.js
+++ b/ui/js/src/kimchi.api.js
@@ -1,7 +1,7 @@
/*
* Project Kimchi
*
- * Copyright IBM Corp, 2013-2016
+ * Copyright IBM Corp, 2013-2017
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1314,9 +1314,9 @@ var kimchi = {
* Get the host information.
*/
- kimchi.getHostDetails = function(suc, err) {
+ kimchi.serverConfig = function(suc, err) {
wok.requestJSON({
- url: 'plugins/gingerbase/host',
+ url: 'plugins/kimchi/host',
type: 'GET',
resend: true,
contentType: 'application/json',
diff --git a/ui/js/src/kimchi.main.js b/ui/js/src/kimchi.main.js
index f3078ec..47531ae 100644
--- a/ui/js/src/kimchi.main.js
+++ b/ui/js/src/kimchi.main.js
@@ -1,7 +1,7 @@
/*
* Project Kimchi
*
- * Copyright IBM Corp, 2013-2016
+ * Copyright IBM Corp, 2013-2017
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,8 +34,8 @@ kimchi.getCapabilities(function(result) {
});
kimchi.hostarch = undefined;
-kimchi.getHostDetails(function(result) {
- kimchi.hostarch = result["architecture"];
+kimchi.serverConfig(function(result) {
+ kimchi.hostarch = result["arch"];
});
$(function(){
--
2.9.3
7 years, 7 months
[PATCH] [Kimchi] Move Kimchi specific functions from gingerbase.disks to Kimchi
by Aline Manera
The idea behind this patch is to eliminate the Ginger Base dependency.
Some functions in gingerbase.disks are for only Kimchi matters.
Others are shared with Ginger and can not be rewritten using PyParted,
each means they will be place on Kimchi and Ginger Base.
As this code is mature enough, further changes will have few impact and
the gain to incorporate Ginger Base into Ginger and eliminate a Kimchi
dependency is bigger.
Signed-off-by: Aline Manera <alinefm(a)linux.vnet.ibm.com>
---
disks.py | 325 ++++++++++++++++++++++++++++++++++++++++++++++++++++
i18n.py | 5 +
model/host.py | 4 +-
tests/test_disks.py | 53 +++++++++
4 files changed, 385 insertions(+), 2 deletions(-)
create mode 100644 disks.py
create mode 100644 tests/test_disks.py
diff --git a/disks.py b/disks.py
new file mode 100644
index 0000000..e86ca2f
--- /dev/null
+++ b/disks.py
@@ -0,0 +1,325 @@
+#
+# Project Kimchi
+#
+# Copyright IBM Corp, 2015-2017
+#
+# Code derived from Ginger Base project
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os.path
+import re
+from parted import Device as PDevice
+from parted import Disk as PDisk
+
+from wok.exception import NotFoundError, OperationFailed
+from wok.stringutils import encode_value
+from wok.utils import run_command, wok_log
+
+
+def _get_dev_node_path(maj_min):
+ """ Returns device node path given the device number 'major:min' """
+
+ dm_name = "/sys/dev/block/%s/dm/name" % maj_min
+ if os.path.exists(dm_name):
+ with open(dm_name) as dm_f:
+ content = dm_f.read().rstrip('\n')
+ return "/dev/mapper/" + content
+
+ uevent = "/sys/dev/block/%s/uevent" % maj_min
+ with open(uevent) as ueventf:
+ content = ueventf.read()
+
+ data = dict(re.findall(r'(\S+)=(".*?"|\S+)', content.replace("\n", " ")))
+
+ return "/dev/%s" % data["DEVNAME"]
+
+
+def _get_lsblk_devs(keys, devs=None):
+ if devs is None:
+ devs = []
+ out, err, returncode = run_command(
+ ["lsblk", "-Pbo"] + [','.join(keys)] + devs
+ )
+ if returncode != 0:
+ if 'not a block device' in err:
+ raise NotFoundError("KCHDISK00002E")
+ else:
+ raise OperationFailed("KCHDISK00001E", {'err': err})
+
+ return _parse_lsblk_output(out, keys)
+
+
+def _get_dev_major_min(name):
+ maj_min = None
+
+ keys = ["NAME", "MAJ:MIN"]
+ try:
+ dev_list = _get_lsblk_devs(keys)
+ except:
+ raise
+
+ for dev in dev_list:
+ if dev['name'].split()[0] == name:
+ maj_min = dev['maj:min']
+ break
+ else:
+ raise NotFoundError("KCHDISK00003E", {'device': name})
+
+ return maj_min
+
+
+def _is_dev_leaf(devNodePath, name=None, devs=None, devtype=None):
+ try:
+ if devs and devtype != 'mpath':
+ for dev in devs:
+ if encode_value(name) == dev['pkname']:
+ return False
+ return True
+ # By default, lsblk prints a device information followed by children
+ # device information
+ childrenCount = len(
+ _get_lsblk_devs(["NAME"], [devNodePath])) - 1
+ except OperationFailed as e:
+ # lsblk is known to fail on multipath devices
+ # Assume these devices contain children
+ wok_log.error(
+ "Error getting device info for %s: %s", devNodePath, e)
+ return False
+
+ return childrenCount == 0
+
+
+def _is_dev_extended_partition(devType, devNodePath):
+ if devType != 'part':
+ return False
+
+ if devNodePath.startswith('/dev/mapper'):
+ try:
+ dev_maj_min = _get_dev_major_min(devNodePath.split("/")[-1])
+ parent_sys_path = '/sys/dev/block/' + dev_maj_min + '/slaves'
+ parent_dm_name = os.listdir(parent_sys_path)[0]
+ parent_maj_min = open(
+ parent_sys_path +
+ '/' +
+ parent_dm_name +
+ '/dev').readline().rstrip()
+ diskPath = _get_dev_node_path(parent_maj_min)
+ except Exception as e:
+ wok_log.error(
+ "Error dealing with dev mapper device: " + devNodePath)
+ raise OperationFailed("KCHDISK00001E", {'err': e.message})
+ else:
+ diskPath = devNodePath.rstrip('0123456789')
+
+ device = PDevice(diskPath)
+ try:
+ extended_part = PDisk(device).getExtendedPartition()
+ except NotImplementedError as e:
+ wok_log.warning(
+ "Error getting extended partition info for dev %s type %s: %s",
+ devNodePath, devType, e.message)
+ # Treate disk with unsupported partiton table as if it does not
+ # contain extended partitions.
+ return False
+ if extended_part and extended_part.path == devNodePath:
+ return True
+ return False
+
+
+def _parse_lsblk_output(output, keys):
+ # output is on format key="value",
+ # where key can be NAME, TYPE, FSTYPE, SIZE, MOUNTPOINT, etc
+ lines = output.rstrip("\n").split("\n")
+ r = []
+ for line in lines:
+ d = {}
+ for key in keys:
+ expression = r"%s=\".*?\"" % key
+ match = re.search(expression, line)
+ field = match.group()
+ k, v = field.split('=', 1)
+ d[k.lower()] = v[1:-1]
+ r.append(d)
+ return r
+
+
+def _is_available(name, devtype, fstype, mountpoint, majmin, devs=None):
+ devNodePath = _get_dev_node_path(majmin)
+ # Only list unmounted and unformated and leaf and (partition or disk)
+ # leaf means a partition, a disk has no partition, or a disk not held
+ # by any multipath device. Physical volume belongs to no volume group
+ # is also listed. Extended partitions should not be listed.
+ if fstype == 'LVM2_member':
+ has_VG = True
+ else:
+ has_VG = False
+ if (devtype in ['part', 'disk', 'mpath'] and
+ fstype in ['', 'LVM2_member'] and
+ mountpoint == "" and
+ not has_VG and
+ _is_dev_leaf(devNodePath, name, devs, devtype) and
+ not _is_dev_extended_partition(devtype, devNodePath)):
+ return True
+ return False
+
+
+def get_partitions_names(check=False):
+ names = set()
+ keys = ["NAME", "TYPE", "FSTYPE", "MOUNTPOINT", "MAJ:MIN"]
+ # output is on format key="value",
+ # where key can be NAME, TYPE, FSTYPE, MOUNTPOINT
+ for dev in _get_lsblk_devs(keys):
+ # split()[0] to avoid the second part of the name, after the
+ # whiteline
+ name = dev['name'].split()[0]
+ if check and not _is_available(name, dev['type'], dev['fstype'],
+ dev['mountpoint'], dev['maj:min']):
+ continue
+ names.add(name)
+
+ return list(names)
+
+
+def get_partition_details(name):
+ majmin = _get_dev_major_min(name)
+ dev_path = _get_dev_node_path(majmin)
+
+ keys = ["TYPE", "FSTYPE", "SIZE", "MOUNTPOINT", "MAJ:MIN", "PKNAME"]
+ try:
+ dev = _get_lsblk_devs(keys, [dev_path])[0]
+ except:
+ wok_log.error("Error getting partition info for %s", name)
+ return {}
+
+ dev['available'] = _is_available(name, dev['type'], dev['fstype'],
+ dev['mountpoint'], majmin)
+ if dev['mountpoint']:
+ # Sometimes the mountpoint comes with [SWAP] or other
+ # info which is not an actual mount point. Filtering it
+ regexp = re.compile(r"\[.*\]")
+ if regexp.search(dev['mountpoint']) is not None:
+ dev['mountpoint'] = ''
+ dev['path'] = dev_path
+ dev['name'] = name
+ return dev
+
+
+def vgs():
+ """
+ lists all volume groups in the system. All size units are in bytes.
+
+ [{'vgname': 'vgtest', 'size': 999653638144L, 'free': 0}]
+ """
+ cmd = ['vgs',
+ '--units',
+ 'b',
+ '--nosuffix',
+ '--noheading',
+ '--unbuffered',
+ '--options',
+ 'vg_name,vg_size,vg_free']
+
+ out, err, rc = run_command(cmd)
+ if rc != 0:
+ raise OperationFailed("KCHDISK00004E", {'err': err})
+
+ if not out:
+ return []
+
+ # remove blank spaces and create a list of VGs
+ vgs = map(lambda v: v.strip(), out.strip('\n').split('\n'))
+
+ # create a dict based on data retrieved from vgs
+ return map(lambda l: {'vgname': l[0],
+ 'size': long(l[1]),
+ 'free': long(l[2])},
+ [fields.split() for fields in vgs])
+
+
+def lvs(vgname=None):
+ """
+ lists all logical volumes found in the system. It can be filtered by
+ the volume group. All size units are in bytes.
+
+ [{'lvname': 'lva', 'path': '/dev/vgtest/lva', 'size': 12345L},
+ {'lvname': 'lvb', 'path': '/dev/vgtest/lvb', 'size': 12345L}]
+ """
+ cmd = ['lvs',
+ '--units',
+ 'b',
+ '--nosuffix',
+ '--noheading',
+ '--unbuffered',
+ '--options',
+ 'lv_name,lv_path,lv_size,vg_name']
+
+ out, err, rc = run_command(cmd)
+ if rc != 0:
+ raise OperationFailed("KCHDISK00004E", {'err': err})
+
+ if not out:
+ return []
+
+ # remove blank spaces and create a list of LVs filtered by vgname, if
+ # provided
+ lvs = filter(lambda f: vgname is None or vgname in f,
+ map(lambda v: v.strip(), out.strip('\n').split('\n')))
+
+ # create a dict based on data retrieved from lvs
+ return map(lambda l: {'lvname': l[0],
+ 'path': l[1],
+ 'size': long(l[2])},
+ [fields.split() for fields in lvs])
+
+
+def pvs(vgname=None):
+ """
+ lists all physical volumes in the system. It can be filtered by the
+ volume group. All size units are in bytes.
+
+ [{'pvname': '/dev/sda3',
+ 'size': 469502001152L,
+ 'uuid': 'kkon5B-vnFI-eKHn-I5cG-Hj0C-uGx0-xqZrXI'},
+ {'pvname': '/dev/sda2',
+ 'size': 21470642176L,
+ 'uuid': 'CyBzhK-cQFl-gWqr-fyWC-A50Y-LMxu-iHiJq4'}]
+ """
+ cmd = ['pvs',
+ '--units',
+ 'b',
+ '--nosuffix',
+ '--noheading',
+ '--unbuffered',
+ '--options',
+ 'pv_name,pv_size,pv_uuid,vg_name']
+
+ out, err, rc = run_command(cmd)
+ if rc != 0:
+ raise OperationFailed("KCHDISK00004E", {'err': err})
+
+ if not out:
+ return []
+
+ # remove blank spaces and create a list of PVs filtered by vgname, if
+ # provided
+ pvs = filter(lambda f: vgname is None or vgname in f,
+ map(lambda v: v.strip(), out.strip('\n').split('\n')))
+
+ # create a dict based on data retrieved from pvs
+ return map(lambda l: {'pvname': l[0],
+ 'size': long(l[1]),
+ 'uuid': l[2]},
+ [fields.split() for fields in pvs])
diff --git a/i18n.py b/i18n.py
index 4460ce5..7dc7b05 100644
--- a/i18n.py
+++ b/i18n.py
@@ -29,6 +29,11 @@ messages = {
"KCHPART0001E": _("Partition %(name)s does not exist in the host"),
+ "KCHDISK00001E": _("Error while accessing dev mapper device, %(err)s"),
+ "KCHDISK00002E": _("Block device not found."),
+ "KCHDISK00003E": _("Block device %(device)s not found."),
+ "KCHDISK00004E": _("Unable to retrieve LVM information. Details: %(err)s"),
+
"KCHDEVS0001E": _('Unknown "_cap" specified'),
"KCHDEVS0002E": _('"_passthrough" should be "true" or "false"'),
"KCHDEVS0003E": _('"_passthrough_affected_by" should be a device name string'),
diff --git a/model/host.py b/model/host.py
index abf1191..90834e3 100644
--- a/model/host.py
+++ b/model/host.py
@@ -1,7 +1,7 @@
#
# Project Kimchi
#
-# Copyright IBM Corp, 2015-2016
+# Copyright IBM Corp, 2015-2017
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -26,7 +26,7 @@ from wok.exception import InvalidParameter
from wok.exception import NotFoundError
from wok.xmlutils.utils import xpath_get_text
-from wok.plugins.gingerbase import disks
+from wok.plugins.kimchi import disks
from wok.plugins.kimchi.model import hostdev
from wok.plugins.kimchi.model.config import CapabilitiesModel
from wok.plugins.kimchi.model.vms import VMModel, VMsModel
diff --git a/tests/test_disks.py b/tests/test_disks.py
new file mode 100644
index 0000000..6782f55
--- /dev/null
+++ b/tests/test_disks.py
@@ -0,0 +1,53 @@
+#
+# Project Kimchi
+#
+# Copyright IBM Corp, 2017
+#
+# Code derived from Ginger Base
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import mock
+import unittest
+
+from wok.exception import NotFoundError, OperationFailed
+from wok.plugins.kimchi.disks import _get_lsblk_devs
+
+
+class DiskTests(unittest.TestCase):
+
+ @mock.patch('wok.plugins.kimchi.disks.run_command')
+ def test_lsblk_returns_404_when_device_not_found(self, mock_run_command):
+ mock_run_command.return_value = ["", "not a block device", 32]
+ fake_dev = "/not/a/true/block/dev"
+ keys = ["MOUNTPOINT"]
+
+ with self.assertRaises(NotFoundError):
+ _get_lsblk_devs(keys, [fake_dev])
+ cmd = ['lsblk', '-Pbo', 'MOUNTPOINT', fake_dev]
+ mock_run_command.assert_called_once_with(cmd)
+
+ @mock.patch('wok.plugins.kimchi.disks.run_command')
+ def test_lsblk_returns_500_when_unknown_error_occurs(
+ self, mock_run_command):
+
+ mock_run_command.return_value = ["", "", 1]
+ valid_dev = "/valid/block/dev"
+ keys = ["MOUNTPOINT"]
+
+ with self.assertRaises(OperationFailed):
+ _get_lsblk_devs(keys, [valid_dev])
+ cmd = ['lsblk', '-Pbo', 'MOUNTPOINT', valid_dev]
+ mock_run_command.assert_called_once_with(cmd)
--
2.9.3
7 years, 7 months
[PATCH] [Kimchi] Get server arch from /config Wok API instead of relying on Ginger Base
by Aline Manera
That is one more change related to remove Ginger Base dependency from
Kimchi.
Signed-off-by: Aline Manera <alinefm(a)linux.vnet.ibm.com>
---
This patch depends on Wok patch [PATCH] [Wok] Add system arch to /config API
---
ui/js/src/kimchi.api.js | 4 ++--
ui/js/src/kimchi.main.js | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js
index 1cb89c0..27e3187 100644
--- a/ui/js/src/kimchi.api.js
+++ b/ui/js/src/kimchi.api.js
@@ -1314,9 +1314,9 @@ var kimchi = {
* Get the host information.
*/
- kimchi.getHostDetails = function(suc, err) {
+ kimchi.serverConfig = function(suc, err) {
wok.requestJSON({
- url: 'plugins/gingerbase/host',
+ url: 'config',
type: 'GET',
resend: true,
contentType: 'application/json',
diff --git a/ui/js/src/kimchi.main.js b/ui/js/src/kimchi.main.js
index f3078ec..f64b209 100644
--- a/ui/js/src/kimchi.main.js
+++ b/ui/js/src/kimchi.main.js
@@ -34,8 +34,8 @@ kimchi.getCapabilities(function(result) {
});
kimchi.hostarch = undefined;
-kimchi.getHostDetails(function(result) {
- kimchi.hostarch = result["architecture"];
+kimchi.serverConfig(function(result) {
+ kimchi.hostarch = result["arch"];
});
$(function(){
--
2.9.3
7 years, 8 months
[PATCH] [Wok] Add system arch to /config API
by Aline Manera
It may be useful when UI differs depending on system arch.
It is part of solution to remove Ginger Base dependency from Kimchi.
Signed-off-by: Aline Manera <alinefm(a)linux.vnet.ibm.com>
---
docs/API/config.md | 2 ++
src/wok/model/config.py | 4 ++++
tests/test_api.py | 2 +-
tests/test_config_model.py | 2 +-
tests/test_server_root.py | 6 +++---
5 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/docs/API/config.md b/docs/API/config.md
index d1d1007..b810f2a 100644
--- a/docs/API/config.md
+++ b/docs/API/config.md
@@ -12,6 +12,8 @@ Contains information about the application environment and configuration.
* proxy_port: SSL port to list on
* websockets_port: Port for websocket proxy to listen on
* auth: Authentication method used to log in to Wok
+ * server_root: Wok root API path
+ * arch: System architecture
* version: Wok version
* **POST**: *See Task Actions*
diff --git a/src/wok/model/config.py b/src/wok/model/config.py
index b69f2dd..54aaf54 100644
--- a/src/wok/model/config.py
+++ b/src/wok/model/config.py
@@ -18,12 +18,15 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import cherrypy
+import platform
import time
from wok.config import config, get_version
from wok.model.notifications import add_notification
from wok.utils import wok_log
+ARCH = platform.machine()
+
class ConfigModel(object):
def __init__(self, **kargs):
@@ -34,6 +37,7 @@ class ConfigModel(object):
'websockets_port': config.get('server', 'websockets_port'),
'auth': config.get('authentication', 'method'),
'server_root': config.get('server', 'server_root'),
+ 'arch': ARCH,
'version': get_version()}
def reload(self, name):
diff --git a/tests/test_api.py b/tests/test_api.py
index 6fbee75..4e2e413 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -52,7 +52,7 @@ class APITests(unittest.TestCase):
def test_config(self):
resp = self.request('/config').read()
conf = json.loads(resp)
- keys = ["auth", "proxy_port", "websockets_port", "version",
+ keys = ["arch", "auth", "proxy_port", "websockets_port", "version",
"server_root"]
self.assertEquals(sorted(keys), sorted(conf.keys()))
diff --git a/tests/test_config_model.py b/tests/test_config_model.py
index f8b0848..df2b058 100644
--- a/tests/test_config_model.py
+++ b/tests/test_config_model.py
@@ -30,7 +30,7 @@ class ConfigModelTests(unittest.TestCase):
config = inst.config_lookup('')
self.assertItemsEqual(
['proxy_port', 'websockets_port', 'auth',
- 'server_root', 'version'],
+ 'server_root', 'arch', 'version'],
config.keys()
)
diff --git a/tests/test_server_root.py b/tests/test_server_root.py
index e95a13b..89f34d3 100644
--- a/tests/test_server_root.py
+++ b/tests/test_server_root.py
@@ -1,7 +1,7 @@
#
# Project Wok
#
-# Copyright IBM Corp, 2016
+# Copyright IBM Corp, 2016-2017
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -49,7 +49,7 @@ class ServerRootTests(unittest.TestCase):
# check if server_root in config is the same used to start server
resp = request(server_root + '/config').read()
conf = json.loads(resp)
- self.assertEquals(len(conf), 5)
+ self.assertEquals(len(conf), 6)
def test_development_env(self):
"""
@@ -61,4 +61,4 @@ class ServerRootTests(unittest.TestCase):
# check if server_root in config is the same used to start server
resp = request(server_root + '/config').read()
conf = json.loads(resp)
- self.assertEquals(len(conf), 5)
+ self.assertEquals(len(conf), 6)
--
2.9.3
7 years, 8 months
Minor Issue with Deb packages on Ubuntu 16.4LTS
by W Kern
When installing the latest Wok/Ginger/Kimchi to a fresh MINIMAL install
you can run into a dependency loop with nginx where apt complains that
nginx is not configured but if you go in and try an nginx install then
that complains that ginger is not properly configured.
such as:
Errors were encountered while processing:
nginx-core
nginx
wok
Package nginx is not configured yet.
Package nginx-core which provides nginx is not configured yet.
Seems to happen whether you start with Wok or start with Ginger.
The trick is to make sure nginx is installed prior to starting on the
Wok/Ginger/Kimchi package set (and if you started already, you have to
remove everything THEN install nginx THEN start again with Wok, Ginger etc)
Obviously upgrades to an existing installation work fine as would a more
complete Ubuntu install that includes nginx.
I'm not up on apt packaging but maybe check for nginx on each package
(assuming that is possible), even if that particular one didn't
necessarily need it.
-bill
7 years, 8 months
Request to use Compat 1.1 on New QCOW2 image creation?
by WK
Any reason to keep on using Compat 0.10?
Its easy enough to fix with this, but it seems like it shouldn't be
necessary on what is by definition a new install.
#!/bin/sh
src_img=$1
qemu-img amend -p -f qcow2 -o compat=1.1 ${src_img}
-bill
7 years, 8 months
[PATCH] Add apache configuration
by Cédric Bosdonnat
Provide an apache2 virtual host configuration in contrib. With this,
the user can choose to use either nginx or apache as a proxy.
The reason why the configuration has been added to contrib rather
than src/apache2 to mimic the nginx config, is that there is no distro
with the same apache2 config layout than others.
Also add Wants and After for apache2 in the systemd unit file.
Signed-off-by: Cédric Bosdonnat <cbosdonnat(a)suse.com>
---
contrib/wok.conf.apache | 34 ++++++++++++++++++++++++++++++++++
contrib/wokd.service.systemd | 2 ++
2 files changed, 36 insertions(+)
create mode 100644 contrib/wok.conf.apache
diff --git a/contrib/wok.conf.apache b/contrib/wok.conf.apache
new file mode 100644
index 00000000..c7e650ee
--- /dev/null
+++ b/contrib/wok.conf.apache
@@ -0,0 +1,34 @@
+Listen 8001
+Listen 8000
+
+<VirtualHost *:8001>
+ ErrorLog /var/log/apache2/error_log
+ TransferLog /var/log/apache2/access_log
+
+ Timeout 600
+ LimitRequestBody 4294967296
+
+ SSLEngine on
+ SSLCertificateFile /etc/wok/wok-cert.pem
+ SSLCertificateKeyFile /etc/wok/wok-key.pem
+ SSLOpenSSLConfCmd DHParameters /etc/wok/dhparams.pem
+ SSLSessionCacheTimeout 600
+
+ RewriteEngine On
+ RewriteCond %{HTTP:Upgrade} =websocket [NC]
+ RewriteRule /(.*) ws://localhost:64667/$1 [P,L]
+
+ ProxyPass /websockify http://127.0.0.1:64667/websockify
+ ProxyPassReverse /websockify http://127.0.0.1:64667/websockify
+
+ ProxyPass / http://127.0.0.1:8010/
+ ProxyPassReverse / http://127.0.0.1:8010/
+</VirtualHost>
+
+<VirtualHost *:8000>
+ ErrorLog /var/log/apache2/error_log
+ TransferLog /var/log/apache2/access_log
+
+ RewriteEngine On
+ RewriteRule ^/(.*)$ https://localhost:8001/$1 [R]
+</VirtualHost>
diff --git a/contrib/wokd.service.systemd b/contrib/wokd.service.systemd
index 621be601..eba80a9d 100644
--- a/contrib/wokd.service.systemd
+++ b/contrib/wokd.service.systemd
@@ -3,6 +3,8 @@ Description=Wok - Webserver Originated from Kimchi
Documentation=https://github.com/kimchi-project/wok/wiki
Wants=nginx.service
After=nginx.service
+Wants=apache2.service
+After=apache2.service
[Service]
Type=simple
--
2.12.0
7 years, 8 months
[PATCH] [Wok] python-mock not listed as dependency
by Ramon Medeiros
Signed-off-by: Ramon Medeiros <ramonn(a)linux.vnet.ibm.com>
---
docs/fedora-deps.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/fedora-deps.md b/docs/fedora-deps.md
index 29b477f..484f409 100644
--- a/docs/fedora-deps.md
+++ b/docs/fedora-deps.md
@@ -42,7 +42,7 @@ Packages required for UI development
Packages required for tests
---------------------------
- $ sudo yum install pyflakes python-pep8 python-requests rpmlint
+ $ sudo yum install pyflakes python-pep8 python-requests rpmlint python-mock
# For RHEL systems, install the additional packages:
$ sudo yum install python-unittest2
--
2.9.3
7 years, 8 months
[PATCH] [Kimchi] Remove Open Sans font requirement
by Aline Manera
Signed-off-by: Aline Manera <alinefm(a)linux.vnet.ibm.com>
---
ui/css/kimchi.css | 28 ++++++++++++++--------------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/ui/css/kimchi.css b/ui/css/kimchi.css
index 948b888..4c40fcd 100644
--- a/ui/css/kimchi.css
+++ b/ui/css/kimchi.css
@@ -82,7 +82,7 @@
/* Template & Guests Modal Windows */
#template-add-window.modal-content label.box-iso-outer,
#guest-add-window.modal-content label.box-iso-outer {
- font-family: "Open Sans", Helvetica, Arial, "Lucida Grande", sans-serif;
+ font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
font-size: 14px !important;
font-weight: 400;
border-radius: 3px;
@@ -345,7 +345,7 @@
#guest-add-window.modal-content label {
font-size: 16px;
font-weight: 400;
- font-family: "Open Sans", Helvetica, Arial, "Lucida Grande", sans-serif;
+ font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
}
#guest-add-window form#form-vm-add {
@@ -391,7 +391,7 @@
padding: 6px 2px;
display: inline-block;
vertical-align: middle;
- font-family: "Open Sans", Helvetica, Arial, "Lucida Grande", sans-serif;
+ font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
font-weight: 300;
font-size: 12.5pt;
line-height: 1.42857;
@@ -416,7 +416,7 @@
padding: 6px 2px;
display: inline-block;
vertical-align: middle;
- font-family: "Open Sans", Helvetica, Arial, "Lucida Grande", sans-serif;
+ font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
font-size: 12.5pt;
line-height: 2.42857;
font-weight: 400;
@@ -542,7 +542,7 @@
#guest-content-container .wok-guest-gallery .wok-guest-list-item > span {
display: inline-block;
width: 100%;
- font-family: "Open Sans", Helvetica, Arial, "Lucida Grande", sans-serif;
+ font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
font-size: 12.5pt;
line-height: 1.42857;
font-weight: 400;
@@ -1272,7 +1272,7 @@ body.wok-gallery {
#guest-edit-window form .header > span {
padding: 6px 2px;
display: inline-block;
- font-family: "Open Sans", Helvetica, Arial, "Lucida Grande", sans-serif;
+ font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
font-weight: 400;
font-size: 12.5pt;
vertical-align: bottom;
@@ -1449,7 +1449,7 @@ body.wok-gallery {
display: block;
padding: 6px 2px;
margin-bottom: 5px;
- font-family: "Open Sans", Helvetica, Arial, "Lucida Grande", sans-serif;
+ font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
font-weight: 400;
font-size: 12.5pt;
vertical-align: bottom;
@@ -1694,7 +1694,7 @@ ul {
#template-edit-window .template-tab-header > span {
padding: 6px 2px;
display: inline-block;
- font-family: "Open Sans", Helvetica, Arial, "Lucida Grande", sans-serif;
+ font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
font-weight: 400;
font-size: 12.5pt;
vertical-align: baseline;
@@ -1859,7 +1859,7 @@ ul {
#template-add-window.modal-content label {
font-size: 16px;
font-weight: 400;
- font-family: "Open Sans", Helvetica, Arial, "Lucida Grande", sans-serif;
+ font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
}
#template-add-window.modal-content label.check-all {
@@ -1990,7 +1990,7 @@ ul {
border-bottom: 1px solid #eee;
height: 38px;
padding: 4px 12px;
- font-family: "Open Sans", Helvetica, Arial, "Lucida Grande", sans-serif;
+ font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
font-size: 13pt;
}
@@ -2049,7 +2049,7 @@ ul {
display: inline-block;
vertical-align: bottom;
height: 36px;
- font-family: "Open Sans", Helvetica, Arial, "Lucida Grande", sans-serif;
+ font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
font-weight: 300;
font-size: 12.5pt;
line-height: 1.42857;
@@ -2075,7 +2075,7 @@ ul {
padding: 6px 2px;
display: inline-block;
vertical-align: top;
- font-family: "Open Sans", Helvetica, Arial, "Lucida Grande", sans-serif;
+ font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
font-size: 12.5pt;
line-height: 2.42857;
font-weight: 400;
@@ -2202,7 +2202,7 @@ ul {
#templates-root-container .wok-vm-gallery .wok-vm-body > span {
display: inline-block;
width: 100%;
- font-family: "Open Sans", Helvetica, Arial, "Lucida Grande", sans-serif;
+ font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
font-size: 12.5pt;
line-height: 1.42857;
font-weight: 400;
@@ -2821,7 +2821,7 @@ ul {
}
#storage-root-container .volumes .wok-list.wok-datagrid > .wok-datagrid-body > .wok-datagrid-row > div > div > div > span {
- font-family: "Open Sans", Helvetica, Arial, "Lucida Grande", sans-serif;
+ font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
line-height: 2.42857;
vertical-align: top;
font-size: 12.5pt;
--
2.9.3
7 years, 8 months