Add the first layout of the new osys architecture #2

Closed
wants to merge 2,610 commits into
from

Conversation

Projects
None yet
Contributor

PCManticore commented Feb 25, 2015

Hello.

This pull request provides a first layout of how the new OS utils should look, according to the last design spec.
It contains only some of the base classes which were brought to the discussion in the aforementioned spec, mainly the Network namespace, with implementations for the Windows platform. It's not yet complete, since I still need to write the tests, but some feedback to see if I'm on the right way should be enough for now. After this, the rest of the base classes should come, which means we can start already working on bringing the code for other distros inside this new location.

smoser and others added some commits Jul 24, 2014

change trunk packaging to be more modern.
use pybuild and drop cdbs.
This also now runs test during that build and does then require build
dependencies.
test: make selinux test skipped if selinux not available.
Also, in debian packaging depend on it (so it wont skip there).
SeLinuxGuard: remove invalid check for sanity around restorecon, fix …
…test

previous commit occurred because the selinux test was failing
in a schroot where there was no /etc/hosts.

Now, fix that test more correctly, and fix some bad assumptions in
the SeLinuxGuard.
do not put comments in /etc/timezone
comments in /etc/timezone are not expected, and can cause problems
if another tool tries to read it.
cc_power_state_change: improve TypeError messages
Include the value that was found to be invalid in the error message.
add ubuntu-init-switch module for testing systemd.
The module is useful primarily for testing in Ubuntu's transition to systemd.
It should be very harmless elsewhere as it defaults to doing nothing,
and will only run if configured as 'ubuntu' distro *and* 'dpkg' is available.
ubuntu-init-switch: fix for determining if systemd
/run/systemd/ seems to exist even if systemd is not the init system.
but /run/systemd/system/ only exists if it has been used as init.
ubuntu-init-switch: support disabling of reboot, minor tweeks.
- modify timeout for ~60 seconds before going on.
- allow specifying 'reboot' on 'cloud-init single' or in config.
- use stdin for subprocess so log is less noisy.
- reboot --force: this is required this early in boot for upstart
  and to my experience at this point always required for systemd
new: Config for FreeBSD.
This doesn't differ much from the regular (linux) config, but currently
it helps while testing and setting up fbsd cloud instances.
fix: To install the new freebsd sysvinit scripts, accept a new
sysvinit_freebsd argument. Specifying sysvinit will install the RH
scripts, which is wrong.
fix: Set the environment var CLOUD_CFG to specify the location of
cloud.cfg since -f is not used for that.

Given the importance of this file/location, it's explicitly  beeing set
in the initscripts instead of trusting on something in /etc/defaults.
change: Install everything in the right location on both Linux (which
ofcourse already was good) and FreeBSD (which realy likes /usr/local
for this).
fix: Install the python package that will install the required link to
the python2.7 binary. This defaults to 2.7, which is fine.
use url_helper to combine url
This seems cleaner, to avoid duplicate '/' being added.
fix rendering resolv.conf if no 'options' are provided
this makes some changes to the cc_resolv_conf to make its
generate_resolv_conf method more easily callable (for future test).

Also sets it up so that 'options' is always defined when the template
is rendered.
ec2_utils.py: get_instance_metadata explicitly add trailing /
This change works around a bug in CloudStack's EC2 api implementation.
That is filed upstream at [1].

The fix is safe for openstack and EC2 use cases as well.
EC2 and OpenStacks' EC2 metadata service both return a list with
access to either of:
  <url_base>/latest/meta-data
  <url_base>/latest/meta-data/

Additionally, the responses explicitly contain a trailing '/' for
items that have a child.  The ec2_utils code then just re-uses the trailng
/ there.  Thus, only the top level request for 'meta-data/' needs
the explicit fix.

This also changes test cases.  Those test cases failed without fixing
them.  If ever this regressed, those would fail again.

--
[1] https://issues.apache.org/jira/browse/CLOUDSTACK-7405
change: Rename the config file to cloud.cfg-freebsd so it doesn't
get copied per default. Packaging will take care of installing this
configfile on the BSD platform.
Removed using lazy mode for umount
Safer for cloud-init to not use lazy mode for unmount
ssh_authkey_fingerprints: fix bug that prevented disabling the module
Module intended to allow disabling by configuration, but that was broken.
Now this allows:
  no_ssh_fingerprints = True

Joshua Harlow and others added some commits Nov 22, 2014

Increase the robustness/configurability of the chef module...
Add the following adjustments to the chef template and module:

- Make it so that the chef directories can be provided (defaults
  to the existing directories)
- Make the params much more configurable, and if a parameter is
  provided in the chef configuration it will override existing template
  parameters.
- Make the template skip lines if the values are None in the configuration
  so that template lines can be removed if/when this is desirable.
- Allow the firstboot json path to be configurable (defaults to the
  existing location).
- Adds a basic set of tests to ensure that good things are happening.
- Make a helper function to tell if already installed.
- Have the install routine not run chef after installed but have it instead
  return a result to tell the caller to run the chef program once completed.
- Use the generated_by() utility function to give the ruby template a
  better header comment.
- Set special parameters after selecting the basic chef parameters.
- Allow for the running after install and run arguments to be configured.
- Allow the omnibus url fetching retries to be configurable.
- Move the chef running to its own helper function
- Add module docs
Fix parse_ssh_config failing in ssh_util.py
This fix handles '=' as a delimiter in SSH config and
adds appropriate test methods to ensure this functionality
continues to work correctly.
Pretty up the debug module
Previously the usage of the yaml_dumps module was causing
any python unicode key and value to show up as:

'item': !!python/unicode "some string"

This was not very pretty...

Fix this by using safe_dumps (which is also a good thing to
use and allow_unicode=True). Also create a tiny helper function
in the cc_debug module that does not include the yaml start and
end footers (since this module has its own footers and headers).

Also includes a basic sanity test for this module.
Be more tolerant of 'ssh_authorized_keys' types
Instead of only expected a list, tuple, or set type
allow for a string type and dict to be passed in for 'ssh_authorized_keys',
and add log message that occurs if some other type is used that
can not be correctly processed.
Update to use a newer and better OMNIBUS_URL
This is minor change which uses the new Chef (company) top
level domain for grabbing the Omnibus installation shell script.
IPv6 support for rhel distro
  
- Saw an issue in my earlier commit with multiple NICs. This commit
  fixes that issue, along with the indentation issue
Updated unittests
 + Scenario with multiple NICs
IPv6 support for rhel distro
When the ubuntu networking info file has ipv6 addresses inside
it we need to make sure that we parse that information out and
place it (at least) in the rhel network configuration writing.

In later patches the other distros that use this parsed network
configuration will likely also need to be updated (ubuntu and
debian already should function as expected with regard to ipv6
support).
Use assertNotEqual which exists on python2.6
Instead of using assertGreater which is new on
python2.7 just use assertNotEqual which does exist
on python2.6 to perform the same/similar operation.

This makes the unittest not break on python2.6
Fixes rpm spec file build issues (its not currently building).
Currently the rpm building process that cloud-init provides is
not working correctly. This adjusts the spec file, the setup.py
file and the distro files to ensure that it continues to work
as expected.
Be more tolerant of missing information
Instead of failing when IPv6 information is not found we
should be more tolerant of said information not existing
so that we behave like the pre IPv6 addition.
Fix the getgateway function
After the routeinfo function started to return
a dictionary containing ipv4 and ipv6 information
we now need to make sure we search the appropriate
key.
Cleanups for netinfo.py
- Handle ipv6 route information not existing
  gracefully (for systems that don't have it)
- Fix the getgateway function (broken due to
  ipv4/ipv6 keys now existing in route info)
- Separate the centering of the route info ipv4
  information from the centering of the ipv6 information
  so that this looks prettier...
- Use try: except: else instead of settings value to
  None and then later checking for None (more pythonic
  this way)
tools/ccfg-merge-debug: fix for updated user-data/vendor-data
this was broken previously when user-data and vender-data were
brought together.
cloudinit/osys/network.py
+ self.expire = expire
+
+ @abc.abstractproperty
+ def static(self):
@harlowja

harlowja Mar 3, 2015

Contributor

I'm wondering if this should be is_static what do u think?

@PCManticore

PCManticore Mar 3, 2015

Contributor

Sounds good. And it's clearer.

+ """
+
+ def __init__(self):
+ self._route_items = self._routes()
@harlowja

harlowja Mar 3, 2015

Contributor

So this feels sorta odd; but maybe its just me. Why not just lazily populate this information instead of populating it before the object is fully constructed? Doing things before the object actually is ready always seems odd to me.

@PCManticore

PCManticore Mar 3, 2015

Contributor

So if I'm understanding correctly, do you want to lazily populate the routes only when they are needed?

@harlowja

harlowja Mar 3, 2015

Contributor

Something like that ya.

cloudinit/osys/network.py
+ return self._route_items[index]
+
+ def __delitem__(self, index):
+ self.__class__.delete(self._route_items[index])
@harlowja

harlowja Mar 3, 2015

Contributor

Wouldn't self.delete also work (and also be less confusing)?

@PCManticore

PCManticore Mar 3, 2015

Contributor

The delete methods will work on the class, since it will be pretty strange to delete a route from a route instance. But the delitem special method was added in the case you have a collection already and you want to change it. It could definitely be a YAGNI at this point. Should I drop it?

@harlowja

harlowja Mar 3, 2015

Contributor

Except classmethods can also be called from the instance, self.delete will look for a instance method, and then just find the class one, so IMHO its prefereable and less confusing just to do that (it also allows people to override delete in other instances).

cloudinit/osys/network.py
+
+
+@six.add_metaclass(abc.ABCMeta)
+class BaseRouteCollection(collections.Sequence):
@harlowja

harlowja Mar 3, 2015

Contributor

So I'm still wondering, but do we need to prefix all these with 'Base'; is that adding any real value?

cloudinit/osys/network.py
+
+
+@six.add_metaclass(abc.ABCMeta)
+class BaseInterface(object):
@harlowja

harlowja Mar 3, 2015

Contributor

Same question about prefix things with 'Base'; I think just 'Interface' would have the same meaning, its pretty obvious its a base class due to the six.add_metaclass(abc.ABCMeta) usage.

@PCManticore

PCManticore Mar 3, 2015

Contributor

There needs to be some marker other that six.add_metaclass to determine that a class is a base class when working with it, if it will be used outside of the file where it was defined (you won't see there that it has ABCMeta as a metaclass and it will require an extra step to go and check, while a Base in the name suggests this from the start).

@harlowja

harlowja Mar 3, 2015

Contributor

Meh, this is python, people will have to look at the base class code to know this anyway. And docs should tell this also.... Prefixing all the things with 'Base' just adds noise imho.

+from cloudinit.osys.windows.util import kernel32
+from cloudinit.osys.windows.util import ws2_32
+
+
@harlowja

harlowja Mar 3, 2015

Contributor

Comments about what these are would be really useful ;)

@PCManticore

PCManticore Mar 3, 2015

Contributor

Indeed. :)

cloudinit/osys/windows/network.py
+ return dhcp_server
+ except Exception as ex:
+ # Not found
+ if ex.errno != 2:
@harlowja

harlowja Mar 3, 2015

Contributor

Is there not a named constant that can be used instead of 2?

@PCManticore

PCManticore Mar 3, 2015

Contributor

Yes, there is, thanks for noticing.

+
+from cloudinit.osys.windows.util import kernel32
+from cloudinit.osys.windows.util import ws2_32
+
@harlowja

harlowja Mar 3, 2015

Contributor

Same question about comments, these are better, but still unsure about some of them (what is GAA_??)

@PCManticore

PCManticore Mar 3, 2015

Contributor

I'll add some comments, thanks.

Contributor

harlowja commented Mar 3, 2015

So looks pretty good to me. Added some comments. The windows stuff seems like it should have more comments and I don't really know if it works (tests here would be great to verify this); I also don't have a windows machine so that's another problem. How did we plan on testing the variations here? Is there any site that can do this (does travis-ci have windows support?)...

cloudinit/osys/util.py
+__all__ = ('abstractclassmethod', )
+
+
+class abstractclassmethod(classmethod):
@harlowja

harlowja Mar 3, 2015

Contributor

This almost seems like it should be in a file @ cloudinit/util.py since this seems more generic and useful outside of 'osys' right?

@PCManticore

PCManticore Mar 3, 2015

Contributor

Yep, indeed.

+ general = general_module.General()
+ user_class = users_module.User
+ route_class = network_module.Route
+ interface_class = network_module.Interface
@harlowja

harlowja Mar 3, 2015

Contributor

Something feels off, why isn't there a corresponding general_class?

@PCManticore

PCManticore Mar 3, 2015

Contributor

At this point, I didn't feel that it was needed. On the other hand, route_class / user_class / interface_class are actually used for controlling various aspects of what they represent (they have some class methods for creating / deleting objects basically).

@harlowja

harlowja Mar 4, 2015

Contributor

Ok dokie.

+
+ @staticmethod
+ def check_os_version(major, minor, build=0):
+ vi = kernel32.Win32_OSVERSIONINFOEX_W()
@harlowja

harlowja Mar 3, 2015

Contributor

Woah, comments here would be great for non-windows people ...

@PCManticore

PCManticore Mar 3, 2015

Contributor

Indeed, will add a couple of them.

+ winreg.KEY_READ) as key:
+ try:
+ dhcp_server = winreg.QueryValueEx(key, "DhcpServer")[0]
+ if dhcp_server == "255.255.255.255":
@harlowja

harlowja Mar 3, 2015

Contributor

Does this need a ipv6 equivalent?

@PCManticore

PCManticore Mar 3, 2015

Contributor

Most likely. We don't have support for this in cloudbase-init, that's why it's not present at the moment.

cloudinit/osys/windows/network.py
+ return interfaces
+
+ if ret_val:
+ raise Exception("GetAdaptersAddresses failed")
@harlowja

harlowja Mar 3, 2015

Contributor

Some other kind of exception would be useful. Perhaps should be a cloudinit/exceptions.py that has these exceptions. Just raising Exception doesn't really allow anyone to catch specific errors/exceptions...

@PCManticore

PCManticore Mar 3, 2015

Contributor

Yeah, I was thinking on adding a custom exceptions file.

@harlowja

harlowja Mar 3, 2015

Contributor

Go for it.

cloudinit/osys/windows/network.py
+ q = conn.query("SELECT * FROM Win32_NetworkAdapter WHERE "
+ "MACAddress = '{}'".format(mac_address))
+ if not len(q):
+ raise Exception("Network adapter not found")
@harlowja

harlowja Mar 3, 2015

Contributor

Same thing about not using Exception

cloudinit/osys/windows/network.py
+ LOG.debug("Setting static IP address")
+ (ret_val, ) = adapter_config.EnableStatic([address], [netmask])
+ if ret_val > 1:
+ raise Exception(
@harlowja

harlowja Mar 3, 2015

Contributor

Same thing about not using Exception

cloudinit/osys/windows/network.py
+ LOG.debug("Setting static gateways")
+ (ret_val, ) = adapter_config.SetGateways([gateway], [1])
+ if ret_val > 1:
+ raise Exception("Cannot set gateway on network adapter")
@harlowja

harlowja Mar 3, 2015

Contributor

Same thing about not using Exception

cloudinit/osys/windows/network.py
+ LOG.debug("Setting static DNS servers")
+ (ret_val,) = adapter_config.SetDNSServerSearchOrder(dnsnameservers)
+ if ret_val > 1:
+ raise Exception("Cannot set DNS on network adapter")
@harlowja

harlowja Mar 3, 2015

Contributor

Same thing about not using Exception

cloudinit/osys/windows/network.py
+ _ComputerNamePhysicalDnsHostname,
+ six.text_type(hostname))
+ if not ret_val:
+ raise Exception("Cannot set host name")
@harlowja

harlowja Mar 3, 2015

Contributor

Same thing about not using Exception

cloudinit/osys/windows/network.py
+ fw_profile = fw_profile.GloballyOpenPorts.Add(fw_port)
+
+ def firewall_remove_rule(self, _, port, protocol, allow=True):
+ if not allow:
@harlowja

harlowja Mar 3, 2015

Contributor

Hmmm, seems like something other than NotImplementedError should be raised here since it actually is implemented but not allowed...

cloudinit/osys/windows/network.py
+ "store=persistent"]
+ ret_val = subprocess.call(args, shell=False)
+ if ret_val:
+ raise Exception(
@harlowja

harlowja Mar 3, 2015

Contributor

Same thing about not using Exception

cloudinit/osys/windows/network.py
+ _general = general.General()
+
+ if not _general.check_os_version(6, 0):
+ raise Exception(
@harlowja

harlowja Mar 3, 2015

Contributor

Same thing about not using Exception

cloudinit/osys/windows/network.py
+ _, stderr = popen.communicate()
+ if popen.returncode or stderr:
+ # Cannot use the return value to determine the outcome
+ raise Exception('Unable to add route: %s' % stderr)
@harlowja

harlowja Mar 3, 2015

Contributor

Same thing about not using Exception

+ args = ['ROUTE', 'ADD',
+ route.destination,
+ 'MASK', route.netmask, route.gateway]
+ popen = subprocess.Popen(args, shell=False,
@harlowja

harlowja Mar 3, 2015

Contributor

We should likely take over the code from http://bazaar.launchpad.net/~cloud-init-dev/cloud-init/trunk/view/head:/cloudinit/util.py#L1627 (the subp function there) and use that instead, since its better and more generic for the use-cases here. Put that in cloudinit/util.py and use it?

@PCManticore

PCManticore Mar 3, 2015

Contributor

Yep, sounds good.

cloudinit/osys/windows/network.py
+ ctypes.byref(size), 0)
+ if err != kernel32.ERROR_NO_DATA:
+ if err:
+ raise Exception(
@harlowja

harlowja Mar 3, 2015

Contributor

Same thing about not using Exception

cloudinit/osys/windows/network.py
+ kernel32.HeapFree(heap, 0, p_forward_table)
+ p = kernel32.HeapAlloc(heap, 0, ctypes.c_size_t(size.value))
+ if not p:
+ raise Exception(
@harlowja

harlowja Mar 3, 2015

Contributor

Same thing about not using Exception

cloudinit/osys/windows/network.py
+ size = wintypes.ULONG(forward_table_size)
+ p = kernel32.HeapAlloc(heap, 0, ctypes.c_size_t(size.value))
+ if not p:
+ raise Exception(
@harlowja

harlowja Mar 3, 2015

Contributor

Same thing about not using Exception

Contributor

PCManticore commented Mar 3, 2015

Thanks! I'm currently writing tests, but they are not ideal, because they are using mocks. On the other hand, we have a generic CI system written for cloudbase-init and I'm planning to use that for the cloud-init v2 as well, after we will have more than "something". It's pretty generic and can be reused for any CI related business (The code for this system is here:https://github.com/PCManticore/argus-ci/tree/develop)

Contributor

harlowja commented Mar 3, 2015

Ya, it'd be really nice to be able to run this without mocks, like on a real windows test environment. Maybe get microsoft to finish travis-ci/travis-ci#216 ;)

Contributor

harlowja commented Mar 3, 2015

Or travis-ci/travis-ci#2104 ; one of those, ha.

Add the first layout of the new osys architecture
This patch provides some of the base classes, which composes the new
operating system utils architecture, based on a OOP approach and separation of
concerns. The utils is namespace is composed from other subnamespaces,
each dealing with one particular area, such as networking or filesystem interaction.
A first layout for the network namespace is provided, as well as a first
operating system utils namespace for the Windows platform.
Contributor

smoser commented Feb 1, 2017

closing this as it is against 2.0 branch

@smoser smoser closed this Feb 1, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment