Add the base classes for the new OS utils architecture

The base classes follows the OS utils architecture spec.

Change-Id: I1365e6c9de20dccc905e35113ccdea03f2a03f1b
This commit is contained in:
Claudiu Popa 2015-04-06 14:26:13 +03:00
parent cbb95af940
commit afacc4881a
7 changed files with 415 additions and 0 deletions

77
cloudinit/osys/base.py Normal file
View File

@ -0,0 +1,77 @@
# Copyright (C) 2015 Canonical Ltd.
# Copyright 2015 Cloudbase Solutions Srl
#
# 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 abc
import importlib
import platform
import six
__all__ = (
'get_osutils',
'OSUtils',
)
def get_osutils():
"""Obtain the OS utils object for the underlying platform."""
name, _, _ = platform.linux_distribution()
if not name:
name = platform.system()
name = name.lower()
location = "cloudinit.osys.{0}.base".format(name)
module = importlib.import_module(location)
return module.OSUtils
@six.add_metaclass(abc.ABCMeta)
class OSUtils(object):
"""Base class for an OS utils namespace.
This base class provides a couple of hooks which needs to be
implemented by subclasses, for each particular OS and distro.
"""
name = None
@abc.abstractproperty
def network(self):
"""Get the network object for the underlying platform."""
@abc.abstractproperty
def filesystem(self):
"""Get the filesystem object for the underlying platform."""
@abc.abstractproperty
def users(self):
"""Get the users object for the underlying platform."""
@abc.abstractproperty
def general(self):
"""Get the general object for the underlying platform."""
@abc.abstractproperty
def user_class(self):
"""Get the user class specific to this operating system."""
@abc.abstractproperty
def route_class(self):
"""Get the route class specific to this operating system."""
@abc.abstractproperty
def interface_class(self):
"""Get the interface class specific to this operating system."""

43
cloudinit/osys/general.py Normal file
View File

@ -0,0 +1,43 @@
# Copyright (C) 2015 Canonical Ltd.
# Copyright 2015 Cloudbase Solutions Srl
#
# 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 abc
import six
@six.add_metaclass(abc.ABCMeta)
class General(object):
"""Base class for the general namespace.
This class should contain common functions between all OSes,
which can't be grouped in a domain-specific namespace.
"""
@abc.abstractmethod
def set_timezone(self, timezone):
"""Change the timezone for the underlying platform.
The `timezone` parameter should be a TZID timezone format,
e.g. 'Africa/Mogadishu'
"""
@abc.abstractmethod
def set_locale(self, locale):
"""Change the locale for the underlying platform."""
@abc.abstractmethod
def reboot(self):
pass

152
cloudinit/osys/network.py Normal file
View File

@ -0,0 +1,152 @@
# Copyright (C) 2015 Canonical Ltd.
# Copyright 2015 Cloudbase Solutions Srl
#
# 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 abc
import six
from cloudinit import util
__all__ = (
'Network',
'Route',
'Interface',
)
@six.add_metaclass(abc.ABCMeta)
class Network(object):
"""Base network class for network related utilities."""
@abc.abstractmethod
def routes(self):
"""Get the list of the available routes."""
@abc.abstractmethod
def default_gateway(self):
"""Get the default gateway, as a route object."""
@abc.abstractmethod
def interfaces(self):
"""Get the list of the available interfaces."""
@abc.abstractmethod
def hosts(self):
"""Get the list of the available hosts."""
@abc.abstractmethod
def set_hostname(self, hostname):
"""Change the host name of the instance."""
@abc.abstractmethod
def set_static_network_config(self, adapter_name, address, netmask,
broadcast, gateway, dnsnameservers):
"""Configure a new static network."""
@six.add_metaclass(abc.ABCMeta)
class Route(object):
"""Base class for routes."""
def __init__(self, destination, gateway, netmask,
interface, metric,
flags=None, refs=None, use=None, expire=None):
self.destination = destination
self.gateway = gateway
self.netmask = netmask
self.interface = interface
self.metric = metric
self.flags = flags
self.refs = refs
self.use = use
self.expire = expire
@abc.abstractproperty
def is_static(self):
"""Check if this route is static."""
@util.abstractclassmethod
def add(cls, route):
"""Add a new route in the underlying OS.
The `route` parameter should be an instance of :class:`Route`.
"""
@util.abstractclassmethod
def delete(cls, route):
"""Delete a route from the underlying OS.
The `route` parameter should be an instance of :class:`Route`.
"""
@six.add_metaclass(abc.ABCMeta)
class Interface(object):
"""Base class reprensenting an interface.
It provides both attributes for retrieving interface information,
as well as methods for modifying the state of a route, such
as activating or deactivating it.
"""
def __init__(self, name, mac, index=None, mtu=None,
dhcp_server=None, dhcp_enabled=None):
self._mtu = mtu
self.name = name
self.index = index
self.mac = mac
self.dhcp_server = dhcp_server
self.dhcp_enabled = dhcp_enabled
def __eq__(self, other):
return (self.mac == other.mac and
self.name == other.name and
self.index == other.index)
@abc.abstractmethod
def _change_mtu(self, value):
"""Change the mtu for the underlying interface."""
@util.abstractclassmethod
def from_name(cls, name):
"""Get an instance of :class:`Interface` from an interface name.
E.g. this should retrieve the 'eth0' interface::
>>> Interface.from_name('eth0')
"""
@abc.abstractmethod
def up(self):
"""Activate the current interface."""
@abc.abstractmethod
def down(self):
"""Deactivate the current interface."""
@abc.abstractmethod
def is_up(self):
"""Check if this interface is activated."""
@property
def mtu(self):
return self._mtu
@mtu.setter
def mtu(self, value):
self._change_mtu(value)
self._mtu = value

67
cloudinit/osys/users.py Normal file
View File

@ -0,0 +1,67 @@
# Copyright (C) 2015 Canonical Ltd.
# Copyright 2015 Cloudbase Solutions Srl
#
# 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 abc
import six
from cloudinit import util
@six.add_metaclass(abc.ABCMeta)
class Users(object):
"""Base class for user related operations."""
@abc.abstractmethod
def groups(self):
"""Get a list of the groups available in the system."""
@abc.abstractmethod
def users(self):
"""Get a list of the users available in the system."""
@six.add_metaclass(abc.ABCMeta)
class Group(object):
"""Base class for user groups."""
@util.abstractclassmethod
def create(cls, group_name):
"""Create a new group with the given name."""
@abc.abstractmethod
def add(self, member):
"""Add a new member to this group."""
@six.add_metaclass(abc.ABCMeta)
class User(object):
"""Base class for an user."""
@classmethod
def create(self, username, password, **kwargs):
"""Create a new user."""
@abc.abstractmethod
def home(self):
"""Get the user's home directory."""
@abc.abstractmethod
def ssh_keys(self):
"""Get the ssh keys for this user."""
@abc.abstractmethod
def change_password(self, password):
"""Change the password for this user."""

View File

View File

@ -0,0 +1,50 @@
# Copyright (C) 2015 Canonical Ltd.
# Copyright 2015 Cloudbase Solutions Srl
#
# 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 unittest
try:
import unittest.mock as mock
except ImportError:
import mock
from cloudinit.osys import base
class TestOSUtils(unittest.TestCase):
@mock.patch('importlib.import_module')
@mock.patch('platform.linux_distribution')
@mock.patch('platform.system')
def _test_getosutils(self, mock_system,
mock_linux_distribution, mock_import_module,
linux=False):
if linux:
os_name = 'Linux'
else:
os_name = 'Windows'
mock_system.return_value = os_name
mock_linux_distribution.return_value = (os_name, None, None)
module = base.get_osutils()
mock_import_module.assert_called_once_with(
"cloudinit.osys.{0}.base".format(os_name.lower()))
self.assertEqual(mock_import_module.return_value.OSUtils,
module)
def test_getosutils(self):
self._test_getosutils(linux=True)
self._test_getosutils(linux=False)

26
cloudinit/util.py Normal file
View File

@ -0,0 +1,26 @@
# Copyright (C) 2015 Canonical Ltd.
# Copyright (C) 2015 Cloudbase Solutions Srl
#
# 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.
__all__ = ('abstractclassmethod', )
class abstractclassmethod(classmethod):
"""A backport for abc.abstractclassmethod from Python 3."""
__isabstractmethod__ = True
def __init__(self, func):
func.__isabstractmethod__ = True
super(abstractclassmethod, self).__init__(func)