Exemplo n.º 1
0
class LXDContainerManagement(object):
    def __init__(self, module):
        """Management of LXC containers via Ansible.

        :param module: Processed Ansible Module.
        :type module: ``object``
        """
        self.module = module
        self.name = self.module.params['name']
        self._build_config()

        self.state = self.module.params['state']

        self.timeout = self.module.params['timeout']
        self.wait_for_ipv4_addresses = self.module.params['wait_for_ipv4_addresses']
        self.force_stop = self.module.params['force_stop']
        self.addresses = None

        self.key_file = self.module.params.get('key_file', None)
        self.cert_file = self.module.params.get('cert_file', None)
        self.debug = self.module._verbosity >= 4

        try:
            if os.path.exists(self.module.params['snap_url'].replace('unix:', '')):
                self.url = self.module.params['snap_url']
            else:
                self.url = self.module.params['url']
        except Exception as e:
            self.module.fail_json(msg=e.msg)

        try:
            self.client = LXDClient(
                self.url, key_file=self.key_file, cert_file=self.cert_file,
                debug=self.debug
            )
        except LXDClientException as e:
            self.module.fail_json(msg=e.msg)
        self.trust_password = self.module.params.get('trust_password', None)
        self.actions = []

    def _build_config(self):
        self.config = {}
        for attr in CONFIG_PARAMS:
            param_val = self.module.params.get(attr, None)
            if param_val is not None:
                self.config[attr] = param_val

    def _get_container_json(self):
        return self.client.do(
            'GET', '/1.0/containers/{0}'.format(self.name),
            ok_error_codes=[404]
        )

    def _get_container_state_json(self):
        return self.client.do(
            'GET', '/1.0/containers/{0}/state'.format(self.name),
            ok_error_codes=[404]
        )

    @staticmethod
    def _container_json_to_module_state(resp_json):
        if resp_json['type'] == 'error':
            return 'absent'
        return ANSIBLE_LXD_STATES[resp_json['metadata']['status']]

    def _change_state(self, action, force_stop=False):
        body_json = {'action': action, 'timeout': self.timeout}
        if force_stop:
            body_json['force'] = True
        return self.client.do('PUT', '/1.0/containers/{0}/state'.format(self.name), body_json=body_json)

    def _create_container(self):
        config = self.config.copy()
        config['name'] = self.name
        self.client.do('POST', '/1.0/containers', config)
        self.actions.append('create')

    def _start_container(self):
        self._change_state('start')
        self.actions.append('start')

    def _stop_container(self):
        self._change_state('stop', self.force_stop)
        self.actions.append('stop')

    def _restart_container(self):
        self._change_state('restart', self.force_stop)
        self.actions.append('restart')

    def _delete_container(self):
        self.client.do('DELETE', '/1.0/containers/{0}'.format(self.name))
        self.actions.append('delete')

    def _freeze_container(self):
        self._change_state('freeze')
        self.actions.append('freeze')

    def _unfreeze_container(self):
        self._change_state('unfreeze')
        self.actions.append('unfreez')

    def _container_ipv4_addresses(self, ignore_devices=None):
        ignore_devices = ['lo'] if ignore_devices is None else ignore_devices

        resp_json = self._get_container_state_json()
        network = resp_json['metadata']['network'] or {}
        network = dict((k, v) for k, v in network.items() if k not in ignore_devices) or {}
        addresses = dict((k, [a['address'] for a in v['addresses'] if a['family'] == 'inet']) for k, v in network.items()) or {}
        return addresses

    @staticmethod
    def _has_all_ipv4_addresses(addresses):
        return len(addresses) > 0 and all(len(v) > 0 for v in addresses.values())

    def _get_addresses(self):
        try:
            due = datetime.datetime.now() + datetime.timedelta(seconds=self.timeout)
            while datetime.datetime.now() < due:
                time.sleep(1)
                addresses = self._container_ipv4_addresses()
                if self._has_all_ipv4_addresses(addresses):
                    self.addresses = addresses
                    return
        except LXDClientException as e:
            e.msg = 'timeout for getting IPv4 addresses'
            raise

    def _started(self):
        if self.old_state == 'absent':
            self._create_container()
            self._start_container()
        else:
            if self.old_state == 'frozen':
                self._unfreeze_container()
            elif self.old_state == 'stopped':
                self._start_container()
            if self._needs_to_apply_container_configs():
                self._apply_container_configs()
        if self.wait_for_ipv4_addresses:
            self._get_addresses()

    def _stopped(self):
        if self.old_state == 'absent':
            self._create_container()
        else:
            if self.old_state == 'stopped':
                if self._needs_to_apply_container_configs():
                    self._start_container()
                    self._apply_container_configs()
                    self._stop_container()
            else:
                if self.old_state == 'frozen':
                    self._unfreeze_container()
                if self._needs_to_apply_container_configs():
                    self._apply_container_configs()
                self._stop_container()

    def _restarted(self):
        if self.old_state == 'absent':
            self._create_container()
            self._start_container()
        else:
            if self.old_state == 'frozen':
                self._unfreeze_container()
            if self._needs_to_apply_container_configs():
                self._apply_container_configs()
            self._restart_container()
        if self.wait_for_ipv4_addresses:
            self._get_addresses()

    def _destroyed(self):
        if self.old_state != 'absent':
            if self.old_state == 'frozen':
                self._unfreeze_container()
            if self.old_state != 'stopped':
                self._stop_container()
            self._delete_container()

    def _frozen(self):
        if self.old_state == 'absent':
            self._create_container()
            self._start_container()
            self._freeze_container()
        else:
            if self.old_state == 'stopped':
                self._start_container()
            if self._needs_to_apply_container_configs():
                self._apply_container_configs()
            self._freeze_container()

    def _needs_to_change_container_config(self, key):
        if key not in self.config:
            return False
        if key == 'config':
            old_configs = dict((k, v) for k, v in self.old_container_json['metadata'][key].items() if not k.startswith('volatile.'))
            for k, v in self.config['config'].items():
                if old_configs[k] != v:
                    return True
            return False
        else:
            old_configs = self.old_container_json['metadata'][key]
            return self.config[key] != old_configs

    def _needs_to_apply_container_configs(self):
        return (
            self._needs_to_change_container_config('architecture') or
            self._needs_to_change_container_config('config') or
            self._needs_to_change_container_config('ephemeral') or
            self._needs_to_change_container_config('devices') or
            self._needs_to_change_container_config('profiles')
        )

    def _apply_container_configs(self):
        old_metadata = self.old_container_json['metadata']
        body_json = {
            'architecture': old_metadata['architecture'],
            'config': old_metadata['config'],
            'devices': old_metadata['devices'],
            'profiles': old_metadata['profiles']
        }
        if self._needs_to_change_container_config('architecture'):
            body_json['architecture'] = self.config['architecture']
        if self._needs_to_change_container_config('config'):
            for k, v in self.config['config'].items():
                body_json['config'][k] = v
        if self._needs_to_change_container_config('ephemeral'):
            body_json['ephemeral'] = self.config['ephemeral']
        if self._needs_to_change_container_config('devices'):
            body_json['devices'] = self.config['devices']
        if self._needs_to_change_container_config('profiles'):
            body_json['profiles'] = self.config['profiles']
        self.client.do('PUT', '/1.0/containers/{0}'.format(self.name), body_json=body_json)
        self.actions.append('apply_container_configs')

    def run(self):
        """Run the main method."""

        try:
            if self.trust_password is not None:
                self.client.authenticate(self.trust_password)

            self.old_container_json = self._get_container_json()
            self.old_state = self._container_json_to_module_state(self.old_container_json)
            action = getattr(self, LXD_ANSIBLE_STATES[self.state])
            action()

            state_changed = len(self.actions) > 0
            result_json = {
                'log_verbosity': self.module._verbosity,
                'changed': state_changed,
                'old_state': self.old_state,
                'actions': self.actions
            }
            if self.client.debug:
                result_json['logs'] = self.client.logs
            if self.addresses is not None:
                result_json['addresses'] = self.addresses
            self.module.exit_json(**result_json)
        except LXDClientException as e:
            state_changed = len(self.actions) > 0
            fail_params = {
                'msg': e.msg,
                'changed': state_changed,
                'actions': self.actions
            }
            if self.client.debug:
                fail_params['logs'] = e.kwargs['logs']
            self.module.fail_json(**fail_params)
Exemplo n.º 2
0
class LXDContainerManagement(object):
    def __init__(self, module):
        """Management of LXC containers via Ansible.

        :param module: Processed Ansible Module.
        :type module: ``object``
        """
        self.module = module
        self.name = self.module.params['name']
        self._build_config()

        self.state = self.module.params['state']

        self.timeout = self.module.params['timeout']
        self.wait_for_ipv4_addresses = self.module.params['wait_for_ipv4_addresses']
        self.force_stop = self.module.params['force_stop']
        self.addresses = None

        self.url = self.module.params['url']
        self.key_file = self.module.params.get('key_file', None)
        self.cert_file = self.module.params.get('cert_file', None)
        self.debug = self.module._verbosity >= 4
        try:
            self.client = LXDClient(
                self.url, key_file=self.key_file, cert_file=self.cert_file,
                debug=self.debug
            )
        except LXDClientException as e:
            self.module.fail_json(msg=e.msg)
        self.trust_password = self.module.params.get('trust_password', None)
        self.actions = []

    def _build_config(self):
        self.config = {}
        for attr in CONFIG_PARAMS:
            param_val = self.module.params.get(attr, None)
            if param_val is not None:
                self.config[attr] = param_val

    def _get_container_json(self):
        return self.client.do(
            'GET', '/1.0/containers/{0}'.format(self.name),
            ok_error_codes=[404]
        )

    def _get_container_state_json(self):
        return self.client.do(
            'GET', '/1.0/containers/{0}/state'.format(self.name),
            ok_error_codes=[404]
        )

    @staticmethod
    def _container_json_to_module_state(resp_json):
        if resp_json['type'] == 'error':
            return 'absent'
        return ANSIBLE_LXD_STATES[resp_json['metadata']['status']]

    def _change_state(self, action, force_stop=False):
        body_json={'action': action, 'timeout': self.timeout}
        if force_stop:
            body_json['force'] = True
        return self.client.do('PUT', '/1.0/containers/{0}/state'.format(self.name), body_json=body_json)

    def _create_container(self):
        config = self.config.copy()
        config['name'] = self.name
        self.client.do('POST', '/1.0/containers', config)
        self.actions.append('create')

    def _start_container(self):
        self._change_state('start')
        self.actions.append('start')

    def _stop_container(self):
        self._change_state('stop', self.force_stop)
        self.actions.append('stop')

    def _restart_container(self):
        self._change_state('restart', self.force_stop)
        self.actions.append('restart')

    def _delete_container(self):
        self.client.do('DELETE', '/1.0/containers/{0}'.format(self.name))
        self.actions.append('delete')

    def _freeze_container(self):
        self._change_state('freeze')
        self.actions.append('freeze')

    def _unfreeze_container(self):
        self._change_state('unfreeze')
        self.actions.append('unfreez')

    def _container_ipv4_addresses(self, ignore_devices=['lo']):
        resp_json = self._get_container_state_json()
        network = resp_json['metadata']['network'] or {}
        network = dict((k, v) for k, v in network.items() if k not in ignore_devices) or {}
        addresses = dict((k, [a['address'] for a in v['addresses'] if a['family'] == 'inet']) for k, v in network.items()) or {}
        return addresses

    @staticmethod
    def _has_all_ipv4_addresses(addresses):
        return len(addresses) > 0 and all([len(v) > 0 for v in addresses.itervalues()])

    def _get_addresses(self):
        try:
            due = datetime.datetime.now() + datetime.timedelta(seconds=self.timeout)
            while datetime.datetime.now() < due:
                time.sleep(1)
                addresses = self._container_ipv4_addresses()
                if self._has_all_ipv4_addresses(addresses):
                    self.addresses = addresses
                    return
        except LXDClientException as e:
            e.msg = 'timeout for getting IPv4 addresses'
            raise

    def _started(self):
        if self.old_state == 'absent':
            self._create_container()
            self._start_container()
        else:
            if self.old_state == 'frozen':
                self._unfreeze_container()
            elif self.old_state == 'stopped':
                self._start_container()
            if self._needs_to_apply_container_configs():
                self._apply_container_configs()
        if self.wait_for_ipv4_addresses:
            self._get_addresses()

    def _stopped(self):
        if self.old_state == 'absent':
            self._create_container()
        else:
            if self.old_state == 'stopped':
                if self._needs_to_apply_container_configs():
                    self._start_container()
                    self._apply_container_configs()
                    self._stop_container()
            else:
                if self.old_state == 'frozen':
                    self._unfreeze_container()
                if self._needs_to_apply_container_configs():
                    self._apply_container_configs()
                self._stop_container()

    def _restarted(self):
        if self.old_state == 'absent':
            self._create_container()
            self._start_container()
        else:
            if self.old_state == 'frozen':
                self._unfreeze_container()
            if self._needs_to_apply_container_configs():
                self._apply_container_configs()
            self._restart_container()
        if self.wait_for_ipv4_addresses:
            self._get_addresses()

    def _destroyed(self):
        if self.old_state != 'absent':
            if self.old_state == 'frozen':
                self._unfreeze_container()
            if self.old_state != 'stopped':
                self._stop_container()
            self._delete_container()

    def _frozen(self):
        if self.old_state == 'absent':
            self._create_container()
            self._start_container()
            self._freeze_container()
        else:
            if self.old_state == 'stopped':
                self._start_container()
            if self._needs_to_apply_container_configs():
                self._apply_container_configs()
            self._freeze_container()

    def _needs_to_change_container_config(self, key):
        if key not in self.config:
            return False
        if key == 'config':
            old_configs = dict((k, v) for k, v in self.old_container_json['metadata'][key].items() if not k.startswith('volatile.'))
        else:
            old_configs = self.old_container_json['metadata'][key]
        return self.config[key] != old_configs

    def _needs_to_apply_container_configs(self):
        return (
            self._needs_to_change_container_config('architecture') or
            self._needs_to_change_container_config('config') or
            self._needs_to_change_container_config('ephemeral') or
            self._needs_to_change_container_config('devices') or
            self._needs_to_change_container_config('profiles')
        )

    def _apply_container_configs(self):
        old_metadata = self.old_container_json['metadata']
        body_json = {
            'architecture': old_metadata['architecture'],
            'config': old_metadata['config'],
            'devices': old_metadata['devices'],
            'profiles': old_metadata['profiles']
        }
        if self._needs_to_change_container_config('architecture'):
            body_json['architecture'] = self.config['architecture']
        if self._needs_to_change_container_config('config'):
            for k, v in self.config['config'].items():
                body_json['config'][k] = v
        if self._needs_to_change_container_config('ephemeral'):
            body_json['ephemeral'] = self.config['ephemeral']
        if self._needs_to_change_container_config('devices'):
            body_json['devices'] = self.config['devices']
        if self._needs_to_change_container_config('profiles'):
            body_json['profiles'] = self.config['profiles']
        self.client.do('PUT', '/1.0/containers/{0}'.format(self.name), body_json=body_json)
        self.actions.append('apply_container_configs')

    def run(self):
        """Run the main method."""

        try:
            if self.trust_password is not None:
                self.client.authenticate(self.trust_password)

            self.old_container_json = self._get_container_json()
            self.old_state = self._container_json_to_module_state(self.old_container_json)
            action = getattr(self, LXD_ANSIBLE_STATES[self.state])
            action()

            state_changed = len(self.actions) > 0
            result_json = {
                'log_verbosity': self.module._verbosity,
                'changed': state_changed,
                'old_state': self.old_state,
                'actions': self.actions
            }
            if self.client.debug:
                result_json['logs'] = self.client.logs
            if self.addresses is not None:
                result_json['addresses'] = self.addresses
            self.module.exit_json(**result_json)
        except LXDClientException as e:
            state_changed = len(self.actions) > 0
            fail_params = {
                'msg': e.msg,
                'changed': state_changed,
                'actions': self.actions
            }
            if self.client.debug:
                fail_params['logs'] = e.kwargs['logs']
            self.module.fail_json(**fail_params)
Exemplo n.º 3
0
class LXDProfileManagement(object):
    def __init__(self, module):
        """Management of LXC containers via Ansible.

        :param module: Processed Ansible Module.
        :type module: ``object``
        """
        self.module = module
        self.name = self.module.params['name']
        self._build_config()
        self.state = self.module.params['state']
        self.new_name = self.module.params.get('new_name', None)

        self.url = self.module.params['url']
        self.key_file = self.module.params.get('key_file', None)
        self.cert_file = self.module.params.get('cert_file', None)
        self.debug = self.module._verbosity >= 4
        try:
            self.client = LXDClient(self.url,
                                    key_file=self.key_file,
                                    cert_file=self.cert_file,
                                    debug=self.debug)
        except LXDClientException as e:
            self.module.fail_json(msg=e.msg)
        self.trust_password = self.module.params.get('trust_password', None)
        self.actions = []

    def _build_config(self):
        self.config = {}
        for attr in CONFIG_PARAMS:
            param_val = self.module.params.get(attr, None)
            if param_val is not None:
                self.config[attr] = param_val

    def _get_profile_json(self):
        return self.client.do('GET',
                              '/1.0/profiles/{0}'.format(self.name),
                              ok_error_codes=[404])

    @staticmethod
    def _profile_json_to_module_state(resp_json):
        if resp_json['type'] == 'error':
            return 'absent'
        return 'present'

    def _update_profile(self):
        if self.state == 'present':
            if self.old_state == 'absent':
                if self.new_name is None:
                    self._create_profile()
                else:
                    self.module.fail_json(
                        msg=
                        'new_name must not be set when the profile does not exist and the specified state is present',
                        changed=False)
            else:
                if self.new_name is not None and self.new_name != self.name:
                    self._rename_profile()
                if self._needs_to_apply_profile_configs():
                    self._apply_profile_configs()
        elif self.state == 'absent':
            if self.old_state == 'present':
                if self.new_name is None:
                    self._delete_profile()
                else:
                    self.module.fail_json(
                        msg=
                        'new_name must not be set when the profile exists and the specified state is absent',
                        changed=False)

    def _create_profile(self):
        config = self.config.copy()
        config['name'] = self.name
        self.client.do('POST', '/1.0/profiles', config)
        self.actions.append('create')

    def _rename_profile(self):
        config = {'name': self.new_name}
        self.client.do('POST', '/1.0/profiles/{}'.format(self.name), config)
        self.actions.append('rename')
        self.name = self.new_name

    def _needs_to_change_profile_config(self, key):
        if key not in self.config:
            return False
        old_configs = self.old_profile_json['metadata'].get(key, None)
        return self.config[key] != old_configs

    def _needs_to_apply_profile_configs(self):
        return (self._needs_to_change_profile_config('config')
                or self._needs_to_change_profile_config('description')
                or self._needs_to_change_profile_config('devices'))

    def _apply_profile_configs(self):
        config = self.old_profile_json.copy()
        for k, v in self.config.items():
            config[k] = v
        self.client.do('PUT', '/1.0/profiles/{}'.format(self.name), config)
        self.actions.append('apply_profile_configs')

    def _delete_profile(self):
        self.client.do('DELETE', '/1.0/profiles/{}'.format(self.name))
        self.actions.append('delete')

    def run(self):
        """Run the main method."""

        try:
            if self.trust_password is not None:
                self.client.authenticate(self.trust_password)

            self.old_profile_json = self._get_profile_json()
            self.old_state = self._profile_json_to_module_state(
                self.old_profile_json)
            self._update_profile()

            state_changed = len(self.actions) > 0
            result_json = {
                'changed': state_changed,
                'old_state': self.old_state,
                'actions': self.actions
            }
            if self.client.debug:
                result_json['logs'] = self.client.logs
            self.module.exit_json(**result_json)
        except LXDClientException as e:
            state_changed = len(self.actions) > 0
            fail_params = {
                'msg': e.msg,
                'changed': state_changed,
                'actions': self.actions
            }
            if self.client.debug:
                fail_params['logs'] = e.kwargs['logs']
            self.module.fail_json(**fail_params)
Exemplo n.º 4
0
class LXDProfileManagement(object):
    def __init__(self, module):
        """Management of LXC containers via Ansible.

        :param module: Processed Ansible Module.
        :type module: ``object``
        """
        self.module = module
        self.name = self.module.params['name']
        self._build_config()
        self.state = self.module.params['state']
        self.new_name = self.module.params.get('new_name', None)

        self.url = self.module.params['url']
        self.key_file = self.module.params.get('key_file', None)
        self.cert_file = self.module.params.get('cert_file', None)
        self.debug = self.module._verbosity >= 4
        try:
            self.client = LXDClient(
                self.url, key_file=self.key_file, cert_file=self.cert_file,
                debug=self.debug
            )
        except LXDClientException as e:
            self.module.fail_json(msg=e.msg)
        self.trust_password = self.module.params.get('trust_password', None)
        self.actions = []

    def _build_config(self):
        self.config = {}
        for attr in CONFIG_PARAMS:
            param_val = self.module.params.get(attr, None)
            if param_val is not None:
                self.config[attr] = param_val

    def _get_profile_json(self):
        return self.client.do(
            'GET', '/1.0/profiles/{0}'.format(self.name),
            ok_error_codes=[404]
        )

    @staticmethod
    def _profile_json_to_module_state(resp_json):
        if resp_json['type'] == 'error':
            return 'absent'
        return 'present'

    def _update_profile(self):
        if self.state == 'present':
            if self.old_state == 'absent':
                if self.new_name is None:
                    self._create_profile()
                else:
                    self.module.fail_json(
                        msg='new_name must not be set when the profile does not exist and the specified state is present',
                        changed=False)
            else:
                if self.new_name is not None and self.new_name != self.name:
                    self._rename_profile()
                if self._needs_to_apply_profile_configs():
                    self._apply_profile_configs()
        elif self.state == 'absent':
            if self.old_state == 'present':
                if self.new_name is None:
                    self._delete_profile()
                else:
                    self.module.fail_json(
                        msg='new_name must not be set when the profile exists and the specified state is absent',
                        changed=False)

    def _create_profile(self):
        config = self.config.copy()
        config['name'] = self.name
        self.client.do('POST', '/1.0/profiles', config)
        self.actions.append('create')

    def _rename_profile(self):
        config = {'name': self.new_name}
        self.client.do('POST', '/1.0/profiles/{}'.format(self.name), config)
        self.actions.append('rename')
        self.name = self.new_name

    def _needs_to_change_profile_config(self, key):
        if key not in self.config:
            return False
        old_configs = self.old_profile_json['metadata'].get(key, None)
        return self.config[key] != old_configs

    def _needs_to_apply_profile_configs(self):
        return (
            self._needs_to_change_profile_config('config') or
            self._needs_to_change_profile_config('description') or
            self._needs_to_change_profile_config('devices')
        )

    def _apply_profile_configs(self):
        config = self.old_profile_json.copy()
        for k, v in self.config.items():
            config[k] = v
        self.client.do('PUT', '/1.0/profiles/{}'.format(self.name), config)
        self.actions.append('apply_profile_configs')

    def _delete_profile(self):
        self.client.do('DELETE', '/1.0/profiles/{}'.format(self.name))
        self.actions.append('delete')

    def run(self):
        """Run the main method."""

        try:
            if self.trust_password is not None:
                self.client.authenticate(self.trust_password)

            self.old_profile_json = self._get_profile_json()
            self.old_state = self._profile_json_to_module_state(self.old_profile_json)
            self._update_profile()

            state_changed = len(self.actions) > 0
            result_json = {
                'changed': state_changed,
                'old_state': self.old_state,
                'actions': self.actions
            }
            if self.client.debug:
                result_json['logs'] = self.client.logs
            self.module.exit_json(**result_json)
        except LXDClientException as e:
            state_changed = len(self.actions) > 0
            fail_params = {
                'msg': e.msg,
                'changed': state_changed,
                'actions': self.actions
            }
            if self.client.debug:
                fail_params['logs'] = e.kwargs['logs']
            self.module.fail_json(**fail_params)