[PATCH 0/6 V3] Host's repositories management support

V2 -> V3: * PEP8 compatibility * Improved mockmodel.py and model/host.py * Updated API.json and backend files to use new exception error system code V1 -> V2: * Removed the enable/disable from docs/API.md and src/kimchi/API.json * fixed wrong URI on test_rest.py * PEP8 compatibility * Updated repositories.py to make consistent with docs/API.md * Raised correct exceptions * Changed model/host.py and mockmodel.py to generate repo_id if not provided * Changed mockmodel.py to declare MockRepositories class V1: This patch set provides support to host's repositories management operations. At this point, an agnostic class is providing support to backend and REST API operations. In addition, YUM (for RHEL, Fedora, SLES and OpenSuse) and APT (for Debian and Ubuntu) specific classes are provided to support the operation os each software update system. To test the backend execute the following commands: $ cd tests $ sudo ./run_tests.sh test_model.ModelTests.test_repository_create $ sudo ./run_tests.sh test_model.ModelTests.test_repository_update $ sudo ./run_tests.sh test_model.ModelTests.test_repository_disable_enable To test the REST API, execute the following commands (all them are agnostic of the host's distro): 1) Get list of all repositories enabled in the host: $ curl -u <USER> -H 'Content-type: application/json' -H 'Accept: application/json' http://localhost:8000/host/repositories/ -X GET 2) Create a new repository: $ curl -u <USER> -H 'Content-type: application/json' -H 'Accept: application/json' http://localhost:8000/host/repositories/ -X POST -d' { "repo_id": "fedora-fake", "baseurl": "http://www.fedora.org", "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-fake-19" } ' 3) Get information from a specific repository: $ curl -u <USER> -H 'Content-type: application/json' -H 'Accept: application/json' http://localhost:8000/host/repositories/fedora-fake 4) Update a specific repository: $ curl -u <USER> -H 'Content-type: application/json' -H 'Accept: application/json' http://localhost:8000/host/repositories/fedora-fake -X PUT -d' { "repo_id":"fedora-fake", "repo_name":"Fedora 19 FAKEs", "baseurl": "http://www.fedora.org/downloads" } ' 5) Disable a specific repository: $ curl -u <USER> -H 'Content-type: application/json' -H 'Accept: application/json' http://localhost:8000/host/repositories/fedora-fake/disable -X POST -d '' 6) Enable a specific repository: $ curl -u <USER> -H 'Content-type: application/json' -H 'Accept: application/json' http://localhost:8000/host/repositories/fedora-fake/enable -X POST -d '' 7) Delete a specific repository: $ curl -u <USER> -H 'Content-type: application/json' -H 'Accept: application/json' http://localhost:8000/host/repositories/fedora-fake -X DELETE Paulo Vital (6): Host's repositories management: Update API.md Host's repositories management: Update REST API Host's repositories management: Update backend. Host's repositories management: Update Makefile Host's repositories management: Update test-cases. Host's repositories management: Update error exception messages. Makefile.am | 1 + docs/API.md | 77 ++++++ src/kimchi/API.json | 76 ++++++ src/kimchi/Makefile.am | 1 + src/kimchi/control/host.py | 21 ++ src/kimchi/i18n.py | 19 ++ src/kimchi/mockmodel.py | 133 +++++++++++ src/kimchi/model/host.py | 54 ++++- src/kimchi/repositories.py | 585 +++++++++++++++++++++++++++++++++++++++++++++ tests/test_model.py | 100 ++++++++ tests/test_rest.py | 33 +++ 11 files changed, 1099 insertions(+), 1 deletion(-) create mode 100644 src/kimchi/repositories.py -- 1.8.3.1

Define get and create API for repositories collection. Define get, update, enable and disable API for repository resource. Signed-off-by: Paulo Vital <pvital@linux.vnet.ibm.com> --- docs/API.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/docs/API.md b/docs/API.md index 8fafb91..24936b0 100644 --- a/docs/API.md +++ b/docs/API.md @@ -831,3 +831,80 @@ Contains the information for a specific package to be updated. * arch: The architecture of the package * version: The new version of the package * repository: The repository name from where package will be downloaded + +### Collection: Host Repositories + +**URI:** /host/repositories + +**Methods:** + +* **GET**: Retrieve a summarized list of all repositories available +* **POST**: Add a new repository + * repo_id *(optional)*: Unique repository name for each repository, +one word. + * baseurl: URL to the repodata directory when "is_mirror" is false. +Otherwise, it can be URL to the mirror system for YUM. Can be an +http://, ftp:// or file:// URL. + * is_mirror *(optional)*: Set the given URI of baseurl as a mirror +list, instead of use baseurl in repository configuration. + * url_args *(optional)*: Arguments to be passed to baseurl, like the +list of APT repositories provided by the same baseurl. + * gpgkey *(optional)*: URL pointing to the ASCII-armored GPG key +file for the repository. This option is used if yum needs a public key +to verify a package and the required key hasn't been imported into the +RPM database. + +### Resource: Repository + +**URI:** /host/repositories/*:repo-id* + +**Methods:** + +* **GET**: Retrieve the full description of a Repository + * repo_id: Unique repository name for each repository, one word. + * repo_name: Human-readable string describing the repository. + * baseurl: URL to the repodata directory when "is_mirror" is false. +Otherwise, it can be URL to the mirror system for YUM. Can be an +http://, ftp:// or file:// URL. + * is_mirror: Set the given URI of baseurl as a mirror list, instead +of use baseurl in repository configuration. + * url_args: Arguments to be passed to baseurl, like the list of APT +repositories provided by the same baseurl. + * enabled: Indicates if repository should be included as a package +source: + * false: Do not include the repository. + * true: Include the repository. + * gpgcheck: Indicates if a GPG signature check on the packages gotten +from repository should be performed: + * false: Do not check GPG signature + * true: Check GPG signature + * gpgkey: URL pointing to the ASCII-armored GPG key file for the +repository. This option is used if yum needs a public key to verify a package +and the required key hasn't been imported into the RPM database. +* **DELETE**: Remove the Repository +* **POST**: *See Repository Actions* +* **PUT**: update the parameters of existing Repository + * repo_id *(otional)*: Unique repository name for each repository, +one word. + * repo_name *(otional)*: Human-readable string describing the +repository. + * baseurl *(optional)*: URL to the repodata directory when +"is_mirror" is false. Otherwise, it can be URL to the mirror system for +YUM. Can be an http://, ftp:// or file:// URL. + * is_mirror *(optional)*: Set the given URI of baseurl as a mirror +list, instead of use baseurl in repository configuration. + * url_args *(optional)*: Arguments to be passed to baseurl, like the +list of APT repositories provided by the same baseurl. + * gpgcheck *(optional)*: Indicates if a GPG signature check on the +packages gotten from repository should be performed: + * false: Do not check GPG signature + * true: Check GPG signature + * gpgkey *(optional)*: URL pointing to the ASCII-armored GPG key +file for the repository. This option is used if yum needs a public key +to verify a package and the required key hasn't been imported into the +RPM database. + +**Actions (POST):** + +* enable: Enable the Repository as package source +* disable: Disable the Repository as package source -- 1.8.3.1

Define Repositories collection and Repository resource according to API.md Update API.json Activate auth support to new collection Signed-off-by: Paulo Vital <pvital@linux.vnet.ibm.com> --- src/kimchi/API.json | 76 ++++++++++++++++++++++++++++++++++++++++++++++ src/kimchi/control/host.py | 21 +++++++++++++ 2 files changed, 97 insertions(+) diff --git a/src/kimchi/API.json b/src/kimchi/API.json index c36244c..68037fc 100644 --- a/src/kimchi/API.json +++ b/src/kimchi/API.json @@ -478,6 +478,82 @@ }, "additionalProperties": false, "error": "KCHAPI0001E" + }, + "repository_create": { + "type": "object", + "properties": { + "repo_id": { + "description": "Unique repository name for each repository, one word.", + "type": "string", + "error": "KCHREPOS0001E" + }, + "baseurl": { + "description": "URL to the directory where the repodata directory of a repository is located. Can be an http://, ftp:// or file:// URL.", + "type": "string", + "pattern": "^((file):|(http)[s]?:|(ftp)[s]?:)?$", + "required": true, + "error": "KCHREPOS0002E" + }, + "is_mirror": { + "description": "Set the given URI of baseurl as a mirror list", + "type": "boolean" + }, + "url_args": { + "description": "Arguments to be passed to baseurl, like the list of APT repositories provided by the same baseurl.", + "type": "string", + "error": "KCHREPOS0003E" + }, + "gpgkey": { + "description": "URL pointing to the ASCII-armored GPG key file for the repository.", + "pattern": "^((file):|(http)[s]?:|(ftp)[s]?:)?$", + "type": "string", + "error": "KCHREPOS0004E" + } + }, + "additionalProperties": false, + "error": "KCHAPI0001E" + }, + "repository_update": { + "type": "object", + "properties": { + "repo_id": { + "description": "Unique repository name for each repository, one word.", + "type": "string", + "error": "KCHREPOS0001E" + }, + "repo_name": { + "description": "Human-readable string describing the repository.", + "type": "string", + "error": "KCHREPOS0005E" + }, + "baseurl": { + "description": "URL to the directory where the repodata directory of a repository is located. Can be an http://, ftp:// or file:// URL.", + "type": "string", + "pattern": "^((file):|(http)[s]?:|(ftp)[s]?:)?$", + "error": "KCHREPOS0002E" + }, + "is_mirror": { + "description": "Set the given URI of baseurl as a mirror list", + "type": "boolean" + }, + "url_args": { + "description": "Arguments to be passed to baseurl, like the list of APT repositories provided by the same baseurl.", + "type": "string", + "error": "KCHREPOS0003E" + }, + "gpgcheck": { + "description": "Indicates if a GPG signature check on the packages gotten from repository should be performed.", + "type": "boolean" + }, + "gpgkey": { + "description": "URL pointing to the ASCII-armored GPG key file for the repository.", + "pattern": "^((file):|(http)[s]?:|(ftp)[s]?:)?$", + "type": "string", + "error": "KCHREPOS0004E" + } + }, + "additionalProperties": false, + "error": "KCHAPI0001E" } } } diff --git a/src/kimchi/control/host.py b/src/kimchi/control/host.py index a705f59..6e9cf35 100644 --- a/src/kimchi/control/host.py +++ b/src/kimchi/control/host.py @@ -42,6 +42,7 @@ class Host(Resource): self.partitions = Partitions(self.model) self.devices = Devices(self.model) self.packagesupdate = PackagesUpdate(self.model) + self.repositories = Repositories(self.model) @property def data(self): @@ -106,3 +107,23 @@ class PackageUpdate(Resource): @property def data(self): return self.info + + +class Repositories(Collection): + def __init__(self, model): + super(Repositories, self).__init__(model) + self.resource = Repository + + +class Repository(Resource): + def __init__(self, model, id): + super(Repository, self).__init__(model, id) + self.update_params = ["repo_id", "repo_name", "baseurl", "mirrors", + "url_args", "enabled", "gpgcheck", "gpgkey"] + self.uri_fmt = "/host/repositories/%s" + self.enable = self.generate_action_handler('enable') + self.disable = self.generate_action_handler('disable') + + @property + def data(self): + return self.info -- 1.8.3.1

On 02/15/2014 07:49 PM, Paulo Vital wrote:
Define Repositories collection and Repository resource according to API.md Update API.json Activate auth support to new collection
Signed-off-by: Paulo Vital <pvital@linux.vnet.ibm.com> --- src/kimchi/API.json | 76 ++++++++++++++++++++++++++++++++++++++++++++++ src/kimchi/control/host.py | 21 +++++++++++++ 2 files changed, 97 insertions(+)
diff --git a/src/kimchi/API.json b/src/kimchi/API.json index c36244c..68037fc 100644 --- a/src/kimchi/API.json +++ b/src/kimchi/API.json @@ -478,6 +478,82 @@ }, "additionalProperties": false, "error": "KCHAPI0001E" + }, + "repository_create": { + "type": "object", + "properties": { + "repo_id": { + "description": "Unique repository name for each repository, one word.", + "type": "string", + "error": "KCHREPOS0001E" + }, + "baseurl": { + "description": "URL to the directory where the repodata directory of a repository is located. Can be an http://, ftp:// or file:// URL.", + "type": "string", + "pattern": "^((file):|(http)[s]?:|(ftp)[s]?:)?$", + "required": true, + "error": "KCHREPOS0002E" + }, + "is_mirror": { + "description": "Set the given URI of baseurl as a mirror list", + "type": "boolean" + }, + "url_args": { + "description": "Arguments to be passed to baseurl, like the list of APT repositories provided by the same baseurl.", + "type": "string", + "error": "KCHREPOS0003E" + }, + "gpgkey": { + "description": "URL pointing to the ASCII-armored GPG key file for the repository.", + "pattern": "^((file):|(http)[s]?:|(ftp)[s]?:)?$", + "type": "string", + "error": "KCHREPOS0004E" + } + }, + "additionalProperties": false, + "error": "KCHAPI0001E" + }, + "repository_update": { + "type": "object", + "properties": { + "repo_id": { + "description": "Unique repository name for each repository, one word.", + "type": "string", + "error": "KCHREPOS0001E" + }, + "repo_name": { + "description": "Human-readable string describing the repository.", + "type": "string", + "error": "KCHREPOS0005E" + }, + "baseurl": { + "description": "URL to the directory where the repodata directory of a repository is located. Can be an http://, ftp:// or file:// URL.", + "type": "string", + "pattern": "^((file):|(http)[s]?:|(ftp)[s]?:)?$", + "error": "KCHREPOS0002E" + }, + "is_mirror": { + "description": "Set the given URI of baseurl as a mirror list", + "type": "boolean" + }, + "url_args": { + "description": "Arguments to be passed to baseurl, like the list of APT repositories provided by the same baseurl.", + "type": "string", + "error": "KCHREPOS0003E" + }, + "gpgcheck": { + "description": "Indicates if a GPG signature check on the packages gotten from repository should be performed.", + "type": "boolean" + }, + "gpgkey": { + "description": "URL pointing to the ASCII-armored GPG key file for the repository.", + "pattern": "^((file):|(http)[s]?:|(ftp)[s]?:)?$", + "type": "string", + "error": "KCHREPOS0004E" + } + }, + "additionalProperties": false, + "error": "KCHAPI0001E" } } } diff --git a/src/kimchi/control/host.py b/src/kimchi/control/host.py index a705f59..6e9cf35 100644 --- a/src/kimchi/control/host.py +++ b/src/kimchi/control/host.py @@ -42,6 +42,7 @@ class Host(Resource): self.partitions = Partitions(self.model) self.devices = Devices(self.model) self.packagesupdate = PackagesUpdate(self.model) + self.repositories = Repositories(self.model)
@property def data(self): @@ -106,3 +107,23 @@ class PackageUpdate(Resource): @property def data(self): return self.info + + +class Repositories(Collection): + def __init__(self, model): + super(Repositories, self).__init__(model) + self.resource = Repository + + +class Repository(Resource): + def __init__(self, model, id): + super(Repository, self).__init__(model, id) + self.update_params = ["repo_id", "repo_name", "baseurl", "mirrors", + "url_args", "enabled", "gpgcheck", "gpgkey"]
'enabled' should not be there. It will be updated through actions enable/disable.
+ self.uri_fmt = "/host/repositories/%s" + self.enable = self.generate_action_handler('enable') + self.disable = self.generate_action_handler('disable') + + @property + def data(self): + return self.info

Update model and mockmodel to support backend opertions. Add new file implementing backend operations. There are two new classes: 1) Repositories (object): Class to represent and operate with repositories information in Kimchi's perspective. It's agnostic to host;s package management system, and can execute all operations necessary: add repository, get all repositories list, get information about one repository, update a repository, enable and disable a repository and remove a repository. This class will load in runtime the necessary classes to work with the host's package management: YumRepo for YUM systems based and AptRepo for APT systems based (support for this last one will be submited in a close future); 2) YumRepo (object): Class to represent and operate with YUM repositories. Loaded only on those systems that supports YUM, it's responsible to connect, collect and provide information of YUM repositories in the system. Also it's responsible to create/delete the files in disk to maintain the repositories in system after disconnection. Signed-off-by: Paulo Vital <pvital@linux.vnet.ibm.com> --- src/kimchi/mockmodel.py | 133 +++++++++++ src/kimchi/model/host.py | 54 ++++- src/kimchi/repositories.py | 585 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 771 insertions(+), 1 deletion(-) create mode 100644 src/kimchi/repositories.py diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index b7b5952..2835120 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -79,6 +79,7 @@ class MockModel(object): self._mock_swupdate = MockSoftwareUpdate() self.next_taskid = 1 self.storagepool_activate('default') + self.host_repositories = MockRepositories() def _static_vm_update(self, dom, params): state = dom.info['state'] @@ -787,6 +788,47 @@ class MockModel(object): task_id = self.add_task('', self._mock_swupdate.doUpdate, None) return self.task_lookup(task_id) + def repositories_get_list(self): + return self.host_repositories.getRepositories().keys() + + def repositories_create(self, params): + repo_id = params.get('repo_id', None) + + # Create a repo_id if not given by user. The repo_id will follow + # the format kimchi_repo_<integer>, where integer is the number of + # seconds since the Epoch (January 1st, 1970), in UTC. + if repo_id is None: + repo_id = "kimchi_repo_%s" % int(time.time()) + while repo_id in self.repositories_get_list(): + repo_id = "kimchi_repo_%s" % int(time.time()) + params.update({'repo_id': repo_id}) + + if repo_id in self.repositories_get_list(): + raise InvalidOperation("KCHREPOS0006E", {'repo_id', repo_id}) + self.host_repositories.addRepository(params) + return repo_id + + def repository_lookup(self, repo_id): + return self.host_repositories.getRepository(repo_id) + + def repository_delete(self, repo_id): + return self.host_repositories.removeRepository(repo_id) + + def repository_enable(self, repo_id): + if not self.host_repositories.enableRepository(repo_id): + raise OperationFailed("KCHREPOS0007E", {'repo_id', repo_id}) + + def repository_disable(self, repo_id): + if not self.host_repositories.disableRepository(repo_id): + raise OperationFailed("KCHREPOS0008E", {'repo_id', repo_id}) + + def repository_update(self, repo_id, params): + try: + self.host_repositories.updateRepository(repo_id, params) + except: + raise OperationFailed("KCHREPOS0009E", {'repo_id', repo_id}) + return repo_id + class MockVMTemplate(VMTemplate): def __init__(self, args, mockmodel_inst=None): @@ -1013,6 +1055,97 @@ class MockSoftwareUpdate(object): cb('\n'.join(msgs), True) +class MockRepositories(object): + def __init__(self): + self._repo_storage = {{"repo_id": "kimchi_repo_1392167832", + "gpgkey": None, + "enabled": True, + "baseurl": "http://www.fedora.org", + "url_args": None, + "gpgcheck": True, + "is_mirror": False, + "repo_name": "kimchi_repo_1392167832"}} + + def addRepository(self, params={}): + # Create and enable the repository + repo = {'repo_id': repo_id, + 'repo_name': params.get('repo_name', repo_id), + 'baseurl': params.get('baseurl'), + 'url_args': params.get('url_args', None), + 'enabled': True, + 'gpgkey': params.get('gpgkey', None), + 'is_mirror': params.get('is_mirror', False)} + + if repo['gpgkey'] is not None: + repo['gpgcheck'] = True + else: + repo['gpgcheck'] = False + + self._repo_storage[repo_id] = repo + + def getRepositories(self): + return self._repo_storage + + def getRepository(self, repo_id): + if not repo_id in self._repo_storage.keys(): + raise NotFoundError("KCHREPOS0010E", {'repo_id': repo_id}) + + repo = self._repo_storage[repo_id] + if (isinstance(repo['baseurl'], list)) and (len(repo['baseurl']) > 0): + repo['baseurl'] = repo['baseurl'][0] + + return repo + + def enabledRepositories(self): + enabled_repos = [] + for repo_id in self._repo_storage.keys(): + if self._repo_storage[repo_id]['enabled']: + enabled_repos.append(repo_id) + return enabled_repos + + def enableRepository(self, repo_id): + # Check if repo_id is already enabled + if repo_id in self.enabledRepositories(): + raise NotFoundError("KCHREPOS0011E", {'repo_id': repo_id}) + + try: + repo = self.getRepository(repo_id) + repo['enabled'] = True + self.updateRepository(repo_id, repo) + return True + except: + raise OperationFailed("KCHREPOS0007E", {'repo_id': repo_id}) + + def disableRepository(self, repo_id): + # Check if repo_id is already disabled + if not repo_id in self.enabledRepositories(): + raise NotFoundError("KCHREPOS0012E", {'repo_id': repo_id}) + + try: + repo = self.getRepository(repo_id) + repo['enabled'] = False + self.updateRepository(repo_id, repo) + return True + except: + raise OperationFailed("KCHREPOS0008E", {'repo_id': repo_id}) + + def updateRepository(self, repo_id, new_repo={}): + if (len(new_repo) == 0): + raise InvalidParameter("KCHREPOS0013E") + + repo = self._repo_storage[repo_id] + repo.update(new_repo) + del self._repo_storage[repo_id] + self._repo_storage[repo_id] = repo + + def removeRepository(self, repo_id): + if not repo_id in self._repo_storage.keys(): + raise NotFoundError("KCHREPOS0010E", {'repo_id': repo_id}) + + del self._repo_storage[repo_id] + return True + + def get_mock_environment(): model = MockModel() for i in xrange(5): diff --git a/src/kimchi/model/host.py b/src/kimchi/model/host.py index 68028b2..3af48fe 100644 --- a/src/kimchi/model/host.py +++ b/src/kimchi/model/host.py @@ -32,10 +32,11 @@ from kimchi import disks from kimchi import netinfo from kimchi import xmlutils from kimchi.basemodel import Singleton -from kimchi.exception import NotFoundError, OperationFailed +from kimchi.exception import InvalidOperation, NotFoundError, OperationFailed from kimchi.model.config import CapabilitiesModel from kimchi.model.tasks import TaskModel from kimchi.model.vms import DOM_STATE_MAP +from kimchi.repositories import Repositories from kimchi.swupdate import SoftwareUpdate from kimchi.utils import add_task, kimchi_log @@ -299,3 +300,54 @@ class PackageUpdateModel(object): raise OperationFailed('KCHPKGUPD0004E') return swupdate.getUpdate(name) + + +class RepositoriesModel(object): + def __init__(self, **kargs): + self.host_repositories = Repositories() + + def get_list(self): + return self.host_repositories.getRepositories().keys() + + def create(self, params): + repo_id = params.get('repo_id', None) + + # Create a repo_id if not given by user. The repo_id will follow + # the format kimchi_repo_<integer>, where integer is the number of + # seconds since the Epoch (January 1st, 1970), in UTC. + if repo_id is None: + repo_id = "kimchi_repo_%s" % int(time.time()) + while repo_id in self.repositories_get_list(): + repo_id = "kimchi_repo_%s" % int(time.time()) + params.update({'repo_id': repo_id}) + + if repo_id in self.get_list(): + raise InvalidOperation("KCHREPOS0006E", {'repo_id', repo_id}) + self.host_repositories.addRepository(params) + return repo_id + + +class RepositoryModel(object): + def __init__(self, **kargs): + self._repositories = Repositories() + + def lookup(self, repo_id): + return self._repositories.getRepository(repo_id) + + def enable(self, repo_id): + if not self._repositories.enableRepository(repo_id): + raise OperationFailed("KCHREPOS0007E", {'repo_id', repo_id}) + + def disable(self, repo_id): + if not self._repositories.disableRepository(repo_id): + raise OperationFailed("KCHREPOS0008E", {'repo_id', repo_id}) + + def update(self, repo_id, params): + try: + self._repositories.updateRepository(repo_id, params) + except: + raise OperationFailed("KCHREPOS0009E", {'repo_id', repo_id}) + return repo_id + + def delete(self, repo_id): + return self._repositories.removeRepository(repo_id) diff --git a/src/kimchi/repositories.py b/src/kimchi/repositories.py new file mode 100644 index 0000000..9b53e98 --- /dev/null +++ b/src/kimchi/repositories.py @@ -0,0 +1,585 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2014 +# +# Authors: +# Paulo Vital <pvital@linux.vnet.ibm.com> +# +# 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 +import platform + +from kimchi.basemodel import Singleton +from kimchi.exception import InvalidOperation, InvalidParameter +from kimchi.exception import OperationFailed, NotFoundError, MissingParameter + +YUM_DISTROS = ['fedora', 'red hat enterprise linux', + 'red hat enterprise linux server', 'opensuse ', + 'suse linux enterprise server '] +APT_DISTROS = ['debian', 'ubuntu'] + + +class Repositories(object): + __metaclass__ = Singleton + + """ + Class to represent and operate with repositories information. + """ + def __init__(self): + # This stores all repositories for Kimchi perspective. It's a + # dictionary of dictionaries, in the format {<repo_id>: {repo}}, + # where: + # repo = {'repo_id': <string>, 'repo_name': <string>, + # 'baseurl': ([<string>], None), 'url_args': ([<string>, None), + # 'enabled': True/False, 'gpgcheck': True/False, + # 'gpgkey': ([<string>], None), + # 'is_mirror': True/False} + + self._repo_storage = {} + + self._distro = platform.linux_distribution()[0].lower() + if (self._distro in YUM_DISTROS): + self._pkg_mnger = YumRepo() + elif (self._distro in APT_DISTROS): + self._pkg_mnger = AptRepo() + else: + raise InvalidOperation("KCHREPOS0014E") + + if self._pkg_mnger: + # update the self._repo_storage with system's repositories + self._scanSystemRepositories() + + def _scanSystemRepositories(self): + """ + Update repositories._repo_storage with system's (host) repositories. + """ + # Call system pkg_mnger to get the repositories as list of dict. + for repo in self._pkg_mnger.getRepositoriesList(): + self.addRepository(repo) + + def addRepository(self, params={}): + """ + Add and enable a new repository into repositories._repo_storage. + """ + # Create and enable the repository + repo = {'repo_id': repo_id, + 'repo_name': params.get('repo_name', repo_id), + 'baseurl': params.get('baseurl'), + 'url_args': params.get('url_args', None), + 'enabled': True, + 'gpgkey': params.get('gpgkey', None), + 'is_mirror': params.get('is_mirror', False)} + + if repo['gpgkey'] is not None: + repo['gpgcheck'] = True + else: + repo['gpgcheck'] = False + + self._repo_storage[repo_id] = repo + + # Check in self._pkg_mnger if the repository already exists there + if not repo_id in [irepo['repo_id'] for irepo in + self._pkg_mnger.getRepositoriesList()]: + self._pkg_mnger.addRepo(repo) + + def getRepositories(self): + """ + Return a dictionary with all Kimchi's repositories. Each element uses + the format {<repo_id>: {repo}}, where repo is a dictionary in the + repositories.Repositories() format. + """ + return self._repo_storage + + def getRepository(self, repo_id): + """ + Return a dictionary with all info from a given repository ID. + """ + if not repo_id in self._repo_storage.keys(): + raise NotFoundError("KCHREPOS0010E", {'repo_id': repo_id}) + + repo = self._repo_storage[repo_id] + if (isinstance(repo['baseurl'], list)) and (len(repo['baseurl']) > 0): + repo['baseurl'] = repo['baseurl'][0] + + return repo + + def getRepositoryFromPkgMnger(self, repo_id): + """ + Return a dictionary with all info from a given repository ID. + All info come from self._pkg_mnger.getRepo(). + """ + return self._pkg_mnger.getRepo(repo_id) + + def enabledRepositories(self): + """ + Return a list with enabled repositories IDs. + """ + enabled_repos = [] + for repo_id in self._repo_storage.keys(): + if self._repo_storage[repo_id]['enabled']: + enabled_repos.append(repo_id) + return enabled_repos + + def enableRepository(self, repo_id): + """ + Enable a repository. + """ + # Check if repo_id is already enabled + if repo_id in self.enabledRepositories(): + raise NotFoundError("KCHREPOS0011E", {'repo_id': repo_id}) + + try: + repo = self.getRepository(repo_id) + repo['enabled'] = True + self.updateRepository(repo_id, repo) + self._pkg_mnger.enableRepo(repo_id) + return True + except: + raise OperationFailed("KCHREPOS0007E", {'repo_id': repo_id}) + + def disableRepository(self, repo_id): + """ + Disable a given repository. + """ + # Check if repo_id is already disabled + if not repo_id in self.enabledRepositories(): + raise NotFoundError("KCHREPOS0012E", {'repo_id': repo_id}) + + try: + repo = self.getRepository(repo_id) + repo['enabled'] = False + self.updateRepository(repo_id, repo) + self._pkg_mnger.disableRepo(repo_id) + return True + except: + raise OperationFailed("KCHREPOS0008E", {'repo_id': repo_id}) + + def updateRepository(self, repo_id, new_repo={}): + """ + Update the information of a given repository. + The input is the repo_id of the repository to be updated and a dict + with the information to be updated. + """ + if (len(new_repo) == 0): + raise InvalidParameter("KCHREPOS0013E") + + repo = self._repo_storage[repo_id] + repo.update(new_repo) + + del self._repo_storage[repo_id] + self._repo_storage[repo_id] = repo + self._pkg_mnger.updateRepo(repo_id, self._repo_storage[repo_id]) + + def removeRepository(self, repo_id): + """ + Remove a given repository + """ + if not repo_id in self._repo_storage.keys(): + raise NotFoundError("KCHREPOS0010E", {'repo_id': repo_id}) + + del self._repo_storage[repo_id] + self._pkg_mnger.removeRepo(repo_id) + return True + + +class YumRepo(object): + """ + Class to represent and operate with YUM repositories. + It's loaded only on those systems listed at YUM_DISTROS and loads necessary + modules in runtime. + """ + def __init__(self): + self._yb = getattr(__import__('yum'), 'YumBase')() + self._repos = self._yb.repos + self._conf = self._yb.conf + self._enabled_repos = self._repos.listEnabled() + + def getRepositoriesList(self): + """ + Return a list of dictionaries in the repositories.Repositories() format + """ + repo_list = [] + for repo in self.enabledRepos(): + irepo = {} + irepo['repo_id'] = repo.id + irepo['repo_name'] = repo.name + irepo['url_args'] = None, + irepo['enabled'] = repo.enabled + irepo['gpgcheck'] = repo.gpgcheck + irepo['gpgkey'] = repo.gpgkey + if len(repo.baseurl) > 0: + irepo['baseurl'] = repo.baseurl + irepo['is_mirror'] = False + else: + irepo['baseurl'] = [repo.mirrorlist] + irepo['is_mirror'] = True + repo_list.append(irepo) + return repo_list + + def addRepo(self, repo={}): + """ + Add a given repository in repositories.Repositories() format to YumBase + """ + if len(repo) == 0: + raise InvalidParameter("KCHREPOS0013E") + + # At least one base url, or one mirror, must be given. + # baseurls must be a list of strings specifying the urls + # mirrorlist must be a list of strings specifying a list of mirrors + # Here we creates the lists, or set as None + if repo['is_mirror']: + mirrors = repo['baseurl'] + baseurl = None + else: + baseurl = [repo['baseurl']] + mirrors = None + + self._yb.add_enable_repo(repo['repo_id'], baseurl, mirrors, + name=repo['repo_name'], + gpgcheck=repo['gpgcheck'], + gpgkey=[repo['gpgkey']]) + + # write a repo file in the system with repo{} information. + self._write2disk(repo) + + def getRepo(self, repo_id): + """ + Return a dictionary in the repositories.Repositories() of the given + repository ID format with the information of a YumRepository object. + """ + try: + repo = self._repos.getRepo(repo_id) + irepo = {} + irepo['repo_id'] = repo.id + irepo['repo_name'] = repo.name + irepo['url_args'] = None, + irepo['enabled'] = repo.enabled + irepo['gpgcheck'] = repo.gpgcheck + irepo['gpgkey'] = repo.gpgkey + if len(repo.baseurl) > 0: + irepo['baseurl'] = repo.baseurl + irepo['is_mirror'] = False + else: + irepo['baseurl'] = [repo.mirrorlist] + irepo['is_mirror'] = True + return irepo + except: + raise OperationFailed("KCHREPOS0010E", {'repo_id': repo_id}) + + def enabledRepos(self): + """ + Return a list with enabled YUM repositories IDs + """ + return self._enabled_repos + + def isRepoEnable(self, repo_id): + """ + Return if a given repository ID is enabled or not + """ + for repo in self.enabledRepos(): + if repo_id == repo.id: + return True + return False + + def enableRepo(self, repo_id): + """ + Enable a given repository + """ + try: + self._repos.getRepo(repo_id).enable() + self._repos.doSetup() + return True + except: + raise OperationFailed("KCHREPOS0007E", {'repo_id': repo_id}) + + def disableRepo(self, repo_id): + """ + Disable a given repository + """ + try: + self._repos.getRepo(repo_id).disable() + self._repos.doSetup() + return True + except: + raise OperationFailed("KCHREPOS0008E", {'repo_id': repo_id}) + + def updateRepo(self, repo_id, repo={}): + """ + Update a given repository in repositories.Repositories() format + """ + if len(repo) == 0: + raise MissingParameter("KCHREPOS0013E") + + self._repos.delete(repo_id) + self.addRepo(repo) + + def removeRepo(self, repo_id): + """ + Remove a given repository + """ + try: + self._repos.delete(repo_id) + self._removefromdisk(repo_id) + except: + raise OperationFailed("KCHREPOS0018E", {'repo_id': repo_id}) + + def _write2disk(self, repo={}): + """ + Write repository info into disk. + """ + # Get a list with all reposdir configured in system's YUM. + conf_dir = self._conf.reposdir + if not conf_dir: + raise NotFoundError("KCHREPOS0015E") + + if len(repo) == 0: + raise InvalidParameter("KCHREPOS0016E") + + # Generate the content to be wrote. + repo_content = '[%s]\n' % repo['repo_id'] + repo_content = repo_content + 'name=%s\n' % repo['repo_name'] + + if isinstance(repo['baseurl'], list): + link = repo['baseurl'][0] + else: + link = repo['baseurl'] + + if repo['is_mirror']: + repo_content = repo_content + 'mirrorlist=%s\n' % link + else: + repo_content = repo_content + 'baseurl=%s\n' % link + + if repo['enabled']: + repo_content = repo_content + 'enabled=1\n' + else: + repo_content = repo_content + 'enabled=0\n' + + if repo['gpgcheck']: + repo_content = repo_content + 'gpgcheck=1\n' + else: + repo_content = repo_content + 'gpgcheck=0\n' + + if repo['gpgkey']: + if isinstance(repo['gpgkey'], list): + link = repo['gpgkey'][0] + else: + link = repo['gpgkey'] + repo_content = repo_content + 'gpgckey=%s\n' % link + + # Scan for the confdirs and write the file in the first available + # directory in the system. YUM will scan each confdir for repo files + # and load it contents, so we can write in the first available dir. + for dir in conf_dir: + if os.path.isdir(dir): + repo_file = dir + '/%s.repo' % repo['repo_id'] + if os.path.isfile(repo_file): + os.remove(repo_file) + + try: + with open(repo_file, 'w') as fd: + fd.write(repo_content) + fd.close() + except: + raise OperationFailed("KCHREPOS0017E", + {'repo_file': repo_file}) + break + return True + + def _removefromdisk(self, repo_id): + """ + Delete the repo file from disk of a given repository + """ + conf_dir = self._conf.reposdir + if not conf_dir: + raise NotFoundError("KCHREPOS0015E") + + for dir in conf_dir: + if os.path.isdir(dir): + repo_file = dir + '/%s.repo' % repo_id + if os.path.isfile(repo_file): + os.remove(repo_file) + + return True + + +class AptRepo(object): + """ + Class to represent and operate with YUM repositories. + It's loaded only on those systems listed at YUM_DISTROS and loads necessary + modules in runtime. + """ + def __init__(self): + getattr(__import__('apt_pkg'), 'init_config')() + getattr(__import__('apt_pkg'), 'init_system')() + self._config = getattr(__import__('apt_pkg'), 'config') + self._etc_slist = '/%s%s' % (self._config.get('Dir::Etc'), + self._config.get('Dir::Etc::sourcelist')) + self._etc_sparts = '/%s%s' % (self._config.get('Dir::Etc'), + self._config.get('Dir::Etc::sourceparts')) + + module = __import__('aptsources.sourceslist', globals(), locals(), + ['SourcesList'], -1) + self._repos = getattr(module, 'SourcesList')() + + def getRepositoriesList(self): + """ + Return a list of dictionaries in the repositories.Repositories() format + """ + repo_list = [] + for repo in self.enabledRepos(): + irepo = {} + if repo.file == self._etc_slist: + id = "%s%s" % (repo.uri.split("//")[1], repo.dist) + else: + id = repo.file.split('/')[-1].split('.')[0] + irepo['repo_id'] = id + irepo['baseurl'] = repo.uri + list = [repo.dist] + for comp in repo.comps: + list.append(comp) + irepo['url_args'] = list + irepo['enabled'] = True + irepo['is_mirror'] = False + irepo['gpgcheck'] = False + irepo['gpgkey'] = None + repo_list.append(irepo) + return repo_list + + def addRepo(self, repo={}): + """ + Add a given repository in repositories.Repositories() format to APT + """ + if len(repo) == 0: + raise InvalidParameter("KCHREPOS0013E") + + if repo['url_args'] is not None: + dist = repo['url_args'][0] + args = repo['url_args'][1:] + else: + dist = None + args = [] + + _file = '%s/%s.list' % (self._etc_sparts, repo['repo_id']) + + self._repos.add('deb', repo['baseurl'], dist, args, file=_file) + self._repos.save() + + def getRepo(self, repo_id): + """ + Return a dictionary in the repositories.Repositories() format of the + given repository ID with the information of a SourceEntry object. + """ + for repo in self._repos: + irepo = {} + if repo.file == self._etc_slist: + id = "%s%s" % (repo.uri.split("//")[1], repo.dist) + else: + id = repo.file.split('/')[-1].split('.')[0] + irepo['repo_id'] = id + irepo['baseurl'] = repo.uri + list = [repo.dist] + for comp in repo.comps: + list.append(comp) + irepo['url_args'] = list + irepo['enabled'] = repo.enabled + irepo['is_mirror'] = False + irepo['gpgcheck'] = False + irepo['gpgkey'] = None + return irepo + raise OperationFailed("KCHREPOS0010E", {'repo_id': repo_id}) + + def enabledRepos(self): + """ + Return a list with enabled APT repositories + """ + enabled_repos = [] + self._repos.refresh() + for repo in self._repos: + if (len(repo.str()) > 3) and (not repo.disabled): + if repo.type == 'deb': + enabled_repos.append(repo) + return enabled_repos + + def enableRepo(self, repo_id): + """ + Enable a given repository + """ + try: + lrepo = self._genSourceLine(repo_id) + self._repos.refresh() + for repo in self._repos: + if repo.disabled and (lrepo == repo.line): + repo.set_enabled('True') + self._repos.save() + return True + except: + raise OperationFailed("KCHREPOS0007E", {'repo_id': repo_id}) + + def disableRepo(self, repo_id): + """ + Disable a given repository + """ + try: + lrepo = self._genSourceLine(repo_id) + self._repos.refresh() + for repo in self._repos: + if (not repo.disabled) and (lrepo == repo.line): + repo.set_enabled('False') + self._repos.save() + return True + except: + raise OperationFailed("KCHREPOS0008E", {'repo_id': repo_id}) + + def updateRepo(self, repo_id, repo={}): + """ + Update a given repository in repositories.Repositories() format + """ + if len(repo) == 0: + raise MissingParameter("KCHREPOS0013E") + + self.removeRepo(repo_id) + self.addRepo(repo) + + def removeRepo(self, repo_id): + """ + Remove a given repository + """ + try: + lrepo = self._genSourceLine(repo_id) + self._repos.refresh() + for repo in self._repos: + if lrepo == repo.line: + self._repos.remove(lrepo) + self._repos.save() + except: + raise OperationFailed("KCHREPOS0018E", {'repo_id': repo_id}) + + def _genSourceLine(self, repo_id): + """ + Generate a source.list line from repo_id information. + """ + line = '' + repo = self.getRepo(repo_id) + if repo['enabled']: + line = 'deb ' + else: + line = '#deb ' + line = line + repo['baseurl'] + ' ' + for i in range(0, len(repo['url_args'])): + line = line + repo['url_args'][i] + ' ' + line = line + '\n' + return line -- 1.8.3.1

Update Makefile files to provide repositories support. Signed-off-by: Paulo Vital <pvital@linux.vnet.ibm.com> --- Makefile.am | 1 + src/kimchi/Makefile.am | 1 + 2 files changed, 2 insertions(+) diff --git a/Makefile.am b/Makefile.am index f89dcf2..378036e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -54,6 +54,7 @@ PEP8_WHITELIST = \ src/kimchi/iscsi.py \ src/kimchi/isoinfo.py \ src/kimchi/model/*.py \ + src/kimchi/repositories.py \ src/kimchi/rollbackcontext.py \ src/kimchi/root.py \ src/kimchi/server.py \ diff --git a/src/kimchi/Makefile.am b/src/kimchi/Makefile.am index c8823fa..6adef3c 100644 --- a/src/kimchi/Makefile.am +++ b/src/kimchi/Makefile.am @@ -41,6 +41,7 @@ kimchi_PYTHON = \ networkxml.py \ objectstore.py \ osinfo.py \ + repositories.py \ rollbackcontext.py \ root.py \ scan.py \ -- 1.8.3.1

Added unit tests into test_model.py and test_rest.py Signed-off-by: Paulo Vital <pvital@linux.vnet.ibm.com> --- tests/test_model.py | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/test_rest.py | 33 +++++++++++++++++ 2 files changed, 133 insertions(+) diff --git a/tests/test_model.py b/tests/test_model.py index bf6f27a..f315541 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -855,6 +855,106 @@ class ModelTests(unittest.TestCase): volumes = inst.storagevolumes_get_list(args['name']) self.assertEquals(len(volumes), 2) + def test_repository_create(self): + inst = model.Model('test:///default', + objstore_loc=self.tmp_store) + + system_host_repos = len(inst.repositories_get_list()) + + test_repos = [{'repo_id': 'fedora-fake', + 'baseurl': 'http://www.fedora.org'}, + {'repo_id': 'fedora-updates-fake', + 'baseurl': 'http://www.fedora.org/updates', + 'is_mirror': True, + 'gpgkey': 'file:///tmp/KEY-fedora-updates-fake-19'}] + + for repo in test_repos: + inst.repositories_create(repo) + host_repos = inst.repositories_get_list() + self.assertEquals(system_host_repos+len(test_repos), len(host_repos)) + + for repo in test_repos: + repo_info = inst.repository_lookup(repo.get('repo_id')) + self.assertEquals(repo.get('repo_id'), repo_info.get('repo_id')) + self.assertEquals(repo.get('baseurl', []), + repo_info.get('baseurl')) + self.assertEquals(repo.get('is_mirror', False), + repo_info.get('is_mirror')) + self.assertEquals(True, repo_info.get('enabled')) + + if 'gpgkey' in repo.keys(): + gpgcheck = True + else: + gpgcheck = False + + self.assertEquals(gpgcheck, repo_info.get('gpgcheck')) + + self.assertRaises(NotFoundError, inst.repository_lookup, 'google') + + # remove files created + for repo in test_repos: + inst.repository_delete(repo['repo_id']) + self.assertRaises(NotFoundError, + inst.repository_lookup, repo['repo_id']) + + def test_repository_update(self): + inst = model.Model('test:///default', + objstore_loc=self.tmp_store) + + system_host_repos = len(inst.repositories_get_list()) + + repo = {'repo_id': 'fedora-fake', + 'repo_name': 'Fedora 19 FAKE', + 'baseurl': 'http://www.fedora.org'} + inst.repositories_create(repo) + + host_repos = inst.repositories_get_list() + self.assertEquals(system_host_repos+1, len(host_repos)) + + new_repo = {'repo_id': 'fedora-fake', + 'repo_name': 'Fedora 19 Update FAKE', + 'baseurl': 'http://www.fedora.org/update'} + + inst.repository_update(repo['repo_id'], new_repo) + repo_info = inst.repository_lookup(new_repo.get('repo_id')) + self.assertEquals(new_repo.get('repo_id'), repo_info.get('repo_id')) + self.assertEquals(new_repo.get('repo_name'), + repo_info.get('repo_name')) + self.assertEquals(new_repo.get('baseurl', None), + repo_info.get('baseurl')) + self.assertEquals(True, repo_info.get('enabled')) + + # remove files creates + inst.repository_delete(repo['repo_id']) + + def test_repository_disable_enable(self): + inst = model.Model('test:///default', + objstore_loc=self.tmp_store) + + system_host_repos = len(inst.repositories_get_list()) + + repo = {'repo_id': 'fedora-fake', + 'repo_name': 'Fedora 19 FAKE', + 'baseurl': 'http://www.fedora.org'} + inst.repositories_create(repo) + + host_repos = inst.repositories_get_list() + self.assertEquals(system_host_repos+1, len(host_repos)) + + repo_info = inst.repository_lookup(repo.get('repo_id')) + self.assertEquals(True, repo_info.get('enabled')) + + inst.repository_disable(repo.get('repo_id')) + repo_info = inst.repository_lookup(repo.get('repo_id')) + self.assertEquals(False, repo_info.get('enabled')) + + inst.repository_enable(repo.get('repo_id')) + repo_info = inst.repository_lookup(repo.get('repo_id')) + self.assertEquals(True, repo_info.get('enabled')) + + # remove files creates + inst.repository_delete(repo['repo_id']) + class BaseModelTests(unittest.TestCase): class FoosModel(object): diff --git a/tests/test_rest.py b/tests/test_rest.py index 0e6a253..5a86a80 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -1489,6 +1489,39 @@ class RestTests(unittest.TestCase): self.assertEquals(1, len(res)) self.assertEquals('test-vm1', res[0]['name']) + def test_repositories(self): + def verify_repo(t, res): + for field in ('repo_id', 'repo_name', 'baseurl', 'is_mirror', + 'url_args', 'enabled', 'gpgcheck', 'gpgkey'): + self.assertEquals(t[field], res[field]) + + base_uri = '/host/repositories' + resp = self.request(base_uri) + self.assertEquals(200, resp.status) + self.assertEquals(0, len(json.loads(resp.read()))) + + # Create a repository + repo = {'repo_id': 'fedora-fake', + 'repo_name': 'Fedora 19 FAKE', + 'baseurl': 'http://www.fedora.org'} + req = json.dumps(repo) + resp = self.request(base_uri, req, 'POST') + self.assertEquals(201, resp.status) + + # Verify the repositorie + res = json.loads(self.request('%s/fedora-fake' % base_uri).read()) + verify_template(repo, res) + + # Update the repository + repo['baseurl'] = 'http://www.fedora.org/update' + req = json.dumps(repo) + resp = self.request('%s/%s' % (base_uri, repo['repo_id']), req, 'PUT') + self.assertEquals(200, resp.status) + + # Verify the template + res = json.loads(self.request('%s/fedora-fake' % base_uri).read()) + verify_template(repo, res) + class HttpsRestTests(RestTests): """ -- 1.8.3.1

Added new error exception messages for host's repositories support. Signed-off-by: Paulo Vital <pvital@linux.vnet.ibm.com> --- src/kimchi/i18n.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index fd61d4e..ba6993f 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -202,4 +202,23 @@ messages = { "KCHCDROM0009E": _("Error while updating storage device: %(error)s"), "KCHCDROM0010E": _("Error while removing storage device: %(error)s"), "KCHCDROM0011E": _("Do not support guest CDROM hot plug attachment"), + + "KCHREPOS0001E": _("Repository ID must be one word only string."), + "KCHREPOS0002E": _("Repository URL must be an http://, ftp:// or file:// URL."), + "KCHREPOS0003E": _("Repository URL arguments must be string."), + "KCHREPOS0004E": _("GPG key must be a URL pointing to the ASCII-armored file."), + "KCHREPOS0005E": _("Repository name must be string."), + "KCHREPOS0006E": _("Repository %(repo_id)s already exists."), + "KCHREPOS0007E": _("Could not enable repository %(repo_id)s."), + "KCHREPOS0008E": _("Could not disable repository %(repo_id)s."), + "KCHREPOS0009E": _("Could not update repository %(repo_id)s."), + "KCHREPOS0010E": _("Repository %(repo_id)s does not exists."), + "KCHREPOS0011E": _("There is no disabled repository called %(repo_id)s."), + "KCHREPOS0012E": _("There is no enabled repository called %(repo_id)s."), + "KCHREPOS0013E": _("There are no parameters to update repository."), + "KCHREPOS0014E": _("OS distro not supported."), + "KCHREPOS0015E": _("There is no YUM configuration directory."), + "KCHREPOS0016E": _("There are no parameters to create a new repo file."), + "KCHREPOS0017E": _("Could not write repo file %(repo_file)s"), + "KCHREPOS0018E": _("Could not remove repository %(repo_id)s."), } -- 1.8.3.1

I got the following errors while running tests: ====================================================================== ERROR: test_get_hostinfo (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 800, in test_get_hostinfo objstore_loc=self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_get_hoststats (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 811, in test_get_hoststats objstore_loc=self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_get_interfaces (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 625, in test_get_interfaces objstore_loc=self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_multithreaded_connection (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 559, in test_multithreaded_connection inst = model.Model('test:///default', self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_network (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 521, in test_network inst = model.Model('qemu:///system', self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_object_store (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 593, in test_object_store 'α', 'β') File "/usr/lib/python2.7/unittest/case.py", line 476, in assertRaises callableObj(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/objectstore.py", line 57, in get raise NotFoundError("KCHOBJST0001E", {'item': ident}) File "/home/alinefm/kimchi/src/kimchi/exception.py", line 33, in __init__ paths = cherrypy.request.app.root.paths AttributeError: 'NoneType' object has no attribute 'root' ====================================================================== ERROR: test_repository_create (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 860, in test_repository_create objstore_loc=self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_repository_disable_enable (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 932, in test_repository_disable_enable objstore_loc=self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_repository_update (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 902, in test_repository_update objstore_loc=self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_storagepool (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 246, in test_storagepool inst = model.Model('qemu:///system', self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_storagevolume (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 310, in test_storagevolume inst = model.Model('qemu:///system', self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_template_create (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 405, in test_template_create objstore_loc=self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_template_storage_customise (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 366, in test_template_storage_customise inst = model.Model(objstore_loc=self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_template_update (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 446, in test_template_update objstore_loc=self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_use_test_host (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 718, in test_use_test_host objstore_loc=self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_vm_cdrom (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 167, in test_vm_cdrom inst = model.Model(objstore_loc=self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_vm_edit (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 485, in test_vm_edit objstore_loc=self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_vm_graphics (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 100, in test_vm_graphics inst = model.Model(objstore_loc=self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_vm_ifaces (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 126, in test_vm_ifaces inst = model.Model(objstore_loc=self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_vm_info (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 54, in test_vm_info inst = model.Model('test:///default', self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_vm_lifecycle (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 75, in test_vm_lifecycle inst = model.Model(objstore_loc=self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_vm_list_sorted (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 701, in test_vm_list_sorted inst = model.Model(objstore_loc=self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_vm_storage_provisioning (test_model.ModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_model.py", line 228, in test_vm_storage_provisioning inst = model.Model(objstore_loc=self.tmp_store) File "/home/alinefm/kimchi/src/kimchi/model/model.py", line 60, in __init__ models.append(instance(**kargs)) File "/home/alinefm/kimchi/src/kimchi/model/host.py", line 307, in __init__ self.host_repositories = Repositories() File "/home/alinefm/kimchi/src/kimchi/basemodel.py", line 54, in __call__ cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 64, in __init__ self._scanSystemRepositories() File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 72, in _scanSystemRepositories self.addRepository(repo) File "/home/alinefm/kimchi/src/kimchi/repositories.py", line 79, in addRepository repo = {'repo_id': repo_id, NameError: global name 'repo_id' is not defined ====================================================================== ERROR: test_server_start (test_server.ServerTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_server.py", line 40, in test_server_start model = kimchi.mockmodel.MockModel('/tmp/obj-store-test') File "/home/alinefm/kimchi/src/kimchi/mockmodel.py", line 62, in __init__ self.reset() File "/home/alinefm/kimchi/src/kimchi/mockmodel.py", line 82, in reset self.host_repositories = MockRepositories() File "/home/alinefm/kimchi/src/kimchi/mockmodel.py", line 1067, in __init__ "repo_name": "kimchi_repo_1392167832"}} TypeError: unhashable type: 'dict' ====================================================================== ERROR: setUpModule (test_plugin) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/alinefm/kimchi/tests/test_plugin.py", line 45, in setUpModule model = kimchi.mockmodel.MockModel('/tmp/obj-store-test') File "/home/alinefm/kimchi/src/kimchi/mockmodel.py", line 62, in __init__ self.reset() File "/home/alinefm/kimchi/src/kimchi/mockmodel.py", line 82, in reset self.host_repositories = MockRepositories() File "/home/alinefm/kimchi/src/kimchi/mockmodel.py", line 1067, in __init__ "repo_name": "kimchi_repo_1392167832"}} TypeError: unhashable type: 'dict' ---------------------------------------------------------------------- Ran 64 tests in 15.956s FAILED (errors=41) make[3]: *** [check-local] Error 1 make[3]: Leaving directory `/home/alinefm/kimchi/tests' make[2]: *** [check-am] Error 2 make[2]: Leaving directory `/home/alinefm/kimchi/tests' make[1]: *** [check] Error 2 make[1]: Leaving directory `/home/alinefm/kimchi/tests' make: *** [check-recursive] Error 1 docs/API.md:845: trailing whitespace. + * baseurl: URL to the repodata directory when "is_mirror" is false. docs/API.md:866: trailing whitespace. + * baseurl: URL to the repodata directory when "is_mirror" is false. docs/API.md:881: trailing whitespace. + * gpgkey: URL pointing to the ASCII-armored GPG key file for the src/kimchi/mockmodel.py:828: trailing whitespace. + except: On 02/15/2014 07:49 PM, Paulo Vital wrote:
V2 -> V3: * PEP8 compatibility * Improved mockmodel.py and model/host.py * Updated API.json and backend files to use new exception error system code
V1 -> V2: * Removed the enable/disable from docs/API.md and src/kimchi/API.json * fixed wrong URI on test_rest.py * PEP8 compatibility * Updated repositories.py to make consistent with docs/API.md * Raised correct exceptions * Changed model/host.py and mockmodel.py to generate repo_id if not provided * Changed mockmodel.py to declare MockRepositories class
V1:
This patch set provides support to host's repositories management operations. At this point, an agnostic class is providing support to backend and REST API operations. In addition, YUM (for RHEL, Fedora, SLES and OpenSuse) and APT (for Debian and Ubuntu) specific classes are provided to support the operation os each software update system.
To test the backend execute the following commands: $ cd tests $ sudo ./run_tests.sh test_model.ModelTests.test_repository_create $ sudo ./run_tests.sh test_model.ModelTests.test_repository_update $ sudo ./run_tests.sh test_model.ModelTests.test_repository_disable_enable
To test the REST API, execute the following commands (all them are agnostic of the host's distro):
1) Get list of all repositories enabled in the host: $ curl -u <USER> -H 'Content-type: application/json' -H 'Accept: application/json' http://localhost:8000/host/repositories/ -X GET
2) Create a new repository: $ curl -u <USER> -H 'Content-type: application/json' -H 'Accept: application/json' http://localhost:8000/host/repositories/ -X POST -d' { "repo_id": "fedora-fake", "baseurl": "http://www.fedora.org", "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-fake-19" } '
3) Get information from a specific repository: $ curl -u <USER> -H 'Content-type: application/json' -H 'Accept: application/json' http://localhost:8000/host/repositories/fedora-fake
4) Update a specific repository: $ curl -u <USER> -H 'Content-type: application/json' -H 'Accept: application/json' http://localhost:8000/host/repositories/fedora-fake -X PUT -d' { "repo_id":"fedora-fake", "repo_name":"Fedora 19 FAKEs", "baseurl": "http://www.fedora.org/downloads" } '
5) Disable a specific repository: $ curl -u <USER> -H 'Content-type: application/json' -H 'Accept: application/json' http://localhost:8000/host/repositories/fedora-fake/disable -X POST -d ''
6) Enable a specific repository: $ curl -u <USER> -H 'Content-type: application/json' -H 'Accept: application/json' http://localhost:8000/host/repositories/fedora-fake/enable -X POST -d ''
7) Delete a specific repository: $ curl -u <USER> -H 'Content-type: application/json' -H 'Accept: application/json' http://localhost:8000/host/repositories/fedora-fake -X DELETE
Paulo Vital (6): Host's repositories management: Update API.md Host's repositories management: Update REST API Host's repositories management: Update backend. Host's repositories management: Update Makefile Host's repositories management: Update test-cases. Host's repositories management: Update error exception messages.
Makefile.am | 1 + docs/API.md | 77 ++++++ src/kimchi/API.json | 76 ++++++ src/kimchi/Makefile.am | 1 + src/kimchi/control/host.py | 21 ++ src/kimchi/i18n.py | 19 ++ src/kimchi/mockmodel.py | 133 +++++++++++ src/kimchi/model/host.py | 54 ++++- src/kimchi/repositories.py | 585 +++++++++++++++++++++++++++++++++++++++++++++ tests/test_model.py | 100 ++++++++ tests/test_rest.py | 33 +++ 11 files changed, 1099 insertions(+), 1 deletion(-) create mode 100644 src/kimchi/repositories.py
participants (2)
-
Aline Manera
-
Paulo Vital