2015-07-10 20:58:25 +00:00
|
|
|
# Copyright 2015 Canonical, Ltd.
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Affero General Public License as
|
|
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
|
|
# License, or (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program 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 Affero General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2015-09-21 21:40:26 +00:00
|
|
|
import copy
|
2015-07-10 20:58:25 +00:00
|
|
|
import yaml
|
|
|
|
|
|
|
|
|
2015-09-10 18:25:54 +00:00
|
|
|
class NetAction():
|
|
|
|
def __init__(self, **options):
|
2015-11-06 15:19:44 +00:00
|
|
|
self._action_keys = ['type', 'name', 'params', 'subnets']
|
2015-11-11 14:36:52 +00:00
|
|
|
self.params = {}
|
|
|
|
self.subnets = []
|
2015-09-10 18:25:54 +00:00
|
|
|
for k, v in options.items():
|
|
|
|
setattr(self, k, v)
|
|
|
|
|
|
|
|
def get(self):
|
|
|
|
action = {k: v for k, v in self.__dict__.items()
|
|
|
|
if k in self._action_keys}
|
|
|
|
return action
|
|
|
|
|
|
|
|
|
|
|
|
class PhysicalAction(NetAction):
|
|
|
|
def __init__(self, **options):
|
2015-11-06 15:19:44 +00:00
|
|
|
options['type'] = 'physical'
|
2015-09-10 18:25:54 +00:00
|
|
|
if 'name' not in options or len(options['name']) == 0:
|
|
|
|
raise Exception('Invalid name for {}'.format(
|
|
|
|
self.__class__.__name__))
|
|
|
|
if 'mac_address' not in options:
|
|
|
|
raise Exception('{} requires a valid mac_address attr'.format(
|
|
|
|
self.__class__.__name__))
|
|
|
|
super().__init__(**options)
|
|
|
|
self._action_keys.extend(['mac_address'])
|
|
|
|
|
|
|
|
|
|
|
|
class BridgeAction(NetAction):
|
|
|
|
def __init__(self, **options):
|
2015-11-06 15:19:44 +00:00
|
|
|
options['type'] = 'bridge'
|
2015-09-10 18:25:54 +00:00
|
|
|
if 'name' not in options or len(options['name']) == 0:
|
|
|
|
raise Exception('Invalid name for {}'.format(
|
|
|
|
self.__class__.__name__))
|
|
|
|
if 'bridge_interfaces' not in options:
|
|
|
|
raise Exception('{} requires bridge_interfaces attr'.format(
|
|
|
|
self.__class__.__name__))
|
|
|
|
super().__init__(**options)
|
|
|
|
self._action_keys.extend(['bridge_interfaces'])
|
|
|
|
|
|
|
|
|
|
|
|
class VlanAction(NetAction):
|
|
|
|
def __init__(self, **options):
|
2015-11-06 15:19:44 +00:00
|
|
|
options['type'] = 'vlan'
|
2015-09-10 18:25:54 +00:00
|
|
|
if 'name' not in options or len(options['name']) == 0:
|
|
|
|
raise Exception('Invalid name for {}'.format(
|
|
|
|
self.__class__.__name__))
|
|
|
|
if 'vlan_id' not in options or 'vlan_link' not in options:
|
|
|
|
raise Exception('{} requires vlan_id and vlan_link attr'.format(
|
|
|
|
self.__class__.__name__))
|
|
|
|
super().__init__(**options)
|
|
|
|
self._action_keys.extend(['vlan_id', 'vlan_link'])
|
|
|
|
|
|
|
|
|
|
|
|
class BondAction(NetAction):
|
|
|
|
def __init__(self, **options):
|
2015-11-06 15:19:44 +00:00
|
|
|
options['type'] = 'bond'
|
2015-09-10 18:25:54 +00:00
|
|
|
if 'name' not in options or len(options['name']) == 0:
|
|
|
|
raise Exception('Invalid name for {}'.format(
|
|
|
|
self.__class__.__name__))
|
|
|
|
if 'bond_interfaces' not in options:
|
|
|
|
raise Exception('{} requires bond_interfaces attr'.format(
|
|
|
|
self.__class__.__name__))
|
|
|
|
super().__init__(**options)
|
|
|
|
self._action_keys.extend(['bond_interfaces'])
|
|
|
|
|
|
|
|
|
2015-10-08 21:06:25 +00:00
|
|
|
class RouteAction(NetAction):
|
|
|
|
def __init__(self, **options):
|
2015-11-06 15:19:44 +00:00
|
|
|
options['type'] = 'route'
|
2015-10-08 21:06:25 +00:00
|
|
|
if 'gateway' not in options:
|
|
|
|
raise Exception('{} requires a valid gateway attr'.format(
|
|
|
|
self.__class__.__name__))
|
|
|
|
super().__init__(**options)
|
|
|
|
self._action_keys.extend(['gateway'])
|
|
|
|
|
|
|
|
|
2015-07-22 01:15:22 +00:00
|
|
|
class DiskAction():
|
2015-11-11 15:34:20 +00:00
|
|
|
def __init__(self, action_id, model, serial, ptable='gpt',
|
|
|
|
wipe='superblock'):
|
2015-07-10 20:58:25 +00:00
|
|
|
self._action_id = action_id
|
|
|
|
self.parent = None
|
2015-07-22 01:15:22 +00:00
|
|
|
self._ptable = ptable
|
|
|
|
self._model = model
|
|
|
|
self._serial = serial
|
2015-08-27 19:26:37 +00:00
|
|
|
self._wipe = wipe
|
2015-10-05 20:09:47 +00:00
|
|
|
self._type = 'disk'
|
2015-07-10 20:58:25 +00:00
|
|
|
|
2015-10-06 20:23:29 +00:00
|
|
|
__hash__ = None
|
2015-11-02 22:48:37 +00:00
|
|
|
|
2015-10-06 20:23:29 +00:00
|
|
|
def __eq__(self, other):
|
|
|
|
if isinstance(other, self.__class__):
|
|
|
|
return (self._action_id == other._action_id and
|
|
|
|
self.parent == other.parent and
|
|
|
|
self._ptable == other._ptable and
|
|
|
|
self._model == other._model and
|
|
|
|
self._serial == other._serial and
|
|
|
|
self._wipe == other._wipe and
|
|
|
|
self._type == other._type)
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2015-07-10 20:58:25 +00:00
|
|
|
def get_parent(self):
|
|
|
|
return self.parent
|
|
|
|
|
|
|
|
@property
|
|
|
|
def action_id(self):
|
|
|
|
return str(self._action_id)
|
|
|
|
|
2015-10-05 20:09:47 +00:00
|
|
|
@property
|
|
|
|
def type(self):
|
|
|
|
return self._type
|
|
|
|
|
2015-07-10 20:58:25 +00:00
|
|
|
def get(self):
|
2015-08-27 19:26:37 +00:00
|
|
|
action = {
|
2015-07-22 01:15:22 +00:00
|
|
|
'id': self.action_id,
|
|
|
|
'model': self._model,
|
|
|
|
'ptable': self._ptable,
|
|
|
|
'serial': self._serial,
|
2015-10-05 20:09:47 +00:00
|
|
|
'type': self._type,
|
2015-07-22 01:15:22 +00:00
|
|
|
}
|
2015-08-27 19:26:37 +00:00
|
|
|
if self._wipe:
|
|
|
|
action.update({'wipe': self._wipe})
|
2015-10-21 14:28:51 +00:00
|
|
|
# if we don't have a valid serial, then we must use
|
|
|
|
# device path, which is stored in action_id
|
|
|
|
if self._serial in ['Unknown Serial']:
|
|
|
|
del action['serial']
|
|
|
|
action.update({'path': '/dev/{}'.format(self.action_id)})
|
|
|
|
|
2015-08-27 19:26:37 +00:00
|
|
|
return action
|
2015-07-10 20:58:25 +00:00
|
|
|
|
2015-10-07 20:24:19 +00:00
|
|
|
def __repr__(self):
|
|
|
|
return str(self.get())
|
|
|
|
|
2015-07-10 20:58:25 +00:00
|
|
|
def dump(self):
|
|
|
|
return yaml.dump(self.get(), default_flow_style=False)
|
|
|
|
|
|
|
|
|
2015-10-02 00:03:54 +00:00
|
|
|
class RaidAction(DiskAction):
|
|
|
|
def __init__(self, action_id, raidlevel, dev_ids, spare_ids):
|
|
|
|
self._action_id = action_id
|
|
|
|
self.parent = None
|
|
|
|
self._raidlevel = raidlevel
|
|
|
|
self._devices = dev_ids
|
|
|
|
self._spares = spare_ids
|
2015-10-06 16:23:10 +00:00
|
|
|
self._type = 'raid'
|
2015-10-02 00:03:54 +00:00
|
|
|
|
2015-10-06 20:23:29 +00:00
|
|
|
__hash__ = None
|
2015-11-02 22:48:37 +00:00
|
|
|
|
2015-10-06 20:23:29 +00:00
|
|
|
def __eq__(self, other):
|
|
|
|
if isinstance(other, self.__class__):
|
|
|
|
return (self._action_id == other._action_id and
|
|
|
|
self.parent == other.parent and
|
|
|
|
self._raidlevel == other._raidlevel and
|
|
|
|
self._devices == other._devices and
|
|
|
|
self._spares == other._spares and
|
|
|
|
self._type == other._type)
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2015-10-02 00:03:54 +00:00
|
|
|
def get(self):
|
|
|
|
action = {
|
|
|
|
'id': self.action_id,
|
|
|
|
'name': self.action_id,
|
|
|
|
'raidlevel': self._raidlevel,
|
|
|
|
'devices': self._devices,
|
|
|
|
'spare_devices': self._spares,
|
2015-10-06 16:23:10 +00:00
|
|
|
'type': self._type,
|
2015-10-02 00:03:54 +00:00
|
|
|
}
|
|
|
|
return action
|
|
|
|
|
|
|
|
|
2015-07-22 01:15:22 +00:00
|
|
|
class PartitionAction(DiskAction):
|
2015-08-27 19:26:37 +00:00
|
|
|
def __init__(self, parent, partnum, offset, size, flags=None):
|
2015-07-10 20:58:25 +00:00
|
|
|
self.parent = parent
|
2015-08-27 19:26:37 +00:00
|
|
|
self.partnum = int(partnum)
|
|
|
|
self._offset = int(offset)
|
2015-07-10 20:58:25 +00:00
|
|
|
self._size = int(size)
|
|
|
|
self.flags = flags
|
2015-10-06 16:23:10 +00:00
|
|
|
self._type = 'partition'
|
2015-07-22 01:15:22 +00:00
|
|
|
self._action_id = "{}{}_part".format(self.parent.action_id,
|
2015-08-27 19:26:37 +00:00
|
|
|
self.partnum)
|
2015-07-10 20:58:25 +00:00
|
|
|
|
2015-07-23 20:20:00 +00:00
|
|
|
''' rename action_id for readability '''
|
|
|
|
if self.flags in ['bios_grub']:
|
|
|
|
self._action_id = 'bios_boot_partition'
|
|
|
|
|
2015-10-06 20:23:29 +00:00
|
|
|
__hash__ = None
|
2015-11-02 22:48:37 +00:00
|
|
|
|
2015-10-06 20:23:29 +00:00
|
|
|
def __eq__(self, other):
|
|
|
|
if isinstance(other, self.__class__):
|
|
|
|
return (self._action_id == other._action_id and
|
|
|
|
self.parent == other.parent and
|
|
|
|
self.partnum == other.partnum and
|
|
|
|
self._offset == other._offset and
|
|
|
|
self._size == other._size and
|
|
|
|
self.flags == other.flags and
|
|
|
|
self._type == other._type)
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2015-08-27 19:26:37 +00:00
|
|
|
@property
|
|
|
|
def path(self):
|
|
|
|
return "{}{}".format(self.parent.action_id, self.partnum)
|
|
|
|
|
2015-10-05 15:56:59 +00:00
|
|
|
@property
|
|
|
|
def devpath(self):
|
|
|
|
return "/dev/{}".format(self.path)
|
|
|
|
|
2015-07-10 20:58:25 +00:00
|
|
|
@property
|
|
|
|
def size(self):
|
|
|
|
return self._size
|
|
|
|
|
2015-08-27 19:26:37 +00:00
|
|
|
@property
|
|
|
|
def offset(self):
|
|
|
|
return self._offset
|
|
|
|
|
2015-07-10 20:58:25 +00:00
|
|
|
def get(self):
|
|
|
|
return {
|
|
|
|
'device': self.parent.action_id,
|
|
|
|
'flag': self.flags,
|
|
|
|
'id': self.action_id,
|
2015-08-27 19:26:37 +00:00
|
|
|
'number': self.partnum,
|
2015-07-22 01:15:22 +00:00
|
|
|
'size': '{}B'.format(self.size),
|
2015-08-27 19:26:37 +00:00
|
|
|
'offset': '{}B'.format(self.offset),
|
2015-10-06 16:23:10 +00:00
|
|
|
'type': self._type,
|
2015-07-10 20:58:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-07-22 01:15:22 +00:00
|
|
|
class BcacheAction(DiskAction):
|
2015-11-13 16:10:33 +00:00
|
|
|
def __init__(self, action_id, backing_id, cache_id):
|
2015-07-10 20:58:25 +00:00
|
|
|
self.parent = None
|
2015-11-13 18:29:18 +00:00
|
|
|
self._backing_device = backing_id
|
|
|
|
self._cache_device = cache_id
|
2015-11-13 16:10:33 +00:00
|
|
|
self._action_id = action_id
|
2015-10-06 16:23:10 +00:00
|
|
|
self._type = 'bcache'
|
2015-07-10 20:58:25 +00:00
|
|
|
|
2015-11-13 16:10:33 +00:00
|
|
|
__hash__ = None
|
|
|
|
|
2015-11-13 16:59:38 +00:00
|
|
|
@property
|
|
|
|
def backing_device(self):
|
|
|
|
return self._backing_device
|
|
|
|
|
|
|
|
@property
|
|
|
|
def cache_device(self):
|
|
|
|
return self._cache_device
|
|
|
|
|
2015-11-13 16:10:33 +00:00
|
|
|
def __eq__(self, other):
|
|
|
|
if isinstance(other, self.__class__):
|
|
|
|
return (self._action_id == other._action_id and
|
|
|
|
self.parent == other.parent and
|
2015-11-13 16:59:38 +00:00
|
|
|
self.backing_device == other.backing_device and
|
|
|
|
self.cache_device == other.cache_device and
|
2015-11-13 16:10:33 +00:00
|
|
|
self._type == other._type)
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2015-07-10 20:58:25 +00:00
|
|
|
def get(self):
|
|
|
|
return {
|
|
|
|
'backing_device': self.backing_device,
|
|
|
|
'cache_device': self.cache_device,
|
|
|
|
'id': self.action_id,
|
2015-10-06 16:23:10 +00:00
|
|
|
'type': self._type,
|
2015-07-10 20:58:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-07-22 01:15:22 +00:00
|
|
|
class FormatAction(DiskAction):
|
2015-07-10 20:58:25 +00:00
|
|
|
def __init__(self, parent, fstype):
|
|
|
|
self.parent = parent
|
|
|
|
self._fstype = fstype
|
|
|
|
self._action_id = "{}_fmt".format(self.parent.action_id)
|
2015-10-06 16:23:10 +00:00
|
|
|
self._type = 'format'
|
2015-07-22 01:15:22 +00:00
|
|
|
# fat filesystem require an id of <= 11 chars
|
|
|
|
if fstype.startswith('fat'):
|
|
|
|
self._action_id = self._action_id[:11]
|
2015-07-10 20:58:25 +00:00
|
|
|
|
2015-10-06 20:23:29 +00:00
|
|
|
__hash__ = None
|
2015-11-02 22:48:37 +00:00
|
|
|
|
2015-10-06 20:23:29 +00:00
|
|
|
def __eq__(self, other):
|
|
|
|
if isinstance(other, self.__class__):
|
|
|
|
return (self._action_id == other._action_id and
|
|
|
|
self.parent == other.parent and
|
|
|
|
self._fstype == other._fstype and
|
|
|
|
self._type == other._type)
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2015-07-10 20:58:25 +00:00
|
|
|
@property
|
|
|
|
def fstype(self):
|
|
|
|
return self._fstype
|
|
|
|
|
|
|
|
def get(self):
|
|
|
|
return {
|
|
|
|
'volume': self.parent.action_id,
|
|
|
|
'id': self.action_id,
|
|
|
|
'fstype': self.fstype,
|
2015-10-06 16:23:10 +00:00
|
|
|
'type': self._type,
|
2015-07-10 20:58:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-07-22 01:15:22 +00:00
|
|
|
class MountAction(DiskAction):
|
2015-07-10 20:58:25 +00:00
|
|
|
def __init__(self, parent, path):
|
|
|
|
self.parent = parent
|
|
|
|
self._path = path
|
2015-07-22 01:15:22 +00:00
|
|
|
self._action_id = "{}_mnt".format(self.parent.action_id)
|
2015-10-06 16:23:10 +00:00
|
|
|
self._type = 'mount'
|
2015-07-10 20:58:25 +00:00
|
|
|
|
2015-10-06 20:23:29 +00:00
|
|
|
__hash__ = None
|
2015-11-02 22:48:37 +00:00
|
|
|
|
2015-10-06 20:23:29 +00:00
|
|
|
def __eq__(self, other):
|
|
|
|
if isinstance(other, self.__class__):
|
|
|
|
return (self._action_id == other._action_id and
|
|
|
|
self.parent == other.parent and
|
|
|
|
self._path == other._path and
|
|
|
|
self._type == other._type)
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2015-07-10 20:58:25 +00:00
|
|
|
@property
|
|
|
|
def path(self):
|
|
|
|
return self._path
|
|
|
|
|
|
|
|
def get(self):
|
|
|
|
return {
|
|
|
|
'device': self.parent.action_id,
|
|
|
|
'id': self.action_id,
|
|
|
|
'path': self.path,
|
2015-10-06 16:23:10 +00:00
|
|
|
'type': self._type,
|
2015-07-10 20:58:25 +00:00
|
|
|
}
|
2015-09-21 21:40:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
def preserve_action(action):
|
|
|
|
a = copy.deepcopy(action)
|
|
|
|
a.update({'preserve': True})
|
|
|
|
return a
|
|
|
|
|
|
|
|
|
|
|
|
def release_action(action):
|
|
|
|
a = copy.deepcopy(action)
|
|
|
|
if 'preserve' in action:
|
|
|
|
del a['preserve']
|
|
|
|
return a
|