aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMagnus Granberg <zorry@gentoo.org>2020-04-05 00:34:22 +0200
committerMagnus Granberg <zorry@gentoo.org>2020-04-05 00:44:59 +0200
commitbe80f7418991180567f0882902ca4151a635e42d (patch)
treec0ddd3545ed6c9c3c0fb6241ba43ee3a7755f252
parentuse perform_checksum for checksum on files (diff)
downloadtinderbox-cluster-be80f7418991180567f0882902ca4151a635e42d.tar.gz
tinderbox-cluster-be80f7418991180567f0882902ca4151a635e42d.tar.bz2
tinderbox-cluster-be80f7418991180567f0882902ca4151a635e42d.zip
Redone the code to use OpenStack's libs
We use Openstack's libs and Nova code as a base for the POC Most of the code to populate the db is done. Signed-off-by: Magnus Granberg <zorry@gentoo.org>
-rw-r--r--README.txt17
-rwxr-xr-xbin/tbc_guest_jobs55
-rwxr-xr-xbin/tbc_host_jobs34
-rw-r--r--conf/tbc.conf15
-rw-r--r--ebuild/tbc-9999.ebuild52
-rw-r--r--etc/gosbs.conf2583
-rw-r--r--gosbs/__init__.py35
-rw-r--r--gosbs/baserpc.py81
-rw-r--r--gosbs/cmd/__init__.py20
-rw-r--r--gosbs/cmd/scheduler.py45
-rw-r--r--gosbs/common/__init__.py (renamed from pym/tbc/__init__.py)0
-rw-r--r--gosbs/common/flags.py211
-rw-r--r--gosbs/common/git.py99
-rw-r--r--gosbs/common/portage_settings.py48
-rw-r--r--gosbs/common/task.py70
-rw-r--r--gosbs/conf/__init__.py47
-rw-r--r--gosbs/conf/base.py43
-rw-r--r--gosbs/conf/database.py183
-rw-r--r--gosbs/conf/keystone.py72
-rw-r--r--gosbs/conf/netconf.py94
-rw-r--r--gosbs/conf/notifications.py118
-rw-r--r--gosbs/conf/opts.py79
-rw-r--r--gosbs/conf/paths.py106
-rw-r--r--gosbs/conf/rpc.py46
-rw-r--r--gosbs/conf/scheduler.py37
-rw-r--r--gosbs/conf/service.py169
-rw-r--r--gosbs/conf/upgrade_levels.py210
-rw-r--r--gosbs/conf/utils.py91
-rw-r--r--gosbs/config.py50
-rw-r--r--gosbs/context.py562
-rw-r--r--gosbs/db/__init__.py13
-rw-r--r--gosbs/db/api.py1891
-rw-r--r--gosbs/db/base.py29
-rw-r--r--gosbs/db/constants.py25
-rw-r--r--gosbs/db/sqlalchemy/__init__.py0
-rw-r--r--gosbs/db/sqlalchemy/api.py5897
-rw-r--r--gosbs/db/sqlalchemy/models.py430
-rw-r--r--gosbs/db/sqlalchemy/types.py74
-rw-r--r--gosbs/db/sqlalchemy/utils.py118
-rw-r--r--gosbs/debugger.py62
-rw-r--r--gosbs/exception.py2394
-rw-r--r--gosbs/i18n.py48
-rw-r--r--gosbs/manager.py149
-rw-r--r--gosbs/middleware.py39
-rw-r--r--gosbs/objects/__init__.py52
-rw-r--r--gosbs/objects/base.py361
-rw-r--r--gosbs/objects/build_iuse.py280
-rw-r--r--gosbs/objects/category.py278
-rw-r--r--gosbs/objects/category_metadata.py278
-rw-r--r--gosbs/objects/ebuild.py288
-rw-r--r--gosbs/objects/ebuild_iuse.py280
-rw-r--r--gosbs/objects/ebuild_keyword.py280
-rw-r--r--gosbs/objects/ebuild_metadata.py282
-rw-r--r--gosbs/objects/ebuild_restriction.py281
-rw-r--r--gosbs/objects/email.py269
-rw-r--r--gosbs/objects/fields.py70
-rw-r--r--gosbs/objects/flavor.py228
-rw-r--r--gosbs/objects/image.py204
-rw-r--r--gosbs/objects/keyword.py277
-rw-r--r--gosbs/objects/package.py300
-rw-r--r--gosbs/objects/package_email.py301
-rw-r--r--gosbs/objects/package_metadata.py279
-rw-r--r--gosbs/objects/project.py279
-rw-r--r--gosbs/objects/project_build.py286
-rw-r--r--gosbs/objects/project_metadata.py308
-rw-r--r--gosbs/objects/project_repo.py295
-rw-r--r--gosbs/objects/repo.py290
-rw-r--r--gosbs/objects/restriction.py281
-rw-r--r--gosbs/objects/service.py486
-rw-r--r--gosbs/objects/service_repo.py296
-rw-r--r--gosbs/objects/task.py291
-rw-r--r--gosbs/objects/use.py278
-rw-r--r--gosbs/objects/user.py278
-rw-r--r--gosbs/policies/__init__.py26
-rw-r--r--gosbs/policies/base.py41
-rw-r--r--gosbs/policies/hosts.py63
-rw-r--r--gosbs/policies/services.py69
-rw-r--r--gosbs/policy.py246
-rw-r--r--gosbs/profiler.py51
-rw-r--r--gosbs/rpc.py448
-rw-r--r--gosbs/safe_utils.py41
-rw-r--r--gosbs/scheduler/__init__.py0
-rw-r--r--gosbs/scheduler/category.py113
-rw-r--r--gosbs/scheduler/ebuild.py203
-rw-r--r--gosbs/scheduler/email.py36
-rw-r--r--gosbs/scheduler/manager.py141
-rw-r--r--gosbs/scheduler/package.py180
-rw-r--r--gosbs/scheduler/project.py35
-rw-r--r--gosbs/scheduler/rpcapi.py127
-rw-r--r--gosbs/service.py331
-rw-r--r--gosbs/tasks/__init__.py0
-rw-r--r--gosbs/tasks/scheduler/__init__.py18
-rw-r--r--gosbs/tasks/scheduler/rebuild_db.py52
-rw-r--r--gosbs/tasks/scheduler/sub/__init__.py0
-rw-r--r--gosbs/tasks/scheduler/sub/build.py73
-rw-r--r--gosbs/tasks/scheduler/update_db.py60
-rw-r--r--gosbs/tasks/scheduler/update_git.py103
-rw-r--r--gosbs/tasks/scheduler/update_repos.py23
-rw-r--r--gosbs/utils.py1358
-rw-r--r--gosbs/version.py90
-rw-r--r--licenses/Apache-2.0201
-rw-r--r--licenses/GPL-2339
-rw-r--r--patches/portage.patch292
-rw-r--r--patches/repoman.patch42
-rw-r--r--pym/tbc/ConnectionManager.py22
-rw-r--r--pym/tbc/build_depgraph.py65
-rw-r--r--pym/tbc/build_job.py202
-rw-r--r--pym/tbc/build_log.py383
-rw-r--r--pym/tbc/buildquerydb.py102
-rw-r--r--pym/tbc/check_setup.py74
-rw-r--r--pym/tbc/db_mapping.py306
-rw-r--r--pym/tbc/depclean.py53
-rw-r--r--pym/tbc/flags.py222
-rw-r--r--pym/tbc/irk.py28
-rw-r--r--pym/tbc/jobs.py80
-rw-r--r--pym/tbc/log.py38
-rw-r--r--pym/tbc/old_cpv.py43
-rw-r--r--pym/tbc/package.py409
-rw-r--r--pym/tbc/qachecks.py100
-rw-r--r--pym/tbc/readconf.py45
-rw-r--r--pym/tbc/sqlquerys.py665
-rw-r--r--pym/tbc/sync.py122
-rw-r--r--pym/tbc/text.py49
-rw-r--r--pym/tbc/updatedb.py173
-rw-r--r--requirements.txt18
-rw-r--r--setup.cfg35
-rw-r--r--setup.py48
-rw-r--r--sql/data_dump.sql193
-rw-r--r--sql/structure_dump.sql1538
129 files changed, 29264 insertions, 4948 deletions
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..da77900
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,17 @@
+=============================
+Gentoo OpenStack Build System
+=============================
+
+This is a POC
+We are useing OpenStack's libs and nova as code base.
+https://github.com/openstack/nova
+For the building and populate the db we use portage's api.
+https://gitweb.gentoo.org/proj/portage.git
+Thanks to the community of thos team's.
+
+The code for now need alot of work.
+We need to cleanout not needed Nova code.
+Most of the code to populate the db is done.
+The code for the build worker neeed to be done.
+
+https://wiki.gentoo.org/wiki/Project:Tinderbox-cluster
diff --git a/bin/tbc_guest_jobs b/bin/tbc_guest_jobs
deleted file mode 100755
index 5614f07..0000000
--- a/bin/tbc_guest_jobs
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/python3.4
-#
-# Copyright 1998-2016 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-from __future__ import print_function
-from tbc.readconf import read_config_settings
-from tbc.ConnectionManager import NewConnection
-from tbc.sqlquerys import get_config_id_fqdn, check_host_updatedb, update_deamon_status
-from tbc.check_setup import check_configure_guest
-from tbc.build_job import build_job_action
-from tbc.jobs import jobs_main
-from tbc.log import setup_logger, write_log
-from sqlalchemy.orm import sessionmaker
-import portage
-import sys
-import os
-import time
-
-def main():
- repeat = True
- tbc_settings = read_config_settings()
-
- # setup the logger
- logger = setup_logger(tbc_settings)
-
- Session = sessionmaker(bind=NewConnection(tbc_settings))
- session = Session()
- config_id = get_config_id_fqdn(session, tbc_settings['hostname'])
- write_log(session, "Job and build deamon started.", "info", config_id, 'main')
- Status = 'Waiting'
- update_deamon_status(session, Status, config_id)
- msg = 'Status: %s Host: %s' % (Status, tbc_settings['hostname'],)
- write_log(session, msg, "info", config_id, 'main')
- init_build_job = build_job_action(config_id, session)
- while repeat:
- jobs_main(session, config_id)
- if not check_configure_guest(session, config_id) or check_host_updatedb(session):
- time.sleep(60)
- continue
- else:
- Status = 'Runing'
- update_deamon_status(session, Status, config_id)
- msg = 'Status: %s Host: %s' % (Status, tbc_settings['hostname'],)
- write_log(session, msg, "info", config_id, 'main')
- init_build_job.procces_build_jobs()
- Status = 'Waiting'
- update_deamon_status(session, Status, config_id)
- msg = 'Status: %s Host: %s' % (Status, tbc_settings['hostname'],)
- write_log(session, msg, "info", config_id, 'main')
- time.sleep(60)
- conn.close
-
-if __name__ == "__main__":
- main()
diff --git a/bin/tbc_host_jobs b/bin/tbc_host_jobs
deleted file mode 100755
index 50dfe76..0000000
--- a/bin/tbc_host_jobs
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/python3.4
-#
-# Copyright 1998-2016 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-from __future__ import print_function
-
-from tbc.readconf import read_config_settings
-from tbc.jobs import jobs_main
-from tbc.ConnectionManager import NewConnection
-from tbc.sqlquerys import get_config_id_fqdn
-from tbc.log import setup_logger, write_log
-from sqlalchemy.orm import sessionmaker
-import time
-
-def main():
- # Main
- tbc_settings = read_config_settings()
- # setup the logger
- logger = setup_logger(tbc_settings)
-
- Session = sessionmaker(bind=NewConnection(tbc_settings))
- session = Session()
- config_id = get_config_id_fqdn(session, tbc_settings['hostname'])
- write_log(session, "Job deamon started", "info", config_id, 'main')
- repeat = True
- while repeat:
- jobs_main(session, config_id)
- repeat = False
- time.sleep(60)
- write_log(session, "Job deamon stoped", "info", config_id, 'main')
-
-if __name__ == "__main__":
- main() \ No newline at end of file
diff --git a/conf/tbc.conf b/conf/tbc.conf
deleted file mode 100644
index d1254f3..0000000
--- a/conf/tbc.conf
+++ /dev/null
@@ -1,15 +0,0 @@
-# The Sql Backend
-SQLBACKEND=mysql
-# The Sql Datebase
-SQLDB=tbc
-# Sql Host
-SQLHOST=localhost
-# Sql user
-SQLUSER=buildhost
-# Sql Password
-SQLPASSWD=buildhost
-# loging level
-#LOG=INFO
-# logfile
-#LOGFILE=/var/log/tbc.log
-
diff --git a/ebuild/tbc-9999.ebuild b/ebuild/tbc-9999.ebuild
deleted file mode 100644
index e705155..0000000
--- a/ebuild/tbc-9999.ebuild
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright 1999-2016 Gentoo Foundation
-# Distributed under theterms of the GNU General Public License v2
-# $Id$
-
-EAPI="5"
-PYTHON_COMPAT=( python{2_7,3_4,3_5})
-
-inherit distutils-r1 git-2
-
-DESCRIPTION="TBC"
-HOMEPAGE="https://wiki.gentoo.org/wiki/Project:Tinderbox-cluster"
-SRC_URI=""
-LICENSE="GPL-2"
-KEYWORDS="~amd64"
-SLOT="0"
-IUSE="+mysql"
-
-RDEPEND=">=sys-apps/portage-2.3.2[${PYTHON_USEDEP}]
- >=app-portage/repoman-2.3.0-r2[${PYTHON_USEDEP}]
- dev-python/sqlalchemy[${PYTHON_USEDEP}]
- dev-python/git-python[${PYTHON_USEDEP}]
- mysql? ( dev-python/mysql-connector-python[${PYTHON_USEDEP}] )"
-
-DEPEND="${RDEPEND}
- dev-python/setuptools[${PYTHON_USEDEP}]"
-
-EGIT_REPO_URI="https://anongit.gentoo.org/git/proj/tinderbox-cluster.git"
-PATCHES=(
- "${S}"/patches/portage.patch
- "${S}"/patches/repoman.patch
-)
-
-python_prepare_all() {
- python_setup
- einfo "Copying needed files from portage"
- cp $(python_get_sitedir)/_emerge/actions.py ${S}/pym/tbc
- cp $(python_get_sitedir)/_emerge/main.py ${S}/pym/tbc
- cp $(python_get_sitedir)/_emerge/Scheduler.py ${S}/pym/tbc
- cp $(python_get_sitedir)/repoman/main.py ${S}/pym/tbc/repoman.py
- einfo "Done."
- distutils-r1_python_prepare_all
-}
-
-src_install() {
- dodir etc/tbc
- insinto /etc/tbc
- doins ${S}/conf/tbc.conf
- dosbin ${S}/bin/tbc_host_jobs
- dosbin ${S}/bin/tbc_guest_jobs
-
- distutils-r1_src_install
-}
diff --git a/etc/gosbs.conf b/etc/gosbs.conf
new file mode 100644
index 0000000..0d53587
--- /dev/null
+++ b/etc/gosbs.conf
@@ -0,0 +1,2583 @@
+[DEFAULT]
+
+#
+# From gosbs.conf
+#
+
+#
+# The IP address which the host is using to connect to the management network.
+#
+# Possible values:
+#
+# * String with valid IP address. Default is IPv4 address of this host.
+#
+# Related options:
+#
+# * metadata_host
+# * my_block_storage_ip
+# * routing_source_ip
+# * vpn_ip
+# (string value)
+my_ip = 192.168.1.2
+
+#
+# Hostname, FQDN or IP address of this host.
+#
+# Used as:
+#
+# * the oslo.messaging queue name for nova-compute worker
+# * we use this value for the binding_host sent to neutron. This means if you
+# use
+# a neutron agent, it should have the same value for host.
+# * cinder host attachment information
+#
+# Must be valid within AMQP key.
+#
+# Possible values:
+#
+# * String with hostname, FQDN or IP address. Default is hostname of this host.
+# (string value)
+#host = <current_hostname>
+
+#
+# The directory where the Nova python modules are installed.
+#
+# This directory is used to store template files for networking and remote
+# console access. It is also the default path for other config options which
+# need to persist Nova internal data. It is very unlikely that you need to
+# change this option from its default value.
+#
+# Possible values:
+#
+# * The full path to a directory.
+#
+# Related options:
+#
+# * ``state_path``
+# (string value)
+#pybasedir = <Path>
+
+#
+# The directory where the Nova binaries are installed.
+#
+# This option is only relevant if the networking capabilities from Nova are
+# used (see services below). Nova's networking capabilities are targeted to
+# be fully replaced by Neutron in the future. It is very unlikely that you need
+# to change this option from its default value.
+#
+# Possible values:
+#
+# * The full path to a directory.
+# (string value)
+#bindir = /home/mthode/dev/openstack/openstack/nova/.tox/shared/local/bin
+
+#
+# The top-level directory for maintaining Nova's state.
+#
+# This directory is used to store Nova's internal state. It is used by a
+# variety of other config options which derive from this. In some scenarios
+# (for example migrations) it makes sense to use a storage location which is
+# shared between multiple compute hosts (for example via NFS). Unless the
+# option ``instances_path`` gets overwritten, this directory can grow very
+# large.
+#
+# Possible values:
+#
+# * The full path to a directory. Defaults to value provided in ``pybasedir``.
+# (string value)
+#state_path = $pybasedir
+
+repopath = /home/repos
+
+#
+# This option allows setting an alternate timeout value for RPC calls
+# that have the potential to take a long time. If set, RPC calls to
+# other services will use this value for the timeout (in seconds)
+# instead of the global rpc_response_timeout value.
+#
+# Operations with RPC calls that utilize this value:
+#
+# * live migration
+#
+# Related options:
+#
+# * rpc_response_timeout
+# (integer value)
+#long_rpc_timeout = 1800
+
+#
+# Maximum time in seconds since last check-in for up service
+#
+# Each compute node periodically updates their database status based on the
+# specified report interval. If the compute node hasn't updated the status
+# for more than service_down_time, then the compute node is considered down.
+#
+# Related Options:
+#
+# * report_interval (service_down_time should not be less than report_interval)
+# * scheduler.periodic_task_interval
+# (integer value)
+#service_down_time = 60
+
+#
+# Enable periodic tasks.
+#
+# If set to true, this option allows services to periodically run tasks
+# on the manager.
+#
+# In case of running multiple schedulers or conductors you may want to run
+# periodic tasks on only one host - in this case disable this option for all
+# hosts but one.
+# (boolean value)
+#periodic_enable = true
+
+#
+# Number of seconds to randomly delay when starting the periodic task
+# scheduler to reduce stampeding.
+#
+# When compute workers are restarted in unison across a cluster,
+# they all end up running the periodic tasks at the same time
+# causing problems for the external services. To mitigate this
+# behavior, periodic_fuzzy_delay option allows you to introduce a
+# random initial delay when starting the periodic task scheduler.
+#
+# Possible Values:
+#
+# * Any positive integer (in seconds)
+# * 0 : disable the random delay
+# (integer value)
+# Minimum value: 0
+#periodic_fuzzy_delay = 60
+
+#
+# Number of workers for OpenStack API service. The default will be the number
+# of CPUs available.
+#
+# OpenStack API services can be configured to run as multi-process (workers).
+# This overcomes the problem of reduction in throughput when API request
+# concurrency increases. OpenStack API service will run in the specified
+# number of processes.
+#
+# Possible Values:
+#
+# * Any positive integer
+# * None (default value)
+# (integer value)
+# Minimum value: 1
+#osapi_compute_workers = <None>
+
+#
+# Number of workers for metadata service. If not specified the number of
+# available CPUs will be used.
+#
+# The metadata service can be configured to run as multi-process (workers).
+# This overcomes the problem of reduction in throughput when API request
+# concurrency increases. The metadata service will run in the specified
+# number of processes.
+#
+# Possible Values:
+#
+# * Any positive integer
+# * None (default value)
+# (integer value)
+# Minimum value: 1
+#metadata_workers = <None>
+
+#
+# This option specifies the driver to be used for the servicegroup service.
+#
+# ServiceGroup API in nova enables checking status of a compute node. When a
+# compute worker running the nova-compute daemon starts, it calls the join API
+# to join the compute group. Services like nova scheduler can query the
+# ServiceGroup API to check if a node is alive. Internally, the ServiceGroup
+# client driver automatically updates the compute worker status. There are
+# multiple backend implementations for this service: Database ServiceGroup
+# driver
+# and Memcache ServiceGroup driver.
+#
+# Possible Values:
+#
+# * db : Database ServiceGroup driver
+# * mc : Memcache ServiceGroup driver
+#
+# Related Options:
+#
+# * service_down_time (maximum time since last check-in for up service)
+# (string value)
+# Possible values:
+# db - <No description provided>
+# mc - <No description provided>
+#servicegroup_driver = db
+
+#
+# From oslo.log
+#
+
+# If set to true, the logging level will be set to DEBUG instead of the default
+# INFO level. (boolean value)
+# Note: This option can be changed without restarting.
+debug = true
+
+# The name of a logging configuration file. This file is appended to any
+# existing logging configuration files. For details about logging configuration
+# files, see the Python logging module documentation. Note that when logging
+# configuration files are used then all logging configuration is set in the
+# configuration file and other logging configuration options are ignored (for
+# example, logging_context_format_string). (string value)
+# Note: This option can be changed without restarting.
+# Deprecated group/name - [DEFAULT]/log_config
+#log_config_append = <None>
+
+# Defines the format string for %%(asctime)s in log records. Default:
+# %(default)s . This option is ignored if log_config_append is set. (string
+# value)
+#log_date_format = %Y-%m-%d %H:%M:%S
+
+# (Optional) Name of log file to send logging output to. If no default is set,
+# logging will go to stderr as defined by use_stderr. This option is ignored if
+# log_config_append is set. (string value)
+# Deprecated group/name - [DEFAULT]/logfile
+#log_file = test.log
+
+# (Optional) The base directory used for relative log_file paths. This option
+# is ignored if log_config_append is set. (string value)
+# Deprecated group/name - [DEFAULT]/logdir
+#log_dir = /var/log/nova
+
+# Uses logging handler designed to watch file system. When log file is moved or
+# removed this handler will open a new log file with specified path
+# instantaneously. It makes sense only if log_file option is specified and Linux
+# platform is used. This option is ignored if log_config_append is set. (boolean
+# value)
+#watch_log_file = false
+
+# Use syslog for logging. Existing syslog format is DEPRECATED and will be
+# changed later to honor RFC5424. This option is ignored if log_config_append is
+# set. (boolean value)
+#use_syslog = false
+
+# Enable journald for logging. If running in a systemd environment you may wish
+# to enable journal support. Doing so will use the journal native protocol which
+# includes structured metadata in addition to log messages.This option is
+# ignored if log_config_append is set. (boolean value)
+#use_journal = false
+
+# Syslog facility to receive log lines. This option is ignored if
+# log_config_append is set. (string value)
+#syslog_log_facility = LOG_USER
+
+# Use JSON formatting for logging. This option is ignored if log_config_append
+# is set. (boolean value)
+#use_json = false
+
+# Log output to standard error. This option is ignored if log_config_append is
+# set. (boolean value)
+use_stderr = true
+
+# Format string to use for log messages with context. (string value)
+#logging_context_format_string = %(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [%(request_id)s %(user_identity)s] %(instance)s%(message)s
+
+# Format string to use for log messages when context is undefined. (string
+# value)
+#logging_default_format_string = %(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [-] %(instance)s%(message)s
+
+# Additional data to append to log message when logging level for the message is
+# DEBUG. (string value)
+# logging_debug_format_suffix = %(funcName)s %(pathname)s:%(lineno)d
+
+# Prefix each line of exception output with this format. (string value)
+#logging_exception_prefix = %(asctime)s.%(msecs)03d %(process)d ERROR %(name)s %(instance)s
+
+# Defines the format string for %(user_identity)s that is used in
+# logging_context_format_string. (string value)
+#logging_user_identity_format = %(user)s %(tenant)s %(domain)s %(user_domain)s %(project_domain)s
+
+# List of package logging levels in logger=LEVEL pairs. This option is ignored
+# if log_config_append is set. (list value)
+#default_log_levels = amqp=WARN,amqplib=WARN,boto=WARN,qpid=WARN,sqlalchemy=WARN,suds=INFO,oslo.messaging=INFO,oslo_messaging=INFO,iso8601=WARN,requests.packages.urllib3.connectionpool=WARN,urllib3.connectionpool=WARN,websocket=WARN,requests.packages.urllib3.util.retry=WARN,urllib3.util.retry=WARN,keystonemiddleware=WARN,routes.middleware=WARN,stevedore=WARN,taskflow=WARN,keystoneauth=WARN,oslo.cache=INFO,dogpile.core.dogpile=INFO
+
+# Enables or disables publication of error events. (boolean value)
+#publish_errors = false
+
+# The format for an instance that is passed with the log message. (string value)
+#instance_format = "[instance: %(uuid)s] "
+
+# The format for an instance UUID that is passed with the log message. (string
+# value)
+#instance_uuid_format = "[instance: %(uuid)s] "
+
+# Interval, number of seconds, of log rate limiting. (integer value)
+#rate_limit_interval = 0
+
+# Maximum number of logged messages per rate_limit_interval. (integer value)
+#rate_limit_burst = 0
+
+# Log level name used by rate limiting: CRITICAL, ERROR, INFO, WARNING, DEBUG or
+# empty string. Logs with level greater or equal to rate_limit_except_level are
+# not filtered. An empty string means that all levels are filtered. (string
+# value)
+#rate_limit_except_level = CRITICAL
+
+# Enables or disables fatal status of deprecations. (boolean value)
+#fatal_deprecations = false
+
+#
+# From oslo.messaging
+#
+
+# Size of RPC connection pool. (integer value)
+#rpc_conn_pool_size = 30
+
+# The pool size limit for connections expiration policy (integer value)
+#conn_pool_min_size = 2
+
+# The time-to-live in sec of idle connections in the pool (integer value)
+#conn_pool_ttl = 1200
+
+# ZeroMQ bind address. Should be a wildcard (*), an ethernet interface, or IP.
+# The "host" option should point or resolve to this address. (string value)
+#rpc_zmq_bind_address = *
+
+# MatchMaker driver. (string value)
+# Possible values:
+# redis - <No description provided>
+# sentinel - <No description provided>
+# dummy - <No description provided>
+#rpc_zmq_matchmaker = redis
+
+# Number of ZeroMQ contexts, defaults to 1. (integer value)
+#rpc_zmq_contexts = 1
+
+# Maximum number of ingress messages to locally buffer per topic. Default is
+# unlimited. (integer value)
+#rpc_zmq_topic_backlog = <None>
+
+# Directory for holding IPC sockets. (string value)
+#rpc_zmq_ipc_dir = /var/run/openstack
+
+# Name of this node. Must be a valid hostname, FQDN, or IP address. Must match
+# "host" option, if running Nova. (string value)
+#rpc_zmq_host = localhost
+
+# Number of seconds to wait before all pending messages will be sent after
+# closing a socket. The default value of -1 specifies an infinite linger period.
+# The value of 0 specifies no linger period. Pending messages shall be discarded
+# immediately when the socket is closed. Positive values specify an upper bound
+# for the linger period. (integer value)
+# Deprecated group/name - [DEFAULT]/rpc_cast_timeout
+#zmq_linger = -1
+
+# The default number of seconds that poll should wait. Poll raises timeout
+# exception when timeout expired. (integer value)
+#rpc_poll_timeout = 1
+
+# Expiration timeout in seconds of a name service record about existing target (
+# < 0 means no timeout). (integer value)
+#zmq_target_expire = 300
+
+# Update period in seconds of a name service record about existing target.
+# (integer value)
+#zmq_target_update = 180
+
+# Use PUB/SUB pattern for fanout methods. PUB/SUB always uses proxy. (boolean
+# value)
+#use_pub_sub = false
+
+# Use ROUTER remote proxy. (boolean value)
+#use_router_proxy = false
+
+# This option makes direct connections dynamic or static. It makes sense only
+# with use_router_proxy=False which means to use direct connections for direct
+# message types (ignored otherwise). (boolean value)
+#use_dynamic_connections = false
+
+# How many additional connections to a host will be made for failover reasons.
+# This option is actual only in dynamic connections mode. (integer value)
+#zmq_failover_connections = 2
+
+# Minimal port number for random ports range. (port value)
+# Minimum value: 0
+# Maximum value: 65535
+#rpc_zmq_min_port = 49153
+
+# Maximal port number for random ports range. (integer value)
+# Minimum value: 1
+# Maximum value: 65536
+#rpc_zmq_max_port = 65536
+
+# Number of retries to find free port number before fail with ZMQBindError.
+# (integer value)
+#rpc_zmq_bind_port_retries = 100
+
+# Default serialization mechanism for serializing/deserializing
+# outgoing/incoming messages (string value)
+# Possible values:
+# json - <No description provided>
+# msgpack - <No description provided>
+#rpc_zmq_serialization = json
+
+# This option configures round-robin mode in zmq socket. True means not keeping
+# a queue when server side disconnects. False means to keep queue and messages
+# even if server is disconnected, when the server appears we send all
+# accumulated messages to it. (boolean value)
+#zmq_immediate = true
+
+# Enable/disable TCP keepalive (KA) mechanism. The default value of -1 (or any
+# other negative value) means to skip any overrides and leave it to OS default;
+# 0 and 1 (or any other positive value) mean to disable and enable the option
+# respectively. (integer value)
+#zmq_tcp_keepalive = -1
+
+# The duration between two keepalive transmissions in idle condition. The unit
+# is platform dependent, for example, seconds in Linux, milliseconds in Windows
+# etc. The default value of -1 (or any other negative value and 0) means to skip
+# any overrides and leave it to OS default. (integer value)
+#zmq_tcp_keepalive_idle = -1
+
+# The number of retransmissions to be carried out before declaring that remote
+# end is not available. The default value of -1 (or any other negative value and
+# 0) means to skip any overrides and leave it to OS default. (integer value)
+#zmq_tcp_keepalive_cnt = -1
+
+# The duration between two successive keepalive retransmissions, if
+# acknowledgement to the previous keepalive transmission is not received. The
+# unit is platform dependent, for example, seconds in Linux, milliseconds in
+# Windows etc. The default value of -1 (or any other negative value and 0) means
+# to skip any overrides and leave it to OS default. (integer value)
+#zmq_tcp_keepalive_intvl = -1
+
+# Maximum number of (green) threads to work concurrently. (integer value)
+#rpc_thread_pool_size = 100
+
+# Expiration timeout in seconds of a sent/received message after which it is not
+# tracked anymore by a client/server. (integer value)
+#rpc_message_ttl = 300
+
+# Wait for message acknowledgements from receivers. This mechanism works only
+# via proxy without PUB/SUB. (boolean value)
+#rpc_use_acks = false
+
+# Number of seconds to wait for an ack from a cast/call. After each retry
+# attempt this timeout is multiplied by some specified multiplier. (integer
+# value)
+#rpc_ack_timeout_base = 15
+
+# Number to multiply base ack timeout by after each retry attempt. (integer
+# value)
+#rpc_ack_timeout_multiplier = 2
+
+# Default number of message sending attempts in case of any problems occurred:
+# positive value N means at most N retries, 0 means no retries, None or -1 (or
+# any other negative values) mean to retry forever. This option is used only if
+# acknowledgments are enabled. (integer value)
+#rpc_retry_attempts = 3
+
+# List of publisher hosts SubConsumer can subscribe on. This option has higher
+# priority then the default publishers list taken from the matchmaker. (list
+# value)
+#subscribe_on =
+
+# Size of executor thread pool when executor is threading or eventlet. (integer
+# value)
+# Deprecated group/name - [DEFAULT]/rpc_thread_pool_size
+#executor_thread_pool_size = 64
+
+# Seconds to wait for a response from a call. (integer value)
+#rpc_response_timeout = 60
+
+# The network address and optional user credentials for connecting to the
+# messaging backend, in URL format. The expected format is:
+#
+# driver://[user:pass@]host:port[,[userN:passN@]hostN:portN]/virtual_host?query
+#
+# Example: rabbit://rabbitmq:password@127.0.0.1:5672//
+#
+# For full details on the fields in the URL see the documentation of
+# oslo_messaging.TransportURL at
+# https://docs.openstack.org/oslo.messaging/latest/reference/transport.html
+# (string value)
+transport_url = rabbit://gobsmq:GobsMQ20.@controller
+
+
+# The default exchange under which topics are scoped. May be overridden by an
+# exchange name specified in the transport_url option. (string value)
+#control_exchange = gobs
+
+#
+# From oslo.service.periodic_task
+#
+
+# Some periodic tasks can be run in a separate process. Should we run them here?
+# (boolean value)
+#run_external_periodic_tasks = true
+
+#
+# From oslo.service.service
+#
+
+# Enable eventlet backdoor. Acceptable values are 0, <port>, and <start>:<end>,
+# where 0 results in listening on a random tcp port number; <port> results in
+# listening on the specified port number (and not enabling backdoor if that port
+# is in use); and <start>:<end> results in listening on the smallest unused port
+# number within the specified range of port numbers. The chosen port is
+# displayed in the service's log file. (string value)
+#backdoor_port = <None>
+
+# Enable eventlet backdoor, using the provided path as a unix socket that can
+# receive connections. This option is mutually exclusive with 'backdoor_port' in
+# that only one should be provided. If both are provided then the existence of
+# this option overrides the usage of that option. (string value)
+#backdoor_socket = <None>
+
+# Enables or disables logging values of all registered options when starting a
+# service (at DEBUG level). (boolean value)
+log_options = true
+
+# Specify a timeout after which a gracefully shutdown server will exit. Zero
+# value means endless wait. (integer value)
+#graceful_shutdown_timeout = 60
+
+
+[api]
+#
+# Options under this group are used to define Nova API.
+
+#
+# From nova.conf
+#
+
+#
+# This determines the strategy to use for authentication: keystone or noauth2.
+# 'noauth2' is designed for testing only, as it does no actual credential
+# checking. 'noauth2' provides administrative credentials only if 'admin' is
+# specified as the username.
+# (string value)
+# Possible values:
+# keystone - <No description provided>
+# noauth2 - <No description provided>
+auth_strategy = keystone
+
+#
+# When True, the 'X-Forwarded-For' header is treated as the canonical remote
+# address. When False (the default), the 'remote_address' header is used.
+#
+# You should only enable this if you have an HTML sanitizing proxy.
+# (boolean value)
+#use_forwarded_for = false
+
+#
+# When gathering the existing metadata for a config drive, the EC2-style
+# metadata is returned for all versions that don't appear in this option.
+# As of the Liberty release, the available versions are:
+#
+# * 1.0
+# * 2007-01-19
+# * 2007-03-01
+# * 2007-08-29
+# * 2007-10-10
+# * 2007-12-15
+# * 2008-02-01
+# * 2008-09-01
+# * 2009-04-04
+#
+# The option is in the format of a single string, with each version separated
+# by a space.
+#
+# Possible values:
+#
+# * Any string that represents zero or more versions, separated by spaces.
+# (string value)
+#config_drive_skip_versions = 1.0 2007-01-19 2007-03-01 2007-08-29 2007-10-10 2007-12-15 2008-02-01 2008-09-01
+
+#
+# A list of vendordata providers.
+#
+# vendordata providers are how deployers can provide metadata via configdrive
+# and metadata that is specific to their deployment. There are currently two
+# supported providers: StaticJSON and DynamicJSON.
+#
+# StaticJSON reads a JSON file configured by the flag vendordata_jsonfile_path
+# and places the JSON from that file into vendor_data.json and
+# vendor_data2.json.
+#
+# DynamicJSON is configured via the vendordata_dynamic_targets flag, which is
+# documented separately. For each of the endpoints specified in that flag, a
+# section is added to the vendor_data2.json.
+#
+# For more information on the requirements for implementing a vendordata
+# dynamic endpoint, please see the vendordata.rst file in the nova developer
+# reference.
+#
+# Possible values:
+#
+# * A list of vendordata providers, with StaticJSON and DynamicJSON being
+# current options.
+#
+# Related options:
+#
+# * vendordata_dynamic_targets
+# * vendordata_dynamic_ssl_certfile
+# * vendordata_dynamic_connect_timeout
+# * vendordata_dynamic_read_timeout
+# * vendordata_dynamic_failure_fatal
+# (list value)
+#vendordata_providers = StaticJSON
+
+#
+# A list of targets for the dynamic vendordata provider. These targets are of
+# the form <name>@<url>.
+#
+# The dynamic vendordata provider collects metadata by contacting external REST
+# services and querying them for information about the instance. This behaviour
+# is documented in the vendordata.rst file in the nova developer reference.
+# (list value)
+#vendordata_dynamic_targets =
+
+#
+# Path to an optional certificate file or CA bundle to verify dynamic
+# vendordata REST services ssl certificates against.
+#
+# Possible values:
+#
+# * An empty string, or a path to a valid certificate file
+#
+# Related options:
+#
+# * vendordata_providers
+# * vendordata_dynamic_targets
+# * vendordata_dynamic_connect_timeout
+# * vendordata_dynamic_read_timeout
+# * vendordata_dynamic_failure_fatal
+# (string value)
+#vendordata_dynamic_ssl_certfile =
+
+#
+# Maximum wait time for an external REST service to connect.
+#
+# Possible values:
+#
+# * Any integer with a value greater than three (the TCP packet retransmission
+# timeout). Note that instance start may be blocked during this wait time,
+# so this value should be kept small.
+#
+# Related options:
+#
+# * vendordata_providers
+# * vendordata_dynamic_targets
+# * vendordata_dynamic_ssl_certfile
+# * vendordata_dynamic_read_timeout
+# * vendordata_dynamic_failure_fatal
+# (integer value)
+# Minimum value: 3
+#vendordata_dynamic_connect_timeout = 5
+
+#
+# Maximum wait time for an external REST service to return data once connected.
+#
+# Possible values:
+#
+# * Any integer. Note that instance start is blocked during this wait time,
+# so this value should be kept small.
+#
+# Related options:
+#
+# * vendordata_providers
+# * vendordata_dynamic_targets
+# * vendordata_dynamic_ssl_certfile
+# * vendordata_dynamic_connect_timeout
+# * vendordata_dynamic_failure_fatal
+# (integer value)
+# Minimum value: 0
+#vendordata_dynamic_read_timeout = 5
+
+#
+# Should failures to fetch dynamic vendordata be fatal to instance boot?
+#
+# Related options:
+#
+# * vendordata_providers
+# * vendordata_dynamic_targets
+# * vendordata_dynamic_ssl_certfile
+# * vendordata_dynamic_connect_timeout
+# * vendordata_dynamic_read_timeout
+# (boolean value)
+#vendordata_dynamic_failure_fatal = false
+
+#
+# This option is the time (in seconds) to cache metadata. When set to 0,
+# metadata caching is disabled entirely; this is generally not recommended for
+# performance reasons. Increasing this setting should improve response times
+# of the metadata API when under heavy load. Higher values may increase memory
+# usage, and result in longer times for host metadata changes to take effect.
+# (integer value)
+# Minimum value: 0
+#metadata_cache_expiration = 15
+
+#
+# Cloud providers may store custom data in vendor data file that will then be
+# available to the instances via the metadata service, and to the rendering of
+# config-drive. The default class for this, JsonFileVendorData, loads this
+# information from a JSON file, whose path is configured by this option. If
+# there is no path set by this option, the class returns an empty dictionary.
+#
+# Possible values:
+#
+# * Any string representing the path to the data file, or an empty string
+# (default).
+# (string value)
+#vendordata_jsonfile_path = <None>
+
+#
+# As a query can potentially return many thousands of items, you can limit the
+# maximum number of items in a single response by setting this option.
+# (integer value)
+# Minimum value: 0
+# Deprecated group/name - [DEFAULT]/osapi_max_limit
+#max_limit = 1000
+
+#
+# This string is prepended to the normal URL that is returned in links to the
+# OpenStack Compute API. If it is empty (the default), the URLs are returned
+# unchanged.
+#
+# Possible values:
+#
+# * Any string, including an empty string (the default).
+# (string value)
+# Deprecated group/name - [DEFAULT]/osapi_compute_link_prefix
+#compute_link_prefix = <None>
+
+#
+# This string is prepended to the normal URL that is returned in links to
+# Glance resources. If it is empty (the default), the URLs are returned
+# unchanged.
+#
+# Possible values:
+#
+# * Any string, including an empty string (the default).
+# (string value)
+# Deprecated group/name - [DEFAULT]/osapi_glance_link_prefix
+#glance_link_prefix = <None>
+
+#
+# When enabled, this will cause the API to only query cell databases
+# in which the tenant has mapped instances. This requires an additional
+# (fast) query in the API database before each list, but also
+# (potentially) limits the number of cell databases that must be queried
+# to provide the result. If you have a small number of cells, or tenants
+# are likely to have instances in all cells, then this should be
+# False. If you have many cells, especially if you confine tenants to a
+# small subset of those cells, this should be True.
+# (boolean value)
+#instance_list_per_project_cells = false
+
+
+[cache]
+
+#
+# From nova.conf
+#
+
+# Prefix for building the configuration dictionary for the cache region. This
+# should not need to be changed unless there is another dogpile.cache region
+# with the same configuration name. (string value)
+#config_prefix = cache.oslo
+
+# Default TTL, in seconds, for any cached item in the dogpile.cache region. This
+# applies to any cached method that doesn't have an explicit cache expiration
+# time defined for it. (integer value)
+#expiration_time = 600
+
+# Cache backend module. For eventlet-based or environments with hundreds of
+# threaded servers, Memcache with pooling (oslo_cache.memcache_pool) is
+# recommended. For environments with less than 100 threaded servers, Memcached
+# (dogpile.cache.memcached) or Redis (dogpile.cache.redis) is recommended. Test
+# environments with a single instance of the server can use the
+# dogpile.cache.memory backend. (string value)
+# Possible values:
+# oslo_cache.memcache_pool - <No description provided>
+# oslo_cache.dict - <No description provided>
+# oslo_cache.mongo - <No description provided>
+# oslo_cache.etcd3gw - <No description provided>
+# dogpile.cache.memcached - <No description provided>
+# dogpile.cache.pylibmc - <No description provided>
+# dogpile.cache.bmemcached - <No description provided>
+# dogpile.cache.dbm - <No description provided>
+# dogpile.cache.redis - <No description provided>
+# dogpile.cache.memory - <No description provided>
+# dogpile.cache.memory_pickle - <No description provided>
+# dogpile.cache.null - <No description provided>
+#backend = dogpile.cache.null
+
+# Arguments supplied to the backend module. Specify this option once per
+# argument to be passed to the dogpile.cache backend. Example format:
+# "<argname>:<value>". (multi valued)
+#backend_argument =
+
+# Proxy classes to import that will affect the way the dogpile.cache backend
+# functions. See the dogpile.cache documentation on changing-backend-behavior.
+# (list value)
+#proxies =
+
+# Global toggle for caching. (boolean value)
+#enabled = false
+
+# Extra debugging from the cache backend (cache keys, get/set/delete/etc calls).
+# This is only really useful if you need to see the specific cache-backend
+# get/set/delete calls with the keys/values. Typically this should be left set
+# to false. (boolean value)
+#debug_cache_backend = false
+
+# Memcache servers in the format of "host:port". (dogpile.cache.memcache and
+# oslo_cache.memcache_pool backends only). (list value)
+#memcache_servers = localhost:11211
+
+# Number of seconds memcached server is considered dead before it is tried
+# again. (dogpile.cache.memcache and oslo_cache.memcache_pool backends only).
+# (integer value)
+#memcache_dead_retry = 300
+
+# Timeout in seconds for every call to a server. (dogpile.cache.memcache and
+# oslo_cache.memcache_pool backends only). (floating point value)
+#memcache_socket_timeout = 3.0
+
+# Max total number of open connections to every memcached server.
+# (oslo_cache.memcache_pool backend only). (integer value)
+#memcache_pool_maxsize = 10
+
+# Number of seconds a connection to memcached is held unused in the pool before
+# it is closed. (oslo_cache.memcache_pool backend only). (integer value)
+#memcache_pool_unused_timeout = 60
+
+# Number of seconds that an operation will wait to get a memcache client
+# connection. (integer value)
+#memcache_pool_connection_get_timeout = 10
+
+[database]
+
+#
+# From oslo.db
+#
+
+# If True, SQLite uses synchronous mode. (boolean value)
+#sqlite_synchronous = true
+
+# The back end to use for the database. (string value)
+# Deprecated group/name - [DEFAULT]/db_backend
+backend = sqlalchemy
+
+# The SQLAlchemy connection string to use to connect to the database. (string
+# value)
+# Deprecated group/name - [DEFAULT]/sql_connection
+# Deprecated group/name - [DATABASE]/sql_connection
+# Deprecated group/name - [sql]/connection
+connection = mysql+pymysql://gosbs:SGmA1GiDzZcVc9WA@controller/gosbs
+
+# The SQLAlchemy connection string to use to connect to the slave database.
+# (string value)
+#slave_connection = <None>
+
+# The SQL mode to be used for MySQL sessions. This option, including the
+# default, overrides any server-set SQL mode. To use whatever SQL mode is set by
+# the server configuration, set this to no value. Example: mysql_sql_mode=
+# (string value)
+#mysql_sql_mode = TRADITIONAL
+
+# If True, transparently enables support for handling MySQL Cluster (NDB).
+# (boolean value)
+#mysql_enable_ndb = false
+
+# Connections which have been present in the connection pool longer than this
+# number of seconds will be replaced with a new one the next time they are
+# checked out from the pool. (integer value)
+# Deprecated group/name - [DATABASE]/idle_timeout
+# Deprecated group/name - [database]/idle_timeout
+# Deprecated group/name - [DEFAULT]/sql_idle_timeout
+# Deprecated group/name - [DATABASE]/sql_idle_timeout
+# Deprecated group/name - [sql]/idle_timeout
+#connection_recycle_time = 3600
+
+
+# Maximum number of SQL connections to keep open in a pool. Setting a value of 0
+# indicates no limit. (integer value)
+# Deprecated group/name - [DEFAULT]/sql_max_pool_size
+# Deprecated group/name - [DATABASE]/sql_max_pool_size
+#max_pool_size = 5
+
+# Maximum number of database connection retries during startup. Set to -1 to
+# specify an infinite retry count. (integer value)
+# Deprecated group/name - [DEFAULT]/sql_max_retries
+# Deprecated group/name - [DATABASE]/sql_max_retries
+#max_retries = 10
+
+# Interval between retries of opening a SQL connection. (integer value)
+# Deprecated group/name - [DEFAULT]/sql_retry_interval
+# Deprecated group/name - [DATABASE]/reconnect_interval
+#retry_interval = 10
+
+# If set, use this value for max_overflow with SQLAlchemy. (integer value)
+# Deprecated group/name - [DEFAULT]/sql_max_overflow
+# Deprecated group/name - [DATABASE]/sqlalchemy_max_overflow
+#max_overflow = 50
+
+# Verbosity of SQL debugging information: 0=None, 100=Everything. (integer
+# value)
+# Minimum value: 0
+# Maximum value: 100
+# Deprecated group/name - [DEFAULT]/sql_connection_debug
+#connection_debug = 0
+
+# Add Python stack traces to SQL as comment strings. (boolean value)
+# Deprecated group/name - [DEFAULT]/sql_connection_trace
+#connection_trace = false
+
+# If set, use this value for pool_timeout with SQLAlchemy. (integer value)
+# Deprecated group/name - [DATABASE]/sqlalchemy_pool_timeout
+#pool_timeout = <None>
+
+# Enable the experimental use of database reconnect on connection lost. (boolean
+# value)
+#use_db_reconnect = false
+
+# Seconds between retries of a database transaction. (integer value)
+#db_retry_interval = 1
+
+# If True, increases the interval between retries of a database operation up to
+# db_max_retry_interval. (boolean value)
+#db_inc_retry_interval = true
+
+# If db_inc_retry_interval is set, the maximum seconds between retries of a
+# database operation. (integer value)
+#db_max_retry_interval = 10
+
+# Maximum retries in case of connection error or deadlock error before error is
+# raised. Set to -1 to specify an infinite retry count. (integer value)
+#db_max_retries = 20
+
+# Optional URL parameters to append onto the connection URL at connect time;
+# specify as param1=value1&param2=value2&... (string value)
+#connection_parameters =
+
+[healthcheck]
+
+#
+# From oslo.middleware
+#
+
+# Show more detailed information as part of the response (boolean value)
+#detailed = false
+
+# Additional backends that can perform health checks and report that information
+# back as part of a request. (list value)
+#backends =
+
+# Check the presence of a file to determine if an application is running on a
+# port. Used by DisableByFileHealthcheck plugin. (string value)
+#disable_by_file_path = <None>
+
+# Check the presence of a file based on a port to determine if an application is
+# running on a port. Expects a "port:path" list of strings. Used by
+# DisableByFilesPortsHealthcheck plugin. (list value)
+#disable_by_file_paths =
+
+
+[key_manager]
+
+#
+# From nova.conf
+#
+
+#
+# Fixed key returned by key manager, specified in hex.
+#
+# Possible values:
+#
+# * Empty string or a key in hex value
+# (string value)
+#fixed_key = <None>
+
+# Specify the key manager implementation. Options are "barbican" and "vault".
+# Default is "barbican". Will support the values earlier set using
+# [key_manager]/api_class for some time. (string value)
+# Deprecated group/name - [key_manager]/api_class
+#backend = barbican
+
+# The type of authentication credential to create. Possible values are 'token',
+# 'password', 'keystone_token', and 'keystone_password'. Required if no context
+# is passed to the credential factory. (string value)
+#auth_type = <None>
+
+# Token for authentication. Required for 'token' and 'keystone_token' auth_type
+# if no context is passed to the credential factory. (string value)
+#token = <None>
+
+# Username for authentication. Required for 'password' auth_type. Optional for
+# the 'keystone_password' auth_type. (string value)
+#username = <None>
+
+# Password for authentication. Required for 'password' and 'keystone_password'
+# auth_type. (string value)
+#password = <None>
+
+# Use this endpoint to connect to Keystone. (string value)
+#auth_url = <None>
+
+# User ID for authentication. Optional for 'keystone_token' and
+# 'keystone_password' auth_type. (string value)
+#user_id = <None>
+
+# User's domain ID for authentication. Optional for 'keystone_token' and
+# 'keystone_password' auth_type. (string value)
+#user_domain_id = <None>
+
+# User's domain name for authentication. Optional for 'keystone_token' and
+# 'keystone_password' auth_type. (string value)
+#user_domain_name = <None>
+
+# Trust ID for trust scoping. Optional for 'keystone_token' and
+# 'keystone_password' auth_type. (string value)
+#trust_id = <None>
+
+# Domain ID for domain scoping. Optional for 'keystone_token' and
+# 'keystone_password' auth_type. (string value)
+#domain_id = <None>
+
+# Domain name for domain scoping. Optional for 'keystone_token' and
+# 'keystone_password' auth_type. (string value)
+#domain_name = <None>
+
+# Project ID for project scoping. Optional for 'keystone_token' and
+# 'keystone_password' auth_type. (string value)
+#project_id = <None>
+
+# Project name for project scoping. Optional for 'keystone_token' and
+# 'keystone_password' auth_type. (string value)
+#project_name = <None>
+
+# Project's domain ID for project. Optional for 'keystone_token' and
+# 'keystone_password' auth_type. (string value)
+#project_domain_id = <None>
+
+# Project's domain name for project. Optional for 'keystone_token' and
+# 'keystone_password' auth_type. (string value)
+#project_domain_name = <None>
+
+# Allow fetching a new token if the current one is going to expire. Optional for
+# 'keystone_token' and 'keystone_password' auth_type. (boolean value)
+#reauthenticate = true
+
+
+[keystone]
+# Configuration options for the identity service
+
+#
+# From nova.conf
+#
+
+# PEM encoded Certificate Authority to use when verifying HTTPs connections.
+# (string value)
+#cafile = <None>
+
+# PEM encoded client certificate cert file (string value)
+#certfile = <None>
+
+# PEM encoded client certificate key file (string value)
+#keyfile = <None>
+
+# Verify HTTPS connections. (boolean value)
+#insecure = false
+
+# Timeout value for http requests (integer value)
+#timeout = <None>
+
+# Collect per-API call timing information. (boolean value)
+#collect_timing = false
+
+# Log requests to multiple loggers. (boolean value)
+#split_loggers = false
+
+# The default service_type for endpoint URL discovery. (string value)
+#service_type = identity
+
+# The default service_name for endpoint URL discovery. (string value)
+#service_name = <None>
+
+# List of interfaces, in order of preference, for endpoint URL. (list value)
+#valid_interfaces = internal
+
+# The default region_name for endpoint URL discovery. (string value)
+region_name = RegionOne
+
+# API version of the admin Identity API endpoint. (string value)
+auth_version = 2
+
+identity_interface = internal
+# Always use this endpoint URL for requests for this client. NOTE: The
+# unversioned endpoint should be specified here; to request a particular API
+# version, use the `version`, `min-version`, and/or `max-version` options.
+# (string value)
+#endpoint_override = <None>
+auth_url = http://controller:35357
+auth_type = password
+project_domain_name = default
+user_domain_name = default
+project_id = b422264d56ae485f8e8e7abbe83ae781
+username = gobs
+password = gobs20.
+
+[keystone_authtoken]
+
+#
+# From keystonemiddleware.auth_token
+#
+
+# Complete "public" Identity API endpoint. This endpoint should not be an
+# "admin" endpoint, as it should be accessible by all end users. Unauthenticated
+# clients are redirected to this endpoint to authenticate. Although this
+# endpoint should ideally be unversioned, client support in the wild varies. If
+# you're using a versioned v2 endpoint here, then this should *not* be the same
+# endpoint the service user utilizes for validating tokens, because normal end
+# users may not be able to reach that endpoint. (string value)
+# Deprecated group/name - [keystone_authtoken]/auth_uri
+#www_authenticate_uri = <None>
+
+# DEPRECATED: Complete "public" Identity API endpoint. This endpoint should not
+# be an "admin" endpoint, as it should be accessible by all end users.
+# Unauthenticated clients are redirected to this endpoint to authenticate.
+# Although this endpoint should ideally be unversioned, client support in the
+# wild varies. If you're using a versioned v2 endpoint here, then this should
+# *not* be the same endpoint the service user utilizes for validating tokens,
+# because normal end users may not be able to reach that endpoint. This option
+# is deprecated in favor of www_authenticate_uri and will be removed in the S
+# release. (string value)
+# This option is deprecated for removal since Queens.
+# Its value may be silently ignored in the future.
+# Reason: The auth_uri option is deprecated in favor of www_authenticate_uri and
+# will be removed in the S release.
+#auth_uri = <None>
+
+# API version of the admin Identity API endpoint. (string value)
+#auth_version = <None>
+
+# Do not handle authorization requests within the middleware, but delegate the
+# authorization decision to downstream WSGI components. (boolean value)
+#delay_auth_decision = false
+
+# Request timeout value for communicating with Identity API server. (integer
+# value)
+#http_connect_timeout = <None>
+
+# How many times are we trying to reconnect when communicating with Identity API
+# Server. (integer value)
+#http_request_max_retries = 3
+
+# Request environment key where the Swift cache object is stored. When
+# auth_token middleware is deployed with a Swift cache, use this option to have
+# the middleware share a caching backend with swift. Otherwise, use the
+# ``memcached_servers`` option instead. (string value)
+#cache = <None>
+
+# Required if identity server requires client certificate (string value)
+#certfile = <None>
+
+# Required if identity server requires client certificate (string value)
+#keyfile = <None>
+
+# A PEM encoded Certificate Authority to use when verifying HTTPs connections.
+# Defaults to system CAs. (string value)
+#cafile = <None>
+
+# Verify HTTPS connections. (boolean value)
+#insecure = false
+
+# The region in which the identity server can be found. (string value)
+#region_name = <None>
+
+# Optionally specify a list of memcached server(s) to use for caching. If left
+# undefined, tokens will instead be cached in-process. (list value)
+# Deprecated group/name - [keystone_authtoken]/memcache_servers
+#memcached_servers = <None>
+
+# In order to prevent excessive effort spent validating tokens, the middleware
+# caches previously-seen tokens for a configurable duration (in seconds). Set to
+# -1 to disable caching completely. (integer value)
+#token_cache_time = 300
+
+# (Optional) If defined, indicate whether token data should be authenticated or
+# authenticated and encrypted. If MAC, token data is authenticated (with HMAC)
+# in the cache. If ENCRYPT, token data is encrypted and authenticated in the
+# cache. If the value is not one of these options or empty, auth_token will
+# raise an exception on initialization. (string value)
+# Possible values:
+# None - <No description provided>
+# MAC - <No description provided>
+# ENCRYPT - <No description provided>
+#memcache_security_strategy = None
+
+# (Optional, mandatory if memcache_security_strategy is defined) This string is
+# used for key derivation. (string value)
+#memcache_secret_key = <None>
+
+# (Optional) Number of seconds memcached server is considered dead before it is
+# tried again. (integer value)
+#memcache_pool_dead_retry = 300
+
+# (Optional) Maximum total number of open connections to every memcached server.
+# (integer value)
+#memcache_pool_maxsize = 10
+
+# (Optional) Socket timeout in seconds for communicating with a memcached
+# server. (integer value)
+#memcache_pool_socket_timeout = 3
+
+# (Optional) Number of seconds a connection to memcached is held unused in the
+# pool before it is closed. (integer value)
+#memcache_pool_unused_timeout = 60
+
+# (Optional) Number of seconds that an operation will wait to get a memcached
+# client connection from the pool. (integer value)
+#memcache_pool_conn_get_timeout = 10
+
+# (Optional) Use the advanced (eventlet safe) memcached client pool. The
+# advanced pool will only work under python 2.x. (boolean value)
+#memcache_use_advanced_pool = false
+
+# (Optional) Indicate whether to set the X-Service-Catalog header. If False,
+# middleware will not ask for service catalog on token validation and will not
+# set the X-Service-Catalog header. (boolean value)
+#include_service_catalog = true
+
+# Used to control the use and type of token binding. Can be set to: "disabled"
+# to not check token binding. "permissive" (default) to validate binding
+# information if the bind type is of a form known to the server and ignore it if
+# not. "strict" like "permissive" but if the bind type is unknown the token will
+# be rejected. "required" any form of token binding is needed to be allowed.
+# Finally the name of a binding method that must be present in tokens. (string
+# value)
+#enforce_token_bind = permissive
+
+# A choice of roles that must be present in a service token. Service tokens are
+# allowed to request that an expired token can be used and so this check should
+# tightly control that only actual services should be sending this token. Roles
+# here are applied as an ANY check so any role in this list must be present. For
+# backwards compatibility reasons this currently only affects the allow_expired
+# check. (list value)
+#service_token_roles = service
+
+# For backwards compatibility reasons we must let valid service tokens pass that
+# don't pass the service_token_roles check as valid. Setting this true will
+# become the default in a future release and should be enabled if possible.
+# (boolean value)
+service_token_roles_required = true
+
+# Authentication type to load (string value)
+# Deprecated group/name - [keystone_authtoken]/auth_plugin
+#auth_type = <None>
+
+# Config Section from which to load plugin specific options (string value)
+#auth_section = <None>
+#auth_uri = http://controller:5001
+#auth_url = http://controller:35357
+#auth_type = password
+#project_domain_name = default
+#user_domain_name = default
+#project_name = gobsproject
+#username = gobs
+#password = gobs20.
+
+[notifications]
+#
+# Most of the actions in Nova which manipulate the system state generate
+# notifications which are posted to the messaging component (e.g. RabbitMQ) and
+# can be consumed by any service outside the OpenStack. More technical details
+# at https://docs.openstack.org/nova/latest/reference/notifications.html
+
+#
+# From nova.conf
+#
+
+#
+# If set, send compute.instance.update notifications on
+# instance state changes.
+#
+# Please refer to
+# https://docs.openstack.org/nova/latest/reference/notifications.html for
+# additional information on notifications.
+#
+# Possible values:
+#
+# * None - no notifications
+# * "vm_state" - notifications are sent with VM state transition information in
+# the ``old_state`` and ``state`` fields. The ``old_task_state`` and
+# ``new_task_state`` fields will be set to the current task_state of the
+# instance.
+# * "vm_and_task_state" - notifications are sent with VM and task state
+# transition information.
+# (string value)
+# Possible values:
+# <None> - <No description provided>
+# vm_state - <No description provided>
+# vm_and_task_state - <No description provided>
+#notify_on_state_change = <None>
+
+# Default notification level for outgoing notifications. (string value)
+# Possible values:
+# DEBUG - <No description provided>
+# INFO - <No description provided>
+# WARN - <No description provided>
+# ERROR - <No description provided>
+# CRITICAL - <No description provided>
+# Deprecated group/name - [DEFAULT]/default_notification_level
+#default_level = INFO
+
+#
+# Specifies which notification format shall be used by nova.
+#
+# The default value is fine for most deployments and rarely needs to be changed.
+# This value can be set to 'versioned' once the infrastructure moves closer to
+# consuming the newer format of notifications. After this occurs, this option
+# will be removed.
+#
+# Note that notifications can be completely disabled by setting ``driver=noop``
+# in the ``[oslo_messaging_notifications]`` group.
+#
+# Possible values:
+#
+# * unversioned: Only the legacy unversioned notifications are emitted.
+# * versioned: Only the new versioned notifications are emitted.
+# * both: Both the legacy unversioned and the new versioned notifications are
+# emitted. (Default)
+#
+# The list of versioned notifications is visible in
+# https://docs.openstack.org/nova/latest/reference/notifications.html
+# (string value)
+# Possible values:
+# unversioned - <No description provided>
+# versioned - <No description provided>
+# both - <No description provided>
+#notification_format = versioned
+
+#
+# Specifies the topics for the versioned notifications issued by nova.
+#
+# The default value is fine for most deployments and rarely needs to be changed.
+# However, if you have a third-party service that consumes versioned
+# notifications, it might be worth getting a topic for that service.
+# Nova will send a message containing a versioned notification payload to each
+# topic queue in this list.
+#
+# The list of versioned notifications is visible in
+# https://docs.openstack.org/nova/latest/reference/notifications.html
+# (list value)
+#versioned_notifications_topics = versioned_notifications
+
+#
+# If enabled, include block device information in the versioned notification
+# payload. Sending block device information is disabled by default as providing
+# that information can incur some overhead on the system since the information
+# may need to be loaded from the database.
+# (boolean value)
+#bdms_in_notifications = false
+
+[oslo_concurrency]
+
+#
+# From oslo.concurrency
+#
+
+# Enables or disables inter-process locks. (boolean value)
+#disable_process_locking = false
+
+# Directory to use for lock files. For security, the specified directory should
+# only be writable by the user running the processes that need locking. Defaults
+# to environment variable OSLO_LOCK_PATH. If external locks are used, a lock
+# path must be set. (string value)
+#lock_path = <None>
+
+
+[oslo_messaging_amqp]
+
+#
+# From oslo.messaging
+#
+
+# Name for the AMQP container. must be globally unique. Defaults to a generated
+# UUID (string value)
+#container_name = <None>
+
+# Timeout for inactive connections (in seconds) (integer value)
+#idle_timeout = 0
+
+# Debug: dump AMQP frames to stdout (boolean value)
+#trace = false
+
+# Attempt to connect via SSL. If no other ssl-related parameters are given, it
+# will use the system's CA-bundle to verify the server's certificate. (boolean
+# value)
+#ssl = false
+
+# CA certificate PEM file used to verify the server's certificate (string value)
+#ssl_ca_file =
+
+# Self-identifying certificate PEM file for client authentication (string value)
+#ssl_cert_file =
+
+# Private key PEM file used to sign ssl_cert_file certificate (optional) (string
+# value)
+#ssl_key_file =
+
+# Password for decrypting ssl_key_file (if encrypted) (string value)
+#ssl_key_password = <None>
+
+# By default SSL checks that the name in the server's certificate matches the
+# hostname in the transport_url. In some configurations it may be preferable to
+# use the virtual hostname instead, for example if the server uses the Server
+# Name Indication TLS extension (rfc6066) to provide a certificate per virtual
+# host. Set ssl_verify_vhost to True if the server's SSL certificate uses the
+# virtual host name instead of the DNS name. (boolean value)
+#ssl_verify_vhost = false
+
+# DEPRECATED: Accept clients using either SSL or plain TCP (boolean value)
+# This option is deprecated for removal.
+# Its value may be silently ignored in the future.
+# Reason: Not applicable - not a SSL server
+#allow_insecure_clients = false
+
+# Space separated list of acceptable SASL mechanisms (string value)
+#sasl_mechanisms =
+
+# Path to directory that contains the SASL configuration (string value)
+#sasl_config_dir =
+
+# Name of configuration file (without .conf suffix) (string value)
+#sasl_config_name =
+
+# SASL realm to use if no realm present in username (string value)
+#sasl_default_realm =
+
+# DEPRECATED: User name for message broker authentication (string value)
+# This option is deprecated for removal.
+# Its value may be silently ignored in the future.
+# Reason: Should use configuration option transport_url to provide the username.
+#username =
+
+# DEPRECATED: Password for message broker authentication (string value)
+# This option is deprecated for removal.
+# Its value may be silently ignored in the future.
+# Reason: Should use configuration option transport_url to provide the password.
+#password =
+
+# Seconds to pause before attempting to re-connect. (integer value)
+# Minimum value: 1
+#connection_retry_interval = 1
+
+# Increase the connection_retry_interval by this many seconds after each
+# unsuccessful failover attempt. (integer value)
+# Minimum value: 0
+#connection_retry_backoff = 2
+
+# Maximum limit for connection_retry_interval + connection_retry_backoff
+# (integer value)
+# Minimum value: 1
+#connection_retry_interval_max = 30
+
+# Time to pause between re-connecting an AMQP 1.0 link that failed due to a
+# recoverable error. (integer value)
+# Minimum value: 1
+#link_retry_delay = 10
+
+# The maximum number of attempts to re-send a reply message which failed due to
+# a recoverable error. (integer value)
+# Minimum value: -1
+#default_reply_retry = 0
+
+# The deadline for an rpc reply message delivery. (integer value)
+# Minimum value: 5
+#default_reply_timeout = 30
+
+# The deadline for an rpc cast or call message delivery. Only used when caller
+# does not provide a timeout expiry. (integer value)
+# Minimum value: 5
+#default_send_timeout = 30
+
+# The deadline for a sent notification message delivery. Only used when caller
+# does not provide a timeout expiry. (integer value)
+# Minimum value: 5
+#default_notify_timeout = 30
+
+# The duration to schedule a purge of idle sender links. Detach link after
+# expiry. (integer value)
+# Minimum value: 1
+#default_sender_link_timeout = 600
+
+# Indicates the addressing mode used by the driver.
+# Permitted values:
+# 'legacy' - use legacy non-routable addressing
+# 'routable' - use routable addresses
+# 'dynamic' - use legacy addresses if the message bus does not support routing
+# otherwise use routable addressing (string value)
+#addressing_mode = dynamic
+
+# Enable virtual host support for those message buses that do not natively
+# support virtual hosting (such as qpidd). When set to true the virtual host
+# name will be added to all message bus addresses, effectively creating a
+# private 'subnet' per virtual host. Set to False if the message bus supports
+# virtual hosting using the 'hostname' field in the AMQP 1.0 Open performative
+# as the name of the virtual host. (boolean value)
+#pseudo_vhost = true
+
+# address prefix used when sending to a specific server (string value)
+#server_request_prefix = exclusive
+
+# address prefix used when broadcasting to all servers (string value)
+#broadcast_prefix = broadcast
+
+# address prefix when sending to any server in group (string value)
+#group_request_prefix = unicast
+
+# Address prefix for all generated RPC addresses (string value)
+#rpc_address_prefix = openstack.org/om/rpc
+
+# Address prefix for all generated Notification addresses (string value)
+#notify_address_prefix = openstack.org/om/notify
+
+# Appended to the address prefix when sending a fanout message. Used by the
+# message bus to identify fanout messages. (string value)
+#multicast_address = multicast
+
+# Appended to the address prefix when sending to a particular RPC/Notification
+# server. Used by the message bus to identify messages sent to a single
+# destination. (string value)
+#unicast_address = unicast
+
+# Appended to the address prefix when sending to a group of consumers. Used by
+# the message bus to identify messages that should be delivered in a round-robin
+# fashion across consumers. (string value)
+#anycast_address = anycast
+
+# Exchange name used in notification addresses.
+# Exchange name resolution precedence:
+# Target.exchange if set
+# else default_notification_exchange if set
+# else control_exchange if set
+# else 'notify' (string value)
+#default_notification_exchange = <None>
+
+# Exchange name used in RPC addresses.
+# Exchange name resolution precedence:
+# Target.exchange if set
+# else default_rpc_exchange if set
+# else control_exchange if set
+# else 'rpc' (string value)
+#default_rpc_exchange = <None>
+
+# Window size for incoming RPC Reply messages. (integer value)
+# Minimum value: 1
+#reply_link_credit = 200
+
+# Window size for incoming RPC Request messages (integer value)
+# Minimum value: 1
+#rpc_server_credit = 100
+
+# Window size for incoming Notification messages (integer value)
+# Minimum value: 1
+#notify_server_credit = 100
+
+# Send messages of this type pre-settled.
+# Pre-settled messages will not receive acknowledgement
+# from the peer. Note well: pre-settled messages may be
+# silently discarded if the delivery fails.
+# Permitted values:
+# 'rpc-call' - send RPC Calls pre-settled
+# 'rpc-reply'- send RPC Replies pre-settled
+# 'rpc-cast' - Send RPC Casts pre-settled
+# 'notify' - Send Notifications pre-settled
+# (multi valued)
+#pre_settled = rpc-cast
+#pre_settled = rpc-reply
+
+
+[oslo_messaging_kafka]
+
+#
+# From oslo.messaging
+#
+
+# DEPRECATED: Default Kafka broker Host (string value)
+# This option is deprecated for removal.
+# Its value may be silently ignored in the future.
+# Reason: Replaced by [DEFAULT]/transport_url
+#kafka_default_host = localhost
+
+# DEPRECATED: Default Kafka broker Port (port value)
+# Minimum value: 0
+# Maximum value: 65535
+# This option is deprecated for removal.
+# Its value may be silently ignored in the future.
+# Reason: Replaced by [DEFAULT]/transport_url
+#kafka_default_port = 9092
+
+# Max fetch bytes of Kafka consumer (integer value)
+#kafka_max_fetch_bytes = 1048576
+
+# Default timeout(s) for Kafka consumers (floating point value)
+#kafka_consumer_timeout = 1.0
+
+# DEPRECATED: Pool Size for Kafka Consumers (integer value)
+# This option is deprecated for removal.
+# Its value may be silently ignored in the future.
+# Reason: Driver no longer uses connection pool.
+#pool_size = 10
+
+# DEPRECATED: The pool size limit for connections expiration policy (integer
+# value)
+# This option is deprecated for removal.
+# Its value may be silently ignored in the future.
+# Reason: Driver no longer uses connection pool.
+#conn_pool_min_size = 2
+
+# DEPRECATED: The time-to-live in sec of idle connections in the pool (integer
+# value)
+# This option is deprecated for removal.
+# Its value may be silently ignored in the future.
+# Reason: Driver no longer uses connection pool.
+#conn_pool_ttl = 1200
+
+# Group id for Kafka consumer. Consumers in one group will coordinate message
+# consumption (string value)
+#consumer_group = oslo_messaging_consumer
+
+# Upper bound on the delay for KafkaProducer batching in seconds (floating point
+# value)
+#producer_batch_timeout = 0.0
+
+# Size of batch for the producer async send (integer value)
+#producer_batch_size = 16384
+
+# Enable asynchronous consumer commits (boolean value)
+#enable_auto_commit = false
+
+# The maximum number of records returned in a poll call (integer value)
+#max_poll_records = 500
+
+# Protocol used to communicate with brokers (string value)
+# Possible values:
+# PLAINTEXT - <No description provided>
+# SASL_PLAINTEXT - <No description provided>
+# SSL - <No description provided>
+# SASL_SSL - <No description provided>
+#security_protocol = PLAINTEXT
+
+# Mechanism when security protocol is SASL (string value)
+#sasl_mechanism = PLAIN
+
+# CA certificate PEM file used to verify the server certificate (string value)
+#ssl_cafile =
+
+
+[oslo_messaging_notifications]
+
+#
+# From oslo.messaging
+#
+
+# The Drivers(s) to handle sending notifications. Possible values are messaging,
+# messagingv2, routing, log, test, noop (multi valued)
+# Deprecated group/name - [DEFAULT]/notification_driver
+#driver =
+
+# A URL representing the messaging driver to use for notifications. If not set,
+# we fall back to the same configuration used for RPC. (string value)
+# Deprecated group/name - [DEFAULT]/notification_transport_url
+#transport_url = <None>
+
+# AMQP topic used for OpenStack notifications. (list value)
+# Deprecated group/name - [rpc_notifier2]/topics
+# Deprecated group/name - [DEFAULT]/notification_topics
+#topics = notifications
+
+# The maximum number of attempts to re-send a notification message which failed
+# to be delivered due to a recoverable error. 0 - No retry, -1 - indefinite
+# (integer value)
+#retry = -1
+
+
+[oslo_messaging_rabbit]
+
+#
+# From oslo.messaging
+#
+
+# Use durable queues in AMQP. (boolean value)
+# Deprecated group/name - [DEFAULT]/amqp_durable_queues
+# Deprecated group/name - [DEFAULT]/rabbit_durable_queues
+#amqp_durable_queues = false
+
+# Auto-delete queues in AMQP. (boolean value)
+#amqp_auto_delete = false
+
+# Connect over SSL. (boolean value)
+# Deprecated group/name - [oslo_messaging_rabbit]/rabbit_use_ssl
+#ssl = false
+
+# SSL version to use (valid only if SSL enabled). Valid values are TLSv1 and
+# SSLv23. SSLv2, SSLv3, TLSv1_1, and TLSv1_2 may be available on some
+# distributions. (string value)
+# Deprecated group/name - [oslo_messaging_rabbit]/kombu_ssl_version
+#ssl_version =
+
+# SSL key file (valid only if SSL enabled). (string value)
+# Deprecated group/name - [oslo_messaging_rabbit]/kombu_ssl_keyfile
+#ssl_key_file =
+
+# SSL cert file (valid only if SSL enabled). (string value)
+# Deprecated group/name - [oslo_messaging_rabbit]/kombu_ssl_certfile
+#ssl_cert_file =
+
+# SSL certification authority file (valid only if SSL enabled). (string value)
+# Deprecated group/name - [oslo_messaging_rabbit]/kombu_ssl_ca_certs
+#ssl_ca_file =
+
+# How long to wait before reconnecting in response to an AMQP consumer cancel
+# notification. (floating point value)
+#kombu_reconnect_delay = 1.0
+
+# EXPERIMENTAL: Possible values are: gzip, bz2. If not set compression will not
+# be used. This option may not be available in future versions. (string value)
+#kombu_compression = <None>
+
+# How long to wait a missing client before abandoning to send it its replies.
+# This value should not be longer than rpc_response_timeout. (integer value)
+# Deprecated group/name - [oslo_messaging_rabbit]/kombu_reconnect_timeout
+#kombu_missing_consumer_retry_timeout = 60
+
+# Determines how the next RabbitMQ node is chosen in case the one we are
+# currently connected to becomes unavailable. Takes effect only if more than one
+# RabbitMQ node is provided in config. (string value)
+# Possible values:
+# round-robin - <No description provided>
+# shuffle - <No description provided>
+#kombu_failover_strategy = round-robin
+
+# DEPRECATED: The RabbitMQ broker address where a single node is used. (string
+# value)
+# This option is deprecated for removal.
+# Its value may be silently ignored in the future.
+# Reason: Replaced by [DEFAULT]/transport_url
+#rabbit_host = localhost
+
+# DEPRECATED: The RabbitMQ broker port where a single node is used. (port value)
+# Minimum value: 0
+# Maximum value: 65535
+# This option is deprecated for removal.
+# Its value may be silently ignored in the future.
+# Reason: Replaced by [DEFAULT]/transport_url
+#rabbit_port = 5672
+
+# DEPRECATED: RabbitMQ HA cluster host:port pairs. (list value)
+# This option is deprecated for removal.
+# Its value may be silently ignored in the future.
+# Reason: Replaced by [DEFAULT]/transport_url
+#rabbit_hosts = $rabbit_host:$rabbit_port
+
+# DEPRECATED: The RabbitMQ userid. (string value)
+# This option is deprecated for removal.
+# Its value may be silently ignored in the future.
+# Reason: Replaced by [DEFAULT]/transport_url
+#rabbit_userid = guest
+
+# DEPRECATED: The RabbitMQ password. (string value)
+# This option is deprecated for removal.
+# Its value may be silently ignored in the future.
+# Reason: Replaced by [DEFAULT]/transport_url
+#rabbit_password = guest
+
+# The RabbitMQ login method. (string value)
+# Possible values:
+# PLAIN - <No description provided>
+# AMQPLAIN - <No description provided>
+# RABBIT-CR-DEMO - <No description provided>
+#rabbit_login_method = AMQPLAIN
+
+# DEPRECATED: The RabbitMQ virtual host. (string value)
+# This option is deprecated for removal.
+# Its value may be silently ignored in the future.
+# Reason: Replaced by [DEFAULT]/transport_url
+#rabbit_virtual_host = /
+
+# How frequently to retry connecting with RabbitMQ. (integer value)
+#rabbit_retry_interval = 1
+
+# How long to backoff for between retries when connecting to RabbitMQ. (integer
+# value)
+#rabbit_retry_backoff = 2
+
+# Maximum interval of RabbitMQ connection retries. Default is 30 seconds.
+# (integer value)
+#rabbit_interval_max = 30
+
+# DEPRECATED: Maximum number of RabbitMQ connection retries. Default is 0
+# (infinite retry count). (integer value)
+# This option is deprecated for removal.
+# Its value may be silently ignored in the future.
+#rabbit_max_retries = 0
+
+# Try to use HA queues in RabbitMQ (x-ha-policy: all). If you change this
+# option, you must wipe the RabbitMQ database. In RabbitMQ 3.0, queue mirroring
+# is no longer controlled by the x-ha-policy argument when declaring a queue. If
+# you just want to make sure that all queues (except those with auto-generated
+# names) are mirrored across all nodes, run: "rabbitmqctl set_policy HA
+# '^(?!amq\.).*' '{"ha-mode": "all"}' " (boolean value)
+#rabbit_ha_queues = false
+
+# Positive integer representing duration in seconds for queue TTL (x-expires).
+# Queues which are unused for the duration of the TTL are automatically deleted.
+# The parameter affects only reply and fanout queues. (integer value)
+# Minimum value: 1
+#rabbit_transient_queues_ttl = 1800
+
+# Specifies the number of messages to prefetch. Setting to zero allows unlimited
+# messages. (integer value)
+#rabbit_qos_prefetch_count = 0
+
+# Number of seconds after which the Rabbit broker is considered down if
+# heartbeat's keep-alive fails (0 disable the heartbeat). EXPERIMENTAL (integer
+# value)
+#heartbeat_timeout_threshold = 60
+
+# How often times during the heartbeat_timeout_threshold we check the heartbeat.
+# (integer value)
+#heartbeat_rate = 2
+
+
+[oslo_messaging_zmq]
+
+#
+# From oslo.messaging
+#
+
+# ZeroMQ bind address. Should be a wildcard (*), an ethernet interface, or IP.
+# The "host" option should point or resolve to this address. (string value)
+#rpc_zmq_bind_address = *
+
+# MatchMaker driver. (string value)
+# Possible values:
+# redis - <No description provided>
+# sentinel - <No description provided>
+# dummy - <No description provided>
+#rpc_zmq_matchmaker = redis
+
+# Number of ZeroMQ contexts, defaults to 1. (integer value)
+#rpc_zmq_contexts = 1
+
+# Maximum number of ingress messages to locally buffer per topic. Default is
+# unlimited. (integer value)
+#rpc_zmq_topic_backlog = <None>
+
+# Directory for holding IPC sockets. (string value)
+#rpc_zmq_ipc_dir = /var/run/openstack
+
+# Name of this node. Must be a valid hostname, FQDN, or IP address. Must match
+# "host" option, if running Nova. (string value)
+#rpc_zmq_host = localhost
+
+# Number of seconds to wait before all pending messages will be sent after
+# closing a socket. The default value of -1 specifies an infinite linger period.
+# The value of 0 specifies no linger period. Pending messages shall be discarded
+# immediately when the socket is closed. Positive values specify an upper bound
+# for the linger period. (integer value)
+# Deprecated group/name - [DEFAULT]/rpc_cast_timeout
+#zmq_linger = -1
+
+# The default number of seconds that poll should wait. Poll raises timeout
+# exception when timeout expired. (integer value)
+#rpc_poll_timeout = 1
+
+# Expiration timeout in seconds of a name service record about existing target (
+# < 0 means no timeout). (integer value)
+#zmq_target_expire = 300
+
+# Update period in seconds of a name service record about existing target.
+# (integer value)
+#zmq_target_update = 180
+
+# Use PUB/SUB pattern for fanout methods. PUB/SUB always uses proxy. (boolean
+# value)
+#use_pub_sub = false
+
+# Use ROUTER remote proxy. (boolean value)
+#use_router_proxy = false
+
+# This option makes direct connections dynamic or static. It makes sense only
+# with use_router_proxy=False which means to use direct connections for direct
+# message types (ignored otherwise). (boolean value)
+#use_dynamic_connections = false
+
+# How many additional connections to a host will be made for failover reasons.
+# This option is actual only in dynamic connections mode. (integer value)
+#zmq_failover_connections = 2
+
+# Minimal port number for random ports range. (port value)
+# Minimum value: 0
+# Maximum value: 65535
+#rpc_zmq_min_port = 49153
+
+# Maximal port number for random ports range. (integer value)
+# Minimum value: 1
+# Maximum value: 65536
+#rpc_zmq_max_port = 65536
+
+# Number of retries to find free port number before fail with ZMQBindError.
+# (integer value)
+#rpc_zmq_bind_port_retries = 100
+
+# Default serialization mechanism for serializing/deserializing
+# outgoing/incoming messages (string value)
+# Possible values:
+# json - <No description provided>
+# msgpack - <No description provided>
+#rpc_zmq_serialization = json
+
+# This option configures round-robin mode in zmq socket. True means not keeping
+# a queue when server side disconnects. False means to keep queue and messages
+# even if server is disconnected, when the server appears we send all
+# accumulated messages to it. (boolean value)
+#zmq_immediate = true
+
+# Enable/disable TCP keepalive (KA) mechanism. The default value of -1 (or any
+# other negative value) means to skip any overrides and leave it to OS default;
+# 0 and 1 (or any other positive value) mean to disable and enable the option
+# respectively. (integer value)
+#zmq_tcp_keepalive = -1
+
+# The duration between two keepalive transmissions in idle condition. The unit
+# is platform dependent, for example, seconds in Linux, milliseconds in Windows
+# etc. The default value of -1 (or any other negative value and 0) means to skip
+# any overrides and leave it to OS default. (integer value)
+#zmq_tcp_keepalive_idle = -1
+
+# The number of retransmissions to be carried out before declaring that remote
+# end is not available. The default value of -1 (or any other negative value and
+# 0) means to skip any overrides and leave it to OS default. (integer value)
+#zmq_tcp_keepalive_cnt = -1
+
+# The duration between two successive keepalive retransmissions, if
+# acknowledgement to the previous keepalive transmission is not received. The
+# unit is platform dependent, for example, seconds in Linux, milliseconds in
+# Windows etc. The default value of -1 (or any other negative value and 0) means
+# to skip any overrides and leave it to OS default. (integer value)
+#zmq_tcp_keepalive_intvl = -1
+
+# Maximum number of (green) threads to work concurrently. (integer value)
+#rpc_thread_pool_size = 100
+
+# Expiration timeout in seconds of a sent/received message after which it is not
+# tracked anymore by a client/server. (integer value)
+#rpc_message_ttl = 300
+
+# Wait for message acknowledgements from receivers. This mechanism works only
+# via proxy without PUB/SUB. (boolean value)
+#rpc_use_acks = false
+
+# Number of seconds to wait for an ack from a cast/call. After each retry
+# attempt this timeout is multiplied by some specified multiplier. (integer
+# value)
+#rpc_ack_timeout_base = 15
+
+# Number to multiply base ack timeout by after each retry attempt. (integer
+# value)
+#rpc_ack_timeout_multiplier = 2
+
+# Default number of message sending attempts in case of any problems occurred:
+# positive value N means at most N retries, 0 means no retries, None or -1 (or
+# any other negative values) mean to retry forever. This option is used only if
+# acknowledgments are enabled. (integer value)
+#rpc_retry_attempts = 3
+
+# List of publisher hosts SubConsumer can subscribe on. This option has higher
+# priority then the default publishers list taken from the matchmaker. (list
+# value)
+#subscribe_on =
+
+
+[oslo_middleware]
+
+#
+# From oslo.middleware
+#
+
+# The maximum body size for each request, in bytes. (integer value)
+# Deprecated group/name - [DEFAULT]/osapi_max_request_body_size
+# Deprecated group/name - [DEFAULT]/max_request_body_size
+#max_request_body_size = 114688
+
+# DEPRECATED: The HTTP Header that will be used to determine what the original
+# request protocol scheme was, even if it was hidden by a SSL termination proxy.
+# (string value)
+# This option is deprecated for removal.
+# Its value may be silently ignored in the future.
+#secure_proxy_ssl_header = X-Forwarded-Proto
+
+# Whether the application is behind a proxy or not. This determines if the
+# middleware should parse the headers or not. (boolean value)
+#enable_proxy_headers_parsing = false
+
+
+[oslo_policy]
+
+#
+# From oslo.policy
+#
+
+# This option controls whether or not to enforce scope when evaluating policies.
+# If ``True``, the scope of the token used in the request is compared to the
+# ``scope_types`` of the policy being enforced. If the scopes do not match, an
+# ``InvalidScope`` exception will be raised. If ``False``, a message will be
+# logged informing operators that policies are being invoked with mismatching
+# scope. (boolean value)
+#enforce_scope = false
+
+# The file that defines policies. (string value)
+#policy_file = policy.json
+
+# Default rule. Enforced when a requested rule is not found. (string value)
+#policy_default_rule = default
+
+# Directories where policy configuration files are stored. They can be relative
+# to any directory in the search path defined by the config_dir option, or
+# absolute paths. The file defined by policy_file must exist for these
+# directories to be searched. Missing or empty directories are ignored. (multi
+# valued)
+#policy_dirs = policy.d
+
+# Content Type to send and receive data for REST based policy check (string
+# value)
+# Possible values:
+# application/x-www-form-urlencoded - <No description provided>
+# application/json - <No description provided>
+#remote_content_type = application/x-www-form-urlencoded
+
+# server identity verification for REST based policy check (boolean value)
+#remote_ssl_verify_server_crt = false
+
+# Absolute path to ca cert file for REST based policy check (string value)
+#remote_ssl_ca_crt_file = <None>
+
+# Absolute path to client cert for REST based policy check (string value)
+#remote_ssl_client_crt_file = <None>
+
+# Absolute path client key file REST based policy check (string value)
+#remote_ssl_client_key_file = <None>
+
+
+[profiler]
+
+#
+# From osprofiler
+#
+
+#
+# Enable the profiling for all services on this node.
+#
+# Default value is False (fully disable the profiling feature).
+#
+# Possible values:
+#
+# * True: Enables the feature
+# * False: Disables the feature. The profiling cannot be started via this
+# project
+# operations. If the profiling is triggered by another project, this project
+# part will be empty.
+# (boolean value)
+# Deprecated group/name - [profiler]/profiler_enabled
+#enabled = false
+
+#
+# Enable SQL requests profiling in services.
+#
+# Default value is False (SQL requests won't be traced).
+#
+# Possible values:
+#
+# * True: Enables SQL requests profiling. Each SQL query will be part of the
+# trace and can the be analyzed by how much time was spent for that.
+# * False: Disables SQL requests profiling. The spent time is only shown on a
+# higher level of operations. Single SQL queries cannot be analyzed this way.
+# (boolean value)
+#trace_sqlalchemy = false
+
+#
+# Secret key(s) to use for encrypting context data for performance profiling.
+#
+# This string value should have the following format: <key1>[,<key2>,...<keyn>],
+# where each key is some random string. A user who triggers the profiling via
+# the REST API has to set one of these keys in the headers of the REST API call
+# to include profiling results of this node for this particular project.
+#
+# Both "enabled" flag and "hmac_keys" config options should be set to enable
+# profiling. Also, to generate correct profiling information across all services
+# at least one key needs to be consistent between OpenStack projects. This
+# ensures it can be used from client side to generate the trace, containing
+# information from all possible resources.
+# (string value)
+#hmac_keys = SECRET_KEY
+
+#
+# Connection string for a notifier backend.
+#
+# Default value is ``messaging://`` which sets the notifier to oslo_messaging.
+#
+# Examples of possible values:
+#
+# * ``messaging://`` - use oslo_messaging driver for sending spans.
+# * ``redis://127.0.0.1:6379`` - use redis driver for sending spans.
+# * ``mongodb://127.0.0.1:27017`` - use mongodb driver for sending spans.
+# * ``elasticsearch://127.0.0.1:9200`` - use elasticsearch driver for sending
+# spans.
+# * ``jaeger://127.0.0.1:6831`` - use jaeger tracing as driver for sending
+# spans.
+# (string value)
+#connection_string = messaging://
+
+#
+# Document type for notification indexing in elasticsearch.
+# (string value)
+#es_doc_type = notification
+
+#
+# This parameter is a time value parameter (for example: es_scroll_time=2m),
+# indicating for how long the nodes that participate in the search will maintain
+# relevant resources in order to continue and support it.
+# (string value)
+#es_scroll_time = 2m
+
+#
+# Elasticsearch splits large requests in batches. This parameter defines
+# maximum size of each batch (for example: es_scroll_size=10000).
+# (integer value)
+#es_scroll_size = 10000
+
+#
+# Redissentinel provides a timeout option on the connections.
+# This parameter defines that timeout (for example: socket_timeout=0.1).
+# (floating point value)
+#socket_timeout = 0.1
+
+#
+# Redissentinel uses a service name to identify a master redis service.
+# This parameter defines the name (for example:
+# ``sentinal_service_name=mymaster``).
+# (string value)
+#sentinel_service_name = mymaster
+
+#
+# Enable filter traces that contain error/exception to a separated place.
+#
+# Default value is set to False.
+#
+# Possible values:
+#
+# * True: Enable filter traces that contain error/exception.
+# * False: Disable the filter.
+# (boolean value)
+#filter_error_trace = false
+
+
+[remote_debug]
+
+#
+# From nova.conf
+#
+
+#
+# Debug host (IP or name) to connect to. This command line parameter is used
+# when
+# you want to connect to a nova service via a debugger running on a different
+# host.
+#
+# Note that using the remote debug option changes how Nova uses the eventlet
+# library to support async IO. This could result in failures that do not occur
+# under normal operation. Use at your own risk.
+#
+# Possible Values:
+#
+# * IP address of a remote host as a command line parameter
+# to a nova service. For Example:
+#
+# /usr/local/bin/nova-compute --config-file /etc/nova/nova.conf
+# --remote_debug-host <IP address where the debugger is running>
+# (host address value)
+#host = <None>
+
+#
+# Debug port to connect to. This command line parameter allows you to specify
+# the port you want to use to connect to a nova service via a debugger running
+# on different host.
+#
+# Note that using the remote debug option changes how Nova uses the eventlet
+# library to support async IO. This could result in failures that do not occur
+# under normal operation. Use at your own risk.
+#
+# Possible Values:
+#
+# * Port number you want to use as a command line parameter
+# to a nova service. For Example:
+#
+# /usr/local/bin/nova-compute --config-file /etc/nova/nova.conf
+# --remote_debug-host <IP address where the debugger is running>
+# --remote_debug-port <port> it's listening on>.
+# (port value)
+# Minimum value: 0
+# Maximum value: 65535
+#port = <None>
+
+
+[scheduler]
+
+#
+# From nova.conf
+#
+
+baseproject = gosbsbase
+
+#
+# The class of the driver used by the scheduler. This should be chosen from one
+# of the entrypoints under the namespace 'nova.scheduler.driver' of file
+# 'setup.cfg'. If nothing is specified in this option, the 'filter_scheduler' is
+# used.
+#
+# Other options are:
+#
+# * 'caching_scheduler' which aggressively caches the system state for better
+# individual scheduler performance at the risk of more retries when running
+# multiple schedulers. [DEPRECATED]
+# * 'chance_scheduler' which simply picks a host at random. [DEPRECATED]
+# * 'fake_scheduler' which is used for testing.
+#
+# Possible values:
+#
+# * Any of the drivers included in Nova:
+#
+# * filter_scheduler
+# * caching_scheduler
+# * chance_scheduler
+# * fake_scheduler
+#
+# * You may also set this to the entry point name of a custom scheduler driver,
+# but you will be responsible for creating and maintaining it in your
+# setup.cfg
+# file.
+#
+# Related options:
+#
+# * workers
+# (string value)
+# Deprecated group/name - [DEFAULT]/scheduler_driver
+#driver = filter_scheduler
+
+#
+# Periodic task interval.
+#
+# This value controls how often (in seconds) to run periodic tasks in the
+# scheduler. The specific tasks that are run for each period are determined by
+# the particular scheduler being used. Currently the only in-tree scheduler
+# driver that uses this option is the ``caching_scheduler``.
+#
+# If this is larger than the nova-service 'service_down_time' setting, the
+# ComputeFilter (if enabled) may think the compute service is down. As each
+# scheduler can work a little differently than the others, be sure to test this
+# with your selected scheduler.
+#
+# Possible values:
+#
+# * An integer, where the integer corresponds to periodic task interval in
+# seconds. 0 uses the default interval (60 seconds). A negative value disables
+# periodic tasks.
+#
+# Related options:
+#
+# * ``nova-service service_down_time``
+# (integer value)
+#periodic_task_interval = 60
+
+#
+# This is the maximum number of attempts that will be made for a given instance
+# build/move operation. It limits the number of alternate hosts returned by the
+# scheduler. When that list of hosts is exhausted, a MaxRetriesExceeded
+# exception is raised and the instance is set to an error state.
+#
+# Possible values:
+#
+# * A positive integer, where the integer corresponds to the max number of
+# attempts that can be made when building or moving an instance.
+# (integer value)
+# Minimum value: 1
+# Deprecated group/name - [DEFAULT]/scheduler_max_attempts
+#max_attempts = 3
+
+#
+# Periodic task interval.
+#
+# This value controls how often (in seconds) the scheduler should attempt
+# to discover new hosts that have been added to cells. If negative (the
+# default), no automatic discovery will occur.
+#
+# Deployments where compute nodes come and go frequently may want this
+# enabled, where others may prefer to manually discover hosts when one
+# is added to avoid any overhead from constantly checking. If enabled,
+# every time this runs, we will select any unmapped hosts out of each
+# cell database on every run.
+# (integer value)
+# Minimum value: -1
+#discover_hosts_in_cells_interval = -1
+
+#
+# This setting determines the maximum limit on results received from the
+# placement service during a scheduling operation. It effectively limits
+# the number of hosts that may be considered for scheduling requests that
+# match a large number of candidates.
+#
+# A value of 1 (the minimum) will effectively defer scheduling to the placement
+# service strictly on "will it fit" grounds. A higher value will put an upper
+# cap on the number of results the scheduler will consider during the filtering
+# and weighing process. Large deployments may need to set this lower than the
+# total number of hosts available to limit memory consumption, network traffic,
+# etc. of the scheduler.
+#
+# This option is only used by the FilterScheduler; if you use a different
+# scheduler, this option has no effect.
+# (integer value)
+# Minimum value: 1
+#max_placement_results = 1000
+
+#
+# Number of workers for the nova-scheduler service. The default will be the
+# number of CPUs available if using the "filter_scheduler" scheduler driver,
+# otherwise the default will be 1.
+# (integer value)
+# Minimum value: 0
+#workers = <None>
+
+#
+# This setting causes the scheduler to look up a host aggregate with the
+# metadata key of `filter_tenant_id` set to the project of an incoming
+# request, and request results from placement be limited to that aggregate.
+# Multiple tenants may be added to a single aggregate by appending a serial
+# number to the key, such as `filter_tenant_id:123`.
+#
+# The matching aggregate UUID must be mirrored in placement for proper
+# operation. If no host aggregate with the tenant id is found, or that
+# aggregate does not match one in placement, the result will be the same
+# as not finding any suitable hosts for the request.
+#
+# See also the placement_aggregate_required_for_tenants option.
+# (boolean value)
+#limit_tenants_to_placement_aggregate = false
+
+#
+# This setting, when limit_tenants_to_placement_aggregate=True, will control
+# whether or not a tenant with no aggregate affinity will be allowed to schedule
+# to any available node. If aggregates are used to limit some tenants but
+# not all, then this should be False. If all tenants should be confined via
+# aggregate, then this should be True to prevent them from receiving
+# unrestricted
+# scheduling to any available node.
+#
+# See also the limit_tenants_to_placement_aggregate option.
+# (boolean value)
+#placement_aggregate_required_for_tenants = false
+
+#
+# This setting causes the scheduler to look up a host aggregate with the
+# metadata key of `availability_zone` set to the value provided by an
+# incoming request, and request results from placement be limited to that
+# aggregate.
+#
+# The matching aggregate UUID must be mirrored in placement for proper
+# operation. If no host aggregate with the `availability_zone` key is
+# found, or that aggregate does not match one in placement, the result will
+# be the same as not finding any suitable hosts.
+#
+# Note that if you enable this flag, you can disable the (less efficient)
+# AvailabilityZoneFilter in the scheduler.
+# (boolean value)
+#query_placement_for_availability_zone = false
+
+
+[upgrade_levels]
+#
+# upgrade_levels options are used to set version cap for RPC
+# messages sent between different nova services.
+#
+# By default all services send messages using the latest version
+# they know about.
+#
+# The compute upgrade level is an important part of rolling upgrades
+# where old and new nova-compute services run side by side.
+#
+# The other options can largely be ignored, and are only kept to
+# help with a possible future backport issue.
+
+#
+# From nova.conf
+#
+
+#
+# Compute RPC API version cap.
+#
+# By default, we always send messages using the most recent version
+# the client knows about.
+#
+# Where you have old and new compute services running, you should set
+# this to the lowest deployed version. This is to guarantee that all
+# services never send messages that one of the compute nodes can't
+# understand. Note that we only support upgrading from release N to
+# release N+1.
+#
+# Set this option to "auto" if you want to let the compute RPC module
+# automatically determine what version to use based on the service
+# versions in the deployment.
+#
+# Possible values:
+#
+# * By default send the latest version the client knows about
+# * 'auto': Automatically determines what version to use based on
+# the service versions in the deployment.
+# * A string representing a version number in the format 'N.N';
+# for example, possible values might be '1.12' or '2.0'.
+# * An OpenStack release name, in lower case, such as 'mitaka' or
+# 'liberty'.
+# (string value)
+#compute = <None>
+
+#
+# Cells RPC API version cap.
+#
+# Possible values:
+#
+# * By default send the latest version the client knows about
+# * A string representing a version number in the format 'N.N';
+# for example, possible values might be '1.12' or '2.0'.
+# * An OpenStack release name, in lower case, such as 'mitaka' or
+# 'liberty'.
+# (string value)
+#cells = <None>
+
+#
+# Intercell RPC API version cap.
+#
+# Possible values:
+#
+# * By default send the latest version the client knows about
+# * A string representing a version number in the format 'N.N';
+# for example, possible values might be '1.12' or '2.0'.
+# * An OpenStack release name, in lower case, such as 'mitaka' or
+# 'liberty'.
+# (string value)
+#intercell = <None>
+
+# DEPRECATED:
+# Cert RPC API version cap.
+#
+# Possible values:
+#
+# * By default send the latest version the client knows about
+# * A string representing a version number in the format 'N.N';
+# for example, possible values might be '1.12' or '2.0'.
+# * An OpenStack release name, in lower case, such as 'mitaka' or
+# 'liberty'.
+# (string value)
+# This option is deprecated for removal since 18.0.0.
+# Its value may be silently ignored in the future.
+# Reason:
+# The nova-cert service was removed in 16.0.0 (Pike) so this option
+# is no longer used.
+#cert = <None>
+
+#
+# Scheduler RPC API version cap.
+#
+# Possible values:
+#
+# * By default send the latest version the client knows about
+# * A string representing a version number in the format 'N.N';
+# for example, possible values might be '1.12' or '2.0'.
+# * An OpenStack release name, in lower case, such as 'mitaka' or
+# 'liberty'.
+# (string value)
+#scheduler = <None>
+
+#
+# Conductor RPC API version cap.
+#
+# Possible values:
+#
+# * By default send the latest version the client knows about
+# * A string representing a version number in the format 'N.N';
+# for example, possible values might be '1.12' or '2.0'.
+# * An OpenStack release name, in lower case, such as 'mitaka' or
+# 'liberty'.
+# (string value)
+#conductor = <None>
+
+#
+# Console RPC API version cap.
+#
+# Possible values:
+#
+# * By default send the latest version the client knows about
+# * A string representing a version number in the format 'N.N';
+# for example, possible values might be '1.12' or '2.0'.
+# * An OpenStack release name, in lower case, such as 'mitaka' or
+# 'liberty'.
+# (string value)
+#console = <None>
+
+# DEPRECATED:
+# Consoleauth RPC API version cap.
+#
+# Possible values:
+#
+# * By default send the latest version the client knows about
+# * A string representing a version number in the format 'N.N';
+# for example, possible values might be '1.12' or '2.0'.
+# * An OpenStack release name, in lower case, such as 'mitaka' or
+# 'liberty'.
+# (string value)
+# This option is deprecated for removal since 18.0.0.
+# Its value may be silently ignored in the future.
+# Reason:
+# The nova-consoleauth service was deprecated in 18.0.0 (Rocky) and will be
+# removed in an upcoming release.
+#consoleauth = <None>
+
+# DEPRECATED:
+# Network RPC API version cap.
+#
+# Possible values:
+#
+# * By default send the latest version the client knows about
+# * A string representing a version number in the format 'N.N';
+# for example, possible values might be '1.12' or '2.0'.
+# * An OpenStack release name, in lower case, such as 'mitaka' or
+# 'liberty'.
+# (string value)
+# This option is deprecated for removal since 18.0.0.
+# Its value may be silently ignored in the future.
+# Reason:
+# The nova-network service was deprecated in 14.0.0 (Newton) and will be
+# removed in an upcoming release.
+#network = <None>
+
+#
+# Base API RPC API version cap.
+#
+# Possible values:
+#
+# * By default send the latest version the client knows about
+# * A string representing a version number in the format 'N.N';
+# for example, possible values might be '1.12' or '2.0'.
+# * An OpenStack release name, in lower case, such as 'mitaka' or
+# 'liberty'.
+# (string value)
+#baseapi = <None>
diff --git a/gosbs/__init__.py b/gosbs/__init__.py
new file mode 100644
index 0000000..228c89d
--- /dev/null
+++ b/gosbs/__init__.py
@@ -0,0 +1,35 @@
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+:mod:`nova` -- Cloud IaaS Platform
+===================================
+
+.. automodule:: nova
+ :platform: Unix
+ :synopsis: Infrastructure-as-a-Service Cloud platform.
+"""
+
+import os
+
+os.environ['EVENTLET_NO_GREENDNS'] = 'yes'
+
+# NOTE(rpodolyaka): import oslo_service first, so that it makes eventlet hub
+# use a monotonic clock to avoid issues with drifts of system time (see
+# LP 1510234 for details)
+import oslo_service # noqa
+
+import eventlet # noqa
diff --git a/gosbs/baserpc.py b/gosbs/baserpc.py
new file mode 100644
index 0000000..b57f44c
--- /dev/null
+++ b/gosbs/baserpc.py
@@ -0,0 +1,81 @@
+#
+# Copyright 2013 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+# Origin https://github.com/openstack/nova/blob/master/nova/baserpc.py
+
+"""
+Base RPC client and server common to all services.
+"""
+
+import oslo_messaging as messaging
+from oslo_serialization import jsonutils
+
+import gosbs.conf
+from gosbs import rpc
+
+
+CONF = gosbs.conf.CONF
+
+_NAMESPACE = 'baseapi'
+
+
+class BaseAPI(object):
+ """Client side of the base rpc API.
+
+ API version history:
+
+ 1.0 - Initial version.
+ 1.1 - Add get_backdoor_port
+ """
+
+ VERSION_ALIASES = {
+ # baseapi was added in havana
+ }
+
+ def __init__(self, topic):
+ super(BaseAPI, self).__init__()
+ target = messaging.Target(topic=topic,
+ namespace=_NAMESPACE,
+ version='1.0')
+ version_cap = self.VERSION_ALIASES.get(CONF.upgrade_levels.baseapi,
+ CONF.upgrade_levels.baseapi)
+ self.client = rpc.get_client(target, version_cap=version_cap)
+
+ def ping(self, context, arg, timeout=None):
+ arg_p = jsonutils.to_primitive(arg)
+ cctxt = self.client.prepare(timeout=timeout)
+ return cctxt.call(context, 'ping', arg=arg_p)
+
+ def get_backdoor_port(self, context, host):
+ cctxt = self.client.prepare(server=host, version='1.1')
+ return cctxt.call(context, 'get_backdoor_port')
+
+
+class BaseRPCAPI(object):
+ """Server side of the base RPC API."""
+
+ target = messaging.Target(namespace=_NAMESPACE, version='1.1')
+
+ def __init__(self, service_name, backdoor_port):
+ self.service_name = service_name
+ self.backdoor_port = backdoor_port
+
+ def ping(self, context, arg):
+ resp = {'service': self.service_name, 'arg': arg}
+ return jsonutils.to_primitive(resp)
+
+ def get_backdoor_port(self, context):
+ return self.backdoor_port
diff --git a/gosbs/cmd/__init__.py b/gosbs/cmd/__init__.py
new file mode 100644
index 0000000..7970311
--- /dev/null
+++ b/gosbs/cmd/__init__.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/cmd/__init__.py
+
+from gosbs import utils
+
+utils.monkey_patch()
diff --git a/gosbs/cmd/scheduler.py b/gosbs/cmd/scheduler.py
new file mode 100644
index 0000000..db5d612
--- /dev/null
+++ b/gosbs/cmd/scheduler.py
@@ -0,0 +1,45 @@
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/cmd/compute.py
+# Removed code that was not needed
+
+"""Starter script for Gosbs Scheduler."""
+
+import shlex
+import sys
+
+from oslo_log import log as logging
+
+from gosbs.scheduler import rpcapi as scheduler_rpcapi
+import gosbs.conf
+from gosbs import config
+from gosbs import objects
+from gosbs.objects import base as objects_base
+from gosbs import service
+
+CONF = gosbs.conf.CONF
+
+def main():
+ config.parse_args(sys.argv)
+ logging.setup(CONF, 'gosbs')
+ objects.register_all()
+
+ objects.Service.enable_min_version_cache()
+ server = service.Service.create(binary='gosbs-scheduler',
+ topic=scheduler_rpcapi.RPC_TOPIC)
+ service.serve(server)
+ service.wait()
diff --git a/pym/tbc/__init__.py b/gosbs/common/__init__.py
index e69de29..e69de29 100644
--- a/pym/tbc/__init__.py
+++ b/gosbs/common/__init__.py
diff --git a/gosbs/common/flags.py b/gosbs/common/flags.py
new file mode 100644
index 0000000..2e844f0
--- /dev/null
+++ b/gosbs/common/flags.py
@@ -0,0 +1,211 @@
+# Copyright 1999-2020 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+# Origin https://gitweb.gentoo.org/proj/portage.git/tree/pym/portage/api/flag.py?h=public_api
+# Fix so we can use mysettings and myportdb.
+# Add filtring of api, python and ruby.
+
+"""Provides support functions for USE flag settings and analysis"""
+
+
+__all__ = (
+ 'get_iuse',
+ 'get_installed_use',
+ 'reduce_flag',
+ 'reduce_flags',
+ 'filter_flags',
+ 'get_all_cpv_use',
+ 'get_flags'
+)
+
+import portage
+
+
+def get_iuse(cpv, portdb):
+ """Gets the current IUSE flags from the tree
+
+ To be used when a gentoolkit package object is not needed
+ @type: cpv: string
+ @param cpv: cat/pkg-ver
+ @type root: string
+ @param root: tree root to use
+ @param settings: optional portage config settings instance.
+ defaults to portage.api.settings.default_settings
+ @rtype list
+ @returns [] or the list of IUSE flags
+ """
+ return portdb.aux_get(cpv, ["IUSE"])[0].split()
+
+
+def reduce_flag(flag):
+ """Absolute value function for a USE flag
+
+ @type flag: string
+ @param flag: the use flag to absolute.
+ @rtype: string
+ @return absolute USE flag
+ """
+ if flag[0] in ["+","-"]:
+ return flag[1:]
+ else:
+ return flag
+
+
+def reduce_flags(the_list):
+ """Absolute value function for a USE flag list
+
+ @type the_list: list
+ @param the_list: the use flags to absolute.
+ @rtype: list
+ @return absolute USE flags
+ """
+ reduced = []
+ for member in the_list:
+ reduced.append(reduce_flag(member))
+ return reduced
+
+
+def filter_flags(use, use_expand_hidden, usemasked,
+ useforced, settings):
+ """Filter function to remove hidden or otherwise not normally
+ visible USE flags from a list.
+
+ @type use: list
+ @param use: the USE flag list to be filtered.
+ @type use_expand_hidden: list
+ @param use_expand_hidden: list of flags hidden.
+ @type usemasked: list
+ @param usemasked: list of masked USE flags.
+ @type useforced: list
+ @param useforced: the forced USE flags.
+ @param settings: optional portage config settings instance.
+ defaults to portage.api.settings.default_settings
+ @rtype: list
+ @return the filtered USE flags.
+ """
+ # clean out some environment flags, since they will most probably
+ # be confusing for the user
+ for flag in use_expand_hidden:
+ flag = flag.lower() + "_"
+ for expander in use:
+ if flag in expander:
+ use.remove(expander)
+ # clean out any arch's
+ archlist = settings["PORTAGE_ARCHLIST"].split()
+ for key in use[:]:
+ if key in archlist:
+ use.remove(key)
+ # dbl check if any from usemasked or useforced are still there
+ masked = usemasked + useforced
+ for flag in use[:]:
+ if flag in masked:
+ use.remove(flag)
+ # clean out any abi_ flag
+ for a in use[:]:
+ if a.startswith("abi_"):
+ use.remove(a)
+ # clean out any python_ flag
+ for a in use[:]:
+ if a.startswith("python_"):
+ use.remove(a)
+ # clean out any ruby_targets_ flag
+ for a in use[:]:
+ if a.startswith("ruby_targets_"):
+ use.remove(a)
+ return use
+
+
+def get_all_cpv_use(cpv, portdb, settings):
+ """Uses portage to determine final USE flags and settings for an emerge
+
+ @type cpv: string
+ @param cpv: eg cat/pkg-ver
+ @type root: string
+ @param root: tree root to use
+ @param settings: optional portage config settings instance.
+ defaults to portage.api.settings.default_settings
+ @rtype: lists
+ @return use, use_expand_hidden, usemask, useforce
+ """
+ use = None
+ settings.unlock()
+ try:
+ settings.setcpv(cpv, use_cache=None, mydb=portdb)
+ use = settings['PORTAGE_USE'].split()
+ use_expand_hidden = settings["USE_EXPAND_HIDDEN"].split()
+ usemask = list(settings.usemask)
+ useforce = list(settings.useforce)
+ except KeyError:
+ settings.reset()
+ settings.lock()
+ return [], [], [], []
+ # reset cpv filter
+ settings.reset()
+ settings.lock()
+ return use, use_expand_hidden, usemask, useforce
+
+
+def get_flags(cpv, portdb, settings, final_setting=False):
+ """Retrieves all information needed to filter out hidden, masked, etc.
+ USE flags for a given package.
+
+ @type cpv: string
+ @param cpv: eg. cat/pkg-ver
+ @type final_setting: boolean
+ @param final_setting: used to also determine the final
+ enviroment USE flag settings and return them as well.
+ @type root: string
+ @param root: pass through variable needed, tree root to use
+ for other function calls.
+ @param settings: optional portage config settings instance.
+ defaults to portage.api.settings.default_settings
+ @rtype: list or list, list
+ @return IUSE or IUSE, final_flags
+ """
+ (final_use, use_expand_hidden, usemasked, useforced) = \
+ get_all_cpv_use(cpv, portdb, settings)
+ iuse_flags = filter_flags(get_iuse(cpv), use_expand_hidden,
+ usemasked, useforced, settings)
+ #flags = filter_flags(use_flags, use_expand_hidden,
+ #usemasked, useforced, settings)
+ if final_setting:
+ final_flags = filter_flags(final_use, use_expand_hidden,
+ usemasked, useforced, settings)
+ return iuse_flags, final_flags
+ return iuse_flags
+
+
+def get_use_flag_dict(portdir):
+ """ Get all the use flags and return them as a dictionary
+
+ @param portdir: the path to the repository
+ @rtype dictionary of:
+ key = use flag forced to lowercase
+ data = list[0] = 'local' or 'global'
+ list[1] = 'package-name'
+ list[2] = description of flag
+ """
+ use_dict = {}
+
+ # process standard use flags
+
+ _list = portage.grabfile(portdir + '/profiles/use.desc')
+ for item in _list:
+ index = item.find(' - ')
+ use_dict[item[:index].strip().lower()] = ['global', '', item[index+3:]]
+
+ # process local (package specific) use flags
+
+ _list = portage.grabfile(portdir + '/profiles/use.local.desc')
+ for item in _list:
+ index = item.find(' - ')
+ data = item[:index].lower().split(':')
+ try:
+ use_dict[data[1].strip()] = ['local', data[0].strip(), item[index+3:]]
+ except:
+ pass
+ #debug.dprint("FLAG: get_use_flag_dict();"
+ #"error in index??? data[0].strip, item[index:]")
+ #debug.dprint(data[0].strip())
+ #debug.dprint(item[index:])
+ return use_dict
diff --git a/gosbs/common/git.py b/gosbs/common/git.py
new file mode 100644
index 0000000..b22435d
--- /dev/null
+++ b/gosbs/common/git.py
@@ -0,0 +1,99 @@
+# Copyright 1999-2020 Gentoo Authors
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import re
+import git
+import os
+
+from oslo_log import log as logging
+import gosbs.conf
+
+CONF = gosbs.conf.CONF
+LOG = logging.getLogger(__name__)
+
+def fetch(repo):
+ remote = git.remote.Remote(repo, 'origin')
+ info_list = remote.fetch()
+ local_commit = repo.commit()
+ remote_commit = info_list[0].commit
+ if local_commit.hexsha != remote_commit.hexsha:
+ return info_list, False
+ return info_list, True
+
+def merge(repo, info):
+ repo.git.merge(info.commit)
+
+def update_git_repo_db(repo_dict):
+ # check git diffs witch get updated and pass that to objects Packages
+ # fetch and merge the repo
+ search_list = [ '^metadata', '^eclass', '^licenses', '^profiles', '^scripts', '^skel.', '^header.txt']
+ repo = git.Repo(repo_dict['repo_path'])
+ cp_list = []
+ info_list, repo_uptodate = fetch(repo)
+ if repo_uptodate:
+ return True, cp_list
+ # We check for dir changes and add the package to a list
+ repo_diff = repo.git.diff('origin', '--name-only'
+ #write_log(session, 'Git dir diff:\n%s' % (repo_diff,), "debug", config_id, 'sync.git_sync_main')
+ for diff_line in repo_diff.splitlines():
+ find_search = True
+ for search_line in search_list:
+ if re.search(search_line, diff_line):
+ find_search = False
+ if find_search:
+ splited_diff_line = re.split('/', diff_line)
+ c = splited_diff_line[0]
+ p = splited_diff_line[1]
+ cp = c + '/' + p
+ if not cp in cp_list:
+ cp_list.append(cp)
+ #write_log(session, 'Git CP Diff: %s' % (cp_list,), "debug", config_id, 'sync.git_sync_main')
+ merge(repo, info_list[0])
+ return True, cp_list
+
+def update_git_repo(repo_dict):
+ repo = git.Repo(repo_dict['repo_path'])
+ try:
+ repo.git.pull()
+ except:
+ return False
+ return True
+
+def create_git_repo(repo_dict):
+ try:
+ os.mkdir(repo_dict['repo_path'])
+ except OSError:
+ LOG.error("Creation of the directory %s failed" % repo_dict['repo_path'])
+ return False
+ try:
+ if not repo_dict['history']:
+ git.Repo.clone_from(repo_dict['repo_url'], repo_dict['repo_path'], 'depth=1')
+ else:
+ git.Repo.clone_from(repo_dict['repo_url'], repo_dict['repo_path'],)
+ except:
+ return False
+ return True
+
+def check_git_repo_db(repo_dict):
+ if not os.path.isdir(repo_dict['repo_path']):
+ succes = create_git_repo(repo_dict)
+ return succes, None
+ succes, cp_list = update_git_repo_db(repo_dict)
+ return succes, cp_list
+
+def check_git_repo(repo_dict):
+ if not os.path.isdir(repo_dict['repo_path']):
+ succes = create_git_repo(repo_dict)
+ else:
+ succes = update_git_repo(repo_dict)
+ return succes
diff --git a/gosbs/common/portage_settings.py b/gosbs/common/portage_settings.py
new file mode 100644
index 0000000..e5a58f5
--- /dev/null
+++ b/gosbs/common/portage_settings.py
@@ -0,0 +1,48 @@
+# Copyright 1999-2020 Gentoo Authors
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import os
+from pathlib import Path
+
+import portage
+
+from oslo_log import log as logging
+
+from gosbs import objects
+import gosbs.conf
+
+CONF = gosbs.conf.CONF
+LOG = logging.getLogger(__name__)
+
+def check_profile(context, project_repopath, project_metadata_db):
+ profile_repo_db = objects.repo.Repo.get_by_uuid(context, project_metadata_db.project_profile_repo_uuid)
+ profile_repopath = CONF.repopath + '/' + profile_repo_db.name + '.git/' + 'profiles/'
+ if not Path(project_repopath + 'etc/portage/make.profile').is_symlink():
+ Path(project_repopath + 'etc/portage/make.profile').symlink_to(profile_repopath + project_metadata_db.project_profile)
+ else:
+ if Path(project_repopath + 'etc/portage/make.profile').resolve() != project_repopath + project_metadata_db.project_profile:
+ pass
+
+def get_portage_settings(context, project_metadata_db, project_repo_name):
+ settings_repo_db = objects.repo.Repo.get_by_uuid(context, project_metadata_db.project_repo_uuid)
+ project_repopath = CONF.repopath + '/' + settings_repo_db.name + '.git/' + project_repo_name + '/'
+ if Path(project_repopath).exists():
+ check_profile(context, project_repopath, project_metadata_db)
+ # Set config_root (PORTAGE_CONFIGROOT) to project_repopath
+ mysettings = portage.config(config_root = project_repopath)
+ myportdb = portage.portdbapi(mysettings=mysettings)
+ return mysettings, myportdb
+
+def clean_portage_settings(myportdb):
+ myportdb.close_caches()
+ portage.portdbapi.portdbapi_instances.remove(myportdb)
diff --git a/gosbs/common/task.py b/gosbs/common/task.py
new file mode 100644
index 0000000..4fb98cb
--- /dev/null
+++ b/gosbs/common/task.py
@@ -0,0 +1,70 @@
+# Copyright 1999-2020 Gentoo Authors
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
+import pytz
+from importlib import import_module
+
+from oslo_utils import uuidutils
+
+from gosbs import objects
+
+def time_to_run_task(task_db):
+ task_time_now = datetime.now().replace(tzinfo=pytz.UTC)
+ task_time_when = task_db.last + relativedelta(years=+(task_db.run.year - 1))
+ task_time_when = task_time_when + relativedelta(months=+(task_db.run.month - 1))
+ task_time_when = task_time_when + relativedelta(days=+(task_db.run.day -1))
+ task_time_when = task_time_when + relativedelta(hours=+task_db.run.hour)
+ task_time_when = task_time_when + relativedelta(minutes=+task_db.run.minute)
+ if task_time_when < task_time_now:
+ return True
+ else:
+ return False
+
+def create_task_db(context, name, run, repet, service_uuid):
+ task_db = objects.task.Task()
+ task_db.uuid = uuidutils.generate_uuid()
+ task_db.name = name
+ task_db.service_uuid = service_uuid
+ task_db.run = run
+ task_db.repet = repet
+ task_db.status = 'waiting'
+ task_db.last = datetime.now().replace(tzinfo=pytz.UTC)
+ task_db.create(context)
+ return task_db
+
+def check_task_db(context, name, run, repet, service_uuid):
+ filters = {
+ 'name' : name,
+ 'repet' : repet,
+ }
+ task_db = objects.task.Task.get_by_server_uuid(context, service_uuid, filters=filters)
+ if task_db is None:
+ task_db = create_task_db(context, name, run, repet, service_uuid)
+ task_db.status = 'waiting'
+ task_db.save(context)
+
+def run_task(context, filters, service_ref):
+ for task_db in objects.task.TaskList.get_all(context, filters=filters, sort_key='priority'):
+ if time_to_run_task(task_db):
+ task_db.status = 'in-progress'
+ task_db.save(context)
+ module_to_run = import_module('.' + task_db.name , 'gosbs.tasks.' + service_ref.topic)
+ module_to_run.task(context, service_ref.uuid)
+ if task_db.repet:
+ task_db.status = 'waiting'
+ task_db.last = datetime.now().replace(tzinfo=pytz.UTC)
+ task_db.save(context)
+ else:
+ task_db.destroy(context)
diff --git a/gosbs/conf/__init__.py b/gosbs/conf/__init__.py
new file mode 100644
index 0000000..05b2f36
--- /dev/null
+++ b/gosbs/conf/__init__.py
@@ -0,0 +1,47 @@
+# Copyright 2015 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# This package got introduced during the Mitaka cycle in 2015 to
+# have a central place where the config options of Nova can be maintained.
+# For more background see the blueprint "centralize-config-options"
+
+# Origin https://github.com/openstack/nova/blob/master/nova/conf/__init__.py
+# Import only what we need on gosbs
+
+from oslo_config import cfg
+
+from gosbs.conf import base
+from gosbs.conf import database
+from gosbs.conf import keystone
+from gosbs.conf import netconf
+from gosbs.conf import notifications
+from gosbs.conf import paths
+from gosbs.conf import rpc
+from gosbs.conf import scheduler
+from gosbs.conf import service
+from gosbs.conf import upgrade_levels
+
+CONF = cfg.CONF
+
+base.register_opts(CONF)
+database.register_opts(CONF)
+keystone.register_opts(CONF)
+netconf.register_opts(CONF)
+notifications.register_opts(CONF)
+paths.register_opts(CONF)
+rpc.register_opts(CONF)
+scheduler.register_opts(CONF)
+service.register_opts(CONF)
+upgrade_levels.register_opts(CONF)
diff --git a/gosbs/conf/base.py b/gosbs/conf/base.py
new file mode 100644
index 0000000..4390ad3
--- /dev/null
+++ b/gosbs/conf/base.py
@@ -0,0 +1,43 @@
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Justin Santa Barbara
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_config import cfg
+
+base_options = [
+ cfg.StrOpt("auth_strategy",
+ default="keystone",
+ choices=[
+ ("keystone", "Use keystone for authentication."),
+ ("noauth2", "Designed for testing only, as it does no actual "
+ "credential checking. 'noauth2' provides administrative "
+ "credentials only if 'admin' is specified as the username."),
+ ],
+ help="""
+Determine the strategy to use for authentication.
+"""),
+ cfg.StrOpt(
+ 'tempdir',
+ help='Explicitly specify the temporary working directory.'),
+]
+
+
+def register_opts(conf):
+ conf.register_opts(base_options)
+
+
+def list_opts():
+ return {'DEFAULT': base_options}
diff --git a/gosbs/conf/database.py b/gosbs/conf/database.py
new file mode 100644
index 0000000..a82c5e4
--- /dev/null
+++ b/gosbs/conf/database.py
@@ -0,0 +1,183 @@
+# Copyright 2015 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/conf/database.py
+
+from oslo_config import cfg
+from oslo_db import options as oslo_db_options
+
+from gosbs.conf import paths
+
+_DEFAULT_SQL_CONNECTION = 'sqlite:///' + paths.state_path_def('gosbs.sqlite')
+_ENRICHED = False
+
+
+# NOTE(markus_z): We cannot simply do:
+# conf.register_opts(oslo_db_options.database_opts, 'api_database')
+# If we reuse a db config option for two different groups ("api_database"
+# and "database") and deprecate or rename a config option in one of these
+# groups, "oslo.config" cannot correctly determine which one to update.
+# That's why we copied & pasted these config options for the "api_database"
+# group here. See commit ba407e3 ("Add support for multiple database engines")
+# for more details.
+api_db_group = cfg.OptGroup('api_database',
+ title='API Database Options',
+ help="""
+The *Nova API Database* is a separate database which is used for information
+which is used across *cells*. This database is mandatory since the Mitaka
+release (13.0.0).
+""")
+
+api_db_opts = [
+ # TODO(markus_z): This should probably have a required=True attribute
+ cfg.StrOpt('connection',
+ secret=True,
+ help=''),
+ cfg.StrOpt('connection_parameters',
+ default='',
+ help=''),
+ cfg.BoolOpt('sqlite_synchronous',
+ default=True,
+ help=''),
+ cfg.StrOpt('slave_connection',
+ secret=True,
+ help=''),
+ cfg.StrOpt('mysql_sql_mode',
+ default='TRADITIONAL',
+ help=''),
+ cfg.IntOpt('connection_recycle_time',
+ default=3600,
+ deprecated_name='idle_timeout',
+ help=''),
+ # TODO(markus_z): We should probably default this to 5 to not rely on the
+ # SQLAlchemy default. Otherwise we wouldn't provide a stable default.
+ cfg.IntOpt('max_pool_size',
+ help=''),
+ cfg.IntOpt('max_retries',
+ default=10,
+ help=''),
+ # TODO(markus_z): This should have a minimum attribute of 0
+ cfg.IntOpt('retry_interval',
+ default=10,
+ help=''),
+ # TODO(markus_z): We should probably default this to 10 to not rely on the
+ # SQLAlchemy default. Otherwise we wouldn't provide a stable default.
+ cfg.IntOpt('max_overflow',
+ help=''),
+ # TODO(markus_z): This should probably make use of the "choices" attribute.
+ # "oslo.db" uses only the values [<0, 0, 50, 100] see module
+ # /oslo_db/sqlalchemy/engines.py method "_setup_logging"
+ cfg.IntOpt('connection_debug',
+ default=0,
+ help=''),
+ cfg.BoolOpt('connection_trace',
+ default=False,
+ help=''),
+ # TODO(markus_z): We should probably default this to 30 to not rely on the
+ # SQLAlchemy default. Otherwise we wouldn't provide a stable default.
+ cfg.IntOpt('pool_timeout',
+ help='')
+] # noqa
+
+
+def enrich_help_text(alt_db_opts):
+
+ def get_db_opts():
+ for group_name, db_opts in oslo_db_options.list_opts():
+ if group_name == 'database':
+ return db_opts
+ return []
+
+ for db_opt in get_db_opts():
+ for alt_db_opt in alt_db_opts:
+ if alt_db_opt.name == db_opt.name:
+ # NOTE(markus_z): We can append alternative DB specific help
+ # texts here if needed.
+ alt_db_opt.help = db_opt.help + alt_db_opt.help
+
+# NOTE(cdent): See the note above on api_db_group. The same issues
+# apply here.
+
+placement_db_group = cfg.OptGroup('placement_database',
+ title='Placement API database options',
+ help="""
+The *Placement API Database* is a separate database which can be used with the
+placement service. This database is optional: if the connection option is not
+set, the nova api database will be used instead.
+""")
+
+placement_db_opts = [
+ cfg.StrOpt('connection',
+ help='',
+ secret=True),
+ cfg.StrOpt('connection_parameters',
+ default='',
+ help=''),
+ cfg.BoolOpt('sqlite_synchronous',
+ default=True,
+ help=''),
+ cfg.StrOpt('slave_connection',
+ secret=True,
+ help=''),
+ cfg.StrOpt('mysql_sql_mode',
+ default='TRADITIONAL',
+ help=''),
+ cfg.IntOpt('connection_recycle_time',
+ default=3600,
+ help=''),
+ cfg.IntOpt('max_pool_size',
+ help=''),
+ cfg.IntOpt('max_retries',
+ default=10,
+ help=''),
+ cfg.IntOpt('retry_interval',
+ default=10,
+ help=''),
+ cfg.IntOpt('max_overflow',
+ help=''),
+ cfg.IntOpt('connection_debug',
+ default=0,
+ help=''),
+ cfg.BoolOpt('connection_trace',
+ default=False,
+ help=''),
+ cfg.IntOpt('pool_timeout',
+ help=''),
+] # noqa
+
+
+def register_opts(conf):
+ oslo_db_options.set_defaults(conf, connection=_DEFAULT_SQL_CONNECTION)
+ conf.register_opts(api_db_opts, group=api_db_group)
+ conf.register_opts(placement_db_opts, group=placement_db_group)
+
+
+def list_opts():
+ # NOTE(markus_z): 2016-04-04: If we list the oslo_db_options here, they
+ # get emitted twice(!) in the "sample.conf" file. First under the
+ # namespace "nova.conf" and second under the namespace "oslo.db". This
+ # is due to the setting in file "etc/nova/nova-config-generator.conf".
+ # As I think it is useful to have the "oslo.db" namespace information
+ # in the "sample.conf" file, I omit the listing of the "oslo_db_options"
+ # here.
+ global _ENRICHED
+ if not _ENRICHED:
+ enrich_help_text(api_db_opts)
+ enrich_help_text(placement_db_opts)
+ _ENRICHED = True
+ return {
+ api_db_group: api_db_opts,
+ placement_db_group: placement_db_opts,
+ }
diff --git a/gosbs/conf/keystone.py b/gosbs/conf/keystone.py
new file mode 100644
index 0000000..4a7dd57
--- /dev/null
+++ b/gosbs/conf/keystone.py
@@ -0,0 +1,72 @@
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/conf/keystone.py
+
+from keystoneauth1 import loading as ks_loading
+from oslo_config import cfg
+
+from gosbs.conf import utils as confutils
+
+
+DEFAULT_SERVICE_TYPE = 'identity'
+
+keystone_group = cfg.OptGroup(
+ 'keystone',
+ title='Keystone Options',
+ help='Configuration options for the identity service')
+
+keystone_opts = [
+ cfg.StrOpt('auth_version',
+ default='3',
+ help='API version of the admin Identity API endpoint. (string value)'),
+ cfg.StrOpt('identity_interface',
+ default='',
+ help=''),
+ cfg.StrOpt('auth_url',
+ default='',
+ help=''),
+ cfg.StrOpt('project_domain_name',
+ default='',
+ help=''),
+ cfg.StrOpt('user_domain_name',
+ default='',
+ help=''),
+ cfg.StrOpt('project_id',
+ default='',
+ help=''),
+ cfg.StrOpt('username',
+ default='',
+ help=''),
+ cfg.StrOpt('password',
+ secret=True,
+ default='',
+ help=''),
+]
+
+
+def register_opts(conf):
+ conf.register_group(keystone_group)
+ confutils.register_ksa_opts(conf, keystone_group.name,
+ DEFAULT_SERVICE_TYPE, include_auth=True)
+ conf.register_opts(keystone_opts, group=keystone_group)
+
+
+def list_opts():
+ return {
+ keystone_group: (
+ ks_loading.get_session_conf_options() +
+ confutils.get_ksa_adapter_opts(DEFAULT_SERVICE_TYPE) +
+ keystone_opts
+ )
+ }
diff --git a/gosbs/conf/netconf.py b/gosbs/conf/netconf.py
new file mode 100644
index 0000000..5fe0f90
--- /dev/null
+++ b/gosbs/conf/netconf.py
@@ -0,0 +1,94 @@
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+# Copyright 2012 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import socket
+
+from oslo_config import cfg
+from oslo_utils import netutils
+
+
+netconf_opts = [
+ cfg.StrOpt("my_ip",
+ default=netutils.get_my_ipv4(),
+ sample_default='<host_ipv4>',
+ help="""
+The IP address which the host is using to connect to the management network.
+
+Possible values:
+
+* String with valid IP address. Default is IPv4 address of this host.
+
+Related options:
+
+* my_block_storage_ip
+"""),
+ cfg.StrOpt("my_block_storage_ip",
+ default="$my_ip",
+ help="""
+The IP address which is used to connect to the block storage network.
+
+Possible values:
+
+* String with valid IP address. Default is IP address of this host.
+
+Related options:
+
+* my_ip - if my_block_storage_ip is not set, then my_ip value is used.
+"""),
+ cfg.StrOpt("host",
+ default=socket.gethostname(),
+ sample_default='<current_hostname>',
+ help="""
+Hostname, FQDN or IP address of this host.
+
+Used as:
+
+* the oslo.messaging queue name for nova-compute worker
+* we use this value for the binding_host sent to neutron. This means if you use
+ a neutron agent, it should have the same value for host.
+* cinder host attachment information
+
+Must be valid within AMQP key.
+
+Possible values:
+
+* String with hostname, FQDN or IP address. Default is hostname of this host.
+"""),
+ # TODO(sfinucan): This option is tied into the XenAPI, VMWare and Libvirt
+ # drivers.
+ # We should remove this dependency by either adding a new opt for each
+ # driver or simply removing the offending code. Until then we cannot
+ # deprecate this option.
+ cfg.BoolOpt("flat_injected",
+ default=False,
+ help="""
+This option determines whether the network setup information is injected into
+the VM before it is booted. While it was originally designed to be used only
+by nova-network, it is also used by the vmware and xenapi virt drivers to
+control whether network information is injected into a VM. The libvirt virt
+driver also uses it when we use config_drive to configure network to control
+whether network information is injected into a VM.
+"""),
+]
+
+
+def register_opts(conf):
+ conf.register_opts(netconf_opts)
+
+
+def list_opts():
+ return {'DEFAULT': netconf_opts}
diff --git a/gosbs/conf/notifications.py b/gosbs/conf/notifications.py
new file mode 100644
index 0000000..a5946dd
--- /dev/null
+++ b/gosbs/conf/notifications.py
@@ -0,0 +1,118 @@
+# Copyright (c) 2016 Intel, Inc.
+# Copyright (c) 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_config import cfg
+
+notifications_group = cfg.OptGroup(
+ name='notifications',
+ title='Notifications options',
+ help="""
+Most of the actions in Nova which manipulate the system state generate
+notifications which are posted to the messaging component (e.g. RabbitMQ) and
+can be consumed by any service outside the OpenStack. More technical details
+at https://docs.openstack.org/nova/latest/reference/notifications.html
+""")
+
+ALL_OPTS = [
+ cfg.StrOpt(
+ 'notify_on_state_change',
+ choices=[
+ (None, 'no notifications'),
+ ('vm_state', 'Notifications are sent with VM state transition '
+ 'information in the ``old_state`` and ``state`` fields. The '
+ '``old_task_state`` and ``new_task_state`` fields will be set to '
+ 'the current task_state of the instance'),
+ ('vm_and_task_state', 'Notifications are sent with VM and task '
+ 'state transition information'),
+ ],
+ deprecated_group='DEFAULT',
+ help="""
+If set, send compute.instance.update notifications on
+instance state changes.
+
+Please refer to
+https://docs.openstack.org/nova/latest/reference/notifications.html for
+additional information on notifications.
+"""),
+
+ cfg.StrOpt(
+ 'default_level',
+ default='INFO',
+ choices=('DEBUG', 'INFO', 'WARN', 'ERROR', 'CRITICAL'),
+ deprecated_group='DEFAULT',
+ deprecated_name='default_notification_level',
+ help="Default notification level for outgoing notifications."),
+ cfg.StrOpt(
+ 'notification_format',
+ default='unversioned',
+ choices=[
+ ('both', 'Both the legacy unversioned and the new versioned '
+ 'notifications are emitted'),
+ ('versioned', 'Only the new versioned notifications are emitted'),
+ ('unversioned', 'Only the legacy unversioned notifications are '
+ 'emitted'),
+ ],
+ deprecated_group='DEFAULT',
+ help="""
+Specifies which notification format shall be emitted by nova.
+
+The versioned notification interface are in feature parity with the legacy
+interface and the versioned interface is actively developed so new consumers
+should used the versioned interface.
+
+However, the legacy interface is heavily used by ceilometer and other mature
+OpenStack components so it remains the default.
+
+Note that notifications can be completely disabled by setting ``driver=noop``
+in the ``[oslo_messaging_notifications]`` group.
+
+The list of versioned notifications is visible in
+https://docs.openstack.org/nova/latest/reference/notifications.html
+"""),
+ cfg.ListOpt(
+ 'versioned_notifications_topics',
+ default=['versioned_notifications'],
+ help="""
+Specifies the topics for the versioned notifications issued by nova.
+
+The default value is fine for most deployments and rarely needs to be changed.
+However, if you have a third-party service that consumes versioned
+notifications, it might be worth getting a topic for that service.
+Nova will send a message containing a versioned notification payload to each
+topic queue in this list.
+
+The list of versioned notifications is visible in
+https://docs.openstack.org/nova/latest/reference/notifications.html
+"""),
+ cfg.BoolOpt(
+ 'bdms_in_notifications',
+ default=False,
+ help="""
+If enabled, include block device information in the versioned notification
+payload. Sending block device information is disabled by default as providing
+that information can incur some overhead on the system since the information
+may need to be loaded from the database.
+""")
+]
+
+
+def register_opts(conf):
+ conf.register_group(notifications_group)
+ conf.register_opts(ALL_OPTS, group=notifications_group)
+
+
+def list_opts():
+ return {notifications_group: ALL_OPTS}
diff --git a/gosbs/conf/opts.py b/gosbs/conf/opts.py
new file mode 100644
index 0000000..9aed6b2
--- /dev/null
+++ b/gosbs/conf/opts.py
@@ -0,0 +1,79 @@
+# Copyright 2015 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+This is the single point of entry to generate the sample configuration
+file for Nova. It collects all the necessary info from the other modules
+in this package. It is assumed that:
+
+* every other module in this package has a 'list_opts' function which
+ return a dict where
+ * the keys are strings which are the group names
+ * the value of each key is a list of config options for that group
+* the nova.conf package doesn't have further packages with config options
+* this module is only used in the context of sample file generation
+"""
+
+import collections
+import importlib
+import os
+import pkgutil
+
+LIST_OPTS_FUNC_NAME = "list_opts"
+
+
+def _tupleize(dct):
+ """Take the dict of options and convert to the 2-tuple format."""
+ return [(key, val) for key, val in dct.items()]
+
+
+def list_opts():
+ opts = collections.defaultdict(list)
+ module_names = _list_module_names()
+ imported_modules = _import_modules(module_names)
+ _append_config_options(imported_modules, opts)
+ return _tupleize(opts)
+
+
+def _list_module_names():
+ module_names = []
+ package_path = os.path.dirname(os.path.abspath(__file__))
+ for _, modname, ispkg in pkgutil.iter_modules(path=[package_path]):
+ if modname == "opts" or ispkg:
+ continue
+ else:
+ module_names.append(modname)
+ return module_names
+
+
+def _import_modules(module_names):
+ imported_modules = []
+ for modname in module_names:
+ mod = importlib.import_module("nova.conf." + modname)
+ if not hasattr(mod, LIST_OPTS_FUNC_NAME):
+ msg = "The module 'nova.conf.%s' should have a '%s' "\
+ "function which returns the config options." % \
+ (modname, LIST_OPTS_FUNC_NAME)
+ raise Exception(msg)
+ else:
+ imported_modules.append(mod)
+ return imported_modules
+
+
+def _append_config_options(imported_modules, config_options):
+ for mod in imported_modules:
+ configs = mod.list_opts()
+ for key, val in configs.items():
+ config_options[key].extend(val)
diff --git a/gosbs/conf/paths.py b/gosbs/conf/paths.py
new file mode 100644
index 0000000..f4b7f6c
--- /dev/null
+++ b/gosbs/conf/paths.py
@@ -0,0 +1,106 @@
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+# Copyright 2012 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/conf/paths.py
+# Add repopath
+
+import os
+import sys
+
+from oslo_config import cfg
+
+ALL_OPTS = [
+ cfg.StrOpt('pybasedir',
+ default=os.path.abspath(os.path.join(os.path.dirname(__file__),
+ '../../')),
+ sample_default='<Path>',
+ help="""
+The directory where the Nova python modules are installed.
+
+This directory is used to store template files for networking and remote
+console access. It is also the default path for other config options which
+need to persist Nova internal data. It is very unlikely that you need to
+change this option from its default value.
+
+Possible values:
+
+* The full path to a directory.
+
+Related options:
+
+* ``state_path``
+"""),
+ cfg.StrOpt('bindir',
+ default=os.path.join(sys.prefix, 'local', 'bin'),
+ help="""
+The directory where the Nova binaries are installed.
+
+This option is only relevant if the networking capabilities from Nova are
+used (see services below). Nova's networking capabilities are targeted to
+be fully replaced by Neutron in the future. It is very unlikely that you need
+to change this option from its default value.
+
+Possible values:
+
+* The full path to a directory.
+"""),
+
+ cfg.StrOpt('state_path',
+ default='$pybasedir',
+ help="""
+The top-level directory for maintaining Nova's state.
+
+This directory is used to store Nova's internal state. It is used by a
+variety of other config options which derive from this. In some scenarios
+(for example migrations) it makes sense to use a storage location which is
+shared between multiple compute hosts (for example via NFS). Unless the
+option ``instances_path`` gets overwritten, this directory can grow very
+large.
+
+Possible values:
+
+* The full path to a directory. Defaults to value provided in ``pybasedir``.
+"""),
+ cfg.StrOpt(
+ 'repopath',
+ help="""
+Explicitly specify the repos working directory.
+"""),
+]
+
+
+def basedir_def(*args):
+ """Return an uninterpolated path relative to $pybasedir."""
+ return os.path.join('$pybasedir', *args)
+
+
+def bindir_def(*args):
+ """Return an uninterpolated path relative to $bindir."""
+ return os.path.join('$bindir', *args)
+
+
+def state_path_def(*args):
+ """Return an uninterpolated path relative to $state_path."""
+ return os.path.join('$state_path', *args)
+
+
+def register_opts(conf):
+ conf.register_opts(ALL_OPTS)
+
+
+def list_opts():
+ return {"DEFAULT": ALL_OPTS}
diff --git a/gosbs/conf/rpc.py b/gosbs/conf/rpc.py
new file mode 100644
index 0000000..a74ef10
--- /dev/null
+++ b/gosbs/conf/rpc.py
@@ -0,0 +1,46 @@
+# Copyright 2018 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_config import cfg
+
+rpc_opts = [
+ cfg.IntOpt("long_rpc_timeout",
+ default=1800,
+ help="""
+This option allows setting an alternate timeout value for RPC calls
+that have the potential to take a long time. If set, RPC calls to
+other services will use this value for the timeout (in seconds)
+instead of the global rpc_response_timeout value.
+
+Operations with RPC calls that utilize this value:
+
+* live migration
+
+Related options:
+
+* rpc_response_timeout
+"""),
+]
+
+
+ALL_OPTS = rpc_opts
+
+
+def register_opts(conf):
+ conf.register_opts(ALL_OPTS)
+
+
+def list_opts():
+ return {'DEFAULT': ALL_OPTS}
diff --git a/gosbs/conf/scheduler.py b/gosbs/conf/scheduler.py
new file mode 100644
index 0000000..7b38d53
--- /dev/null
+++ b/gosbs/conf/scheduler.py
@@ -0,0 +1,37 @@
+# Copyright 1999-2020 Gentoo Authors
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_config import cfg
+
+scheduler_group = cfg.OptGroup(name="scheduler",
+ title="Scheduler configuration")
+
+scheduler_opts = [
+ cfg.StrOpt(
+ 'git_mirror_url',
+ help="""
+Explicitly specify the mirror git url.
+"""),
+ cfg.StrOpt(
+ 'db_project_repo',
+ help="""
+Explicitly specify the database project repo.
+"""),
+]
+
+def register_opts(conf):
+ conf.register_group(scheduler_group)
+ conf.register_opts(scheduler_opts, group=scheduler_group)
+
+def list_opts():
+ return {scheduler_group: scheduler_opts}
diff --git a/gosbs/conf/service.py b/gosbs/conf/service.py
new file mode 100644
index 0000000..6a57efa
--- /dev/null
+++ b/gosbs/conf/service.py
@@ -0,0 +1,169 @@
+# needs:check_deprecation_status
+
+
+# Copyright 2015 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_config import cfg
+
+service_opts = [
+ # TODO(johngarbutt) we need a better default and minimum, in a backwards
+ # compatible way for report_interval
+ cfg.IntOpt('report_interval',
+ default=10,
+ help="""
+Number of seconds indicating how frequently the state of services on a
+given hypervisor is reported. Nova needs to know this to determine the
+overall health of the deployment.
+
+Related Options:
+
+* service_down_time
+ report_interval should be less than service_down_time. If service_down_time
+ is less than report_interval, services will routinely be considered down,
+ because they report in too rarely.
+"""),
+ # TODO(johngarbutt) the code enforces the min value here, but we could
+ # do to add some min value here, once we sort out report_interval
+ cfg.IntOpt('service_down_time',
+ default=60,
+ help="""
+Maximum time in seconds since last check-in for up service
+
+Each compute node periodically updates their database status based on the
+specified report interval. If the compute node hasn't updated the status
+for more than service_down_time, then the compute node is considered down.
+
+Related Options:
+
+* report_interval (service_down_time should not be less than report_interval)
+* scheduler.periodic_task_interval
+"""),
+ cfg.BoolOpt('periodic_enable',
+ default=True,
+ help="""
+Enable periodic tasks.
+
+If set to true, this option allows services to periodically run tasks
+on the manager.
+
+In case of running multiple schedulers or conductors you may want to run
+periodic tasks on only one host - in this case disable this option for all
+hosts but one.
+"""),
+ cfg.IntOpt('periodic_fuzzy_delay',
+ default=60,
+ min=0,
+ help="""
+Number of seconds to randomly delay when starting the periodic task
+scheduler to reduce stampeding.
+
+When compute workers are restarted in unison across a cluster,
+they all end up running the periodic tasks at the same time
+causing problems for the external services. To mitigate this
+behavior, periodic_fuzzy_delay option allows you to introduce a
+random initial delay when starting the periodic task scheduler.
+
+Possible Values:
+
+* Any positive integer (in seconds)
+* 0 : disable the random delay
+"""),
+ cfg.ListOpt('enabled_apis',
+ item_type=cfg.types.String(choices=['osapi_compute',
+ 'metadata']),
+ default=['osapi_compute', 'metadata'],
+ help="List of APIs to be enabled by default."),
+ cfg.ListOpt('enabled_ssl_apis',
+ default=[],
+ help="""
+List of APIs with enabled SSL.
+
+Nova provides SSL support for the API servers. enabled_ssl_apis option
+allows configuring the SSL support.
+"""),
+ cfg.StrOpt('osapi_compute_listen',
+ default="0.0.0.0",
+ help="""
+IP address on which the OpenStack API will listen.
+
+The OpenStack API service listens on this IP address for incoming
+requests.
+"""),
+ cfg.PortOpt('osapi_compute_listen_port',
+ default=8774,
+ help="""
+Port on which the OpenStack API will listen.
+
+The OpenStack API service listens on this port number for incoming
+requests.
+"""),
+ cfg.IntOpt('osapi_compute_workers',
+ min=1,
+ help="""
+Number of workers for OpenStack API service. The default will be the number
+of CPUs available.
+
+OpenStack API services can be configured to run as multi-process (workers).
+This overcomes the problem of reduction in throughput when API request
+concurrency increases. OpenStack API service will run in the specified
+number of processes.
+
+Possible Values:
+
+* Any positive integer
+* None (default value)
+"""),
+ cfg.StrOpt('metadata_listen',
+ default="0.0.0.0",
+ help="""
+IP address on which the metadata API will listen.
+
+The metadata API service listens on this IP address for incoming
+requests.
+"""),
+ cfg.PortOpt('metadata_listen_port',
+ default=8775,
+ help="""
+Port on which the metadata API will listen.
+
+The metadata API service listens on this port number for incoming
+requests.
+"""),
+ cfg.IntOpt('metadata_workers',
+ min=1,
+ help="""
+Number of workers for metadata service. If not specified the number of
+available CPUs will be used.
+
+The metadata service can be configured to run as multi-process (workers).
+This overcomes the problem of reduction in throughput when API request
+concurrency increases. The metadata service will run in the specified
+number of processes.
+
+Possible Values:
+
+* Any positive integer
+* None (default value)
+"""),
+]
+
+
+def register_opts(conf):
+ conf.register_opts(service_opts)
+
+
+def list_opts():
+ return {'DEFAULT': service_opts}
diff --git a/gosbs/conf/upgrade_levels.py b/gosbs/conf/upgrade_levels.py
new file mode 100644
index 0000000..767ff59
--- /dev/null
+++ b/gosbs/conf/upgrade_levels.py
@@ -0,0 +1,210 @@
+# Copyright 2016 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_config import cfg
+
+upgrade_group = cfg.OptGroup('upgrade_levels',
+ title='Upgrade levels Options',
+ help="""
+upgrade_levels options are used to set version cap for RPC
+messages sent between different nova services.
+
+By default all services send messages using the latest version
+they know about.
+
+The compute upgrade level is an important part of rolling upgrades
+where old and new nova-compute services run side by side.
+
+The other options can largely be ignored, and are only kept to
+help with a possible future backport issue.
+""")
+
+# TODO(sneti): Add default=auto for compute
+upgrade_levels_opts = [
+ cfg.StrOpt('compute',
+ help="""
+Compute RPC API version cap.
+
+By default, we always send messages using the most recent version
+the client knows about.
+
+Where you have old and new compute services running, you should set
+this to the lowest deployed version. This is to guarantee that all
+services never send messages that one of the compute nodes can't
+understand. Note that we only support upgrading from release N to
+release N+1.
+
+Set this option to "auto" if you want to let the compute RPC module
+automatically determine what version to use based on the service
+versions in the deployment.
+
+Possible values:
+
+* By default send the latest version the client knows about
+* 'auto': Automatically determines what version to use based on
+ the service versions in the deployment.
+* A string representing a version number in the format 'N.N';
+ for example, possible values might be '1.12' or '2.0'.
+* An OpenStack release name, in lower case, such as 'mitaka' or
+ 'liberty'.
+"""),
+ cfg.StrOpt('cells',
+ help="""
+Cells RPC API version cap.
+
+Possible values:
+
+* By default send the latest version the client knows about
+* A string representing a version number in the format 'N.N';
+ for example, possible values might be '1.12' or '2.0'.
+* An OpenStack release name, in lower case, such as 'mitaka' or
+ 'liberty'.
+"""),
+ cfg.StrOpt('intercell',
+ help="""
+Intercell RPC API version cap.
+
+Possible values:
+
+* By default send the latest version the client knows about
+* A string representing a version number in the format 'N.N';
+ for example, possible values might be '1.12' or '2.0'.
+* An OpenStack release name, in lower case, such as 'mitaka' or
+ 'liberty'.
+"""),
+ cfg.StrOpt("cert",
+ deprecated_for_removal=True,
+ deprecated_since='18.0.0',
+ deprecated_reason="""
+The nova-cert service was removed in 16.0.0 (Pike) so this option
+is no longer used.
+""",
+ help="""
+Cert RPC API version cap.
+
+Possible values:
+
+* By default send the latest version the client knows about
+* A string representing a version number in the format 'N.N';
+ for example, possible values might be '1.12' or '2.0'.
+* An OpenStack release name, in lower case, such as 'mitaka' or
+ 'liberty'.
+"""),
+ cfg.StrOpt("scheduler",
+ help="""
+Scheduler RPC API version cap.
+
+Possible values:
+
+* By default send the latest version the client knows about
+* A string representing a version number in the format 'N.N';
+ for example, possible values might be '1.12' or '2.0'.
+* An OpenStack release name, in lower case, such as 'mitaka' or
+ 'liberty'.
+"""),
+ cfg.StrOpt("updater",
+ help="""
+Scheduler RPC API version cap.
+
+Possible values:
+
+* By default send the latest version the client knows about
+* A string representing a version number in the format 'N.N';
+ for example, possible values might be '1.12' or '2.0'.
+* An OpenStack release name, in lower case, such as 'mitaka' or
+ 'liberty'.
+"""),
+ cfg.StrOpt('conductor',
+ help="""
+Conductor RPC API version cap.
+
+Possible values:
+
+* By default send the latest version the client knows about
+* A string representing a version number in the format 'N.N';
+ for example, possible values might be '1.12' or '2.0'.
+* An OpenStack release name, in lower case, such as 'mitaka' or
+ 'liberty'.
+"""),
+ cfg.StrOpt('console',
+ help="""
+Console RPC API version cap.
+
+Possible values:
+
+* By default send the latest version the client knows about
+* A string representing a version number in the format 'N.N';
+ for example, possible values might be '1.12' or '2.0'.
+* An OpenStack release name, in lower case, such as 'mitaka' or
+ 'liberty'.
+"""),
+ cfg.StrOpt('consoleauth',
+ deprecated_for_removal=True,
+ deprecated_since='18.0.0',
+ deprecated_reason="""
+The nova-consoleauth service was deprecated in 18.0.0 (Rocky) and will be
+removed in an upcoming release.
+""",
+ help="""
+Consoleauth RPC API version cap.
+
+Possible values:
+
+* By default send the latest version the client knows about
+* A string representing a version number in the format 'N.N';
+ for example, possible values might be '1.12' or '2.0'.
+* An OpenStack release name, in lower case, such as 'mitaka' or
+ 'liberty'.
+"""),
+ cfg.StrOpt('network',
+ deprecated_for_removal=True,
+ deprecated_since='18.0.0',
+ deprecated_reason="""
+The nova-network service was deprecated in 14.0.0 (Newton) and will be
+removed in an upcoming release.
+""",
+ help="""
+Network RPC API version cap.
+
+Possible values:
+
+* By default send the latest version the client knows about
+* A string representing a version number in the format 'N.N';
+ for example, possible values might be '1.12' or '2.0'.
+* An OpenStack release name, in lower case, such as 'mitaka' or
+ 'liberty'.
+"""),
+ cfg.StrOpt('baseapi',
+ help="""
+Base API RPC API version cap.
+
+Possible values:
+
+* By default send the latest version the client knows about
+* A string representing a version number in the format 'N.N';
+ for example, possible values might be '1.12' or '2.0'.
+* An OpenStack release name, in lower case, such as 'mitaka' or
+ 'liberty'.
+""")
+]
+
+
+def register_opts(conf):
+ conf.register_group(upgrade_group)
+ conf.register_opts(upgrade_levels_opts, group=upgrade_group)
+
+
+def list_opts():
+ return {upgrade_group: upgrade_levels_opts}
diff --git a/gosbs/conf/utils.py b/gosbs/conf/utils.py
new file mode 100644
index 0000000..da96444
--- /dev/null
+++ b/gosbs/conf/utils.py
@@ -0,0 +1,91 @@
+# Copyright 2017 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""Common utilities for conf providers.
+
+This module does not provide any actual conf options.
+"""
+from keystoneauth1 import loading as ks_loading
+from oslo_config import cfg
+
+
+_ADAPTER_VERSION_OPTS = ('version', 'min_version', 'max_version')
+
+
+def get_ksa_adapter_opts(default_service_type, deprecated_opts=None):
+ """Get auth, Session, and Adapter conf options from keystonauth1.loading.
+
+ :param default_service_type: Default for the service_type conf option on
+ the Adapter.
+ :param deprecated_opts: dict of deprecated opts to register with the ksa
+ Adapter opts. Works the same as the
+ deprecated_opts kwarg to:
+ keystoneauth1.loading.session.Session.register_conf_options
+ :return: List of cfg.Opts.
+ """
+ opts = ks_loading.get_adapter_conf_options(include_deprecated=False,
+ deprecated_opts=deprecated_opts)
+
+ for opt in opts[:]:
+ # Remove version-related opts. Required/supported versions are
+ # something the code knows about, not the operator.
+ if opt.dest in _ADAPTER_VERSION_OPTS:
+ opts.remove(opt)
+
+ # Override defaults that make sense for nova
+ cfg.set_defaults(opts,
+ valid_interfaces=['internal', 'public'],
+ service_type=default_service_type)
+ return opts
+
+
+def _dummy_opt(name):
+ # A config option that can't be set by the user, so it behaves as if it's
+ # ignored; but consuming code may expect it to be present in a conf group.
+ return cfg.Opt(name, type=lambda x: None)
+
+
+def register_ksa_opts(conf, group, default_service_type, include_auth=True,
+ deprecated_opts=None):
+ """Register keystoneauth auth, Session, and Adapter opts.
+
+ :param conf: oslo_config.cfg.CONF in which to register the options
+ :param group: Conf group, or string name thereof, in which to register the
+ options.
+ :param default_service_type: Default for the service_type conf option on
+ the Adapter.
+ :param include_auth: For service types where Nova is acting on behalf of
+ the user, auth should come from the user context.
+ In those cases, set this arg to False to avoid
+ registering ksa auth options.
+ :param deprecated_opts: dict of deprecated opts to register with the ksa
+ Session or Adapter opts. See docstring for
+ the deprecated_opts param of:
+ keystoneauth1.loading.session.Session.register_conf_options
+ """
+ # ksa register methods need the group name as a string. oslo doesn't care.
+ group = getattr(group, 'name', group)
+ ks_loading.register_session_conf_options(
+ conf, group, deprecated_opts=deprecated_opts)
+ if include_auth:
+ ks_loading.register_auth_conf_options(conf, group)
+ conf.register_opts(get_ksa_adapter_opts(
+ default_service_type, deprecated_opts=deprecated_opts), group=group)
+ # Have to register dummies for the version-related opts we removed
+ for name in _ADAPTER_VERSION_OPTS:
+ conf.register_opt(_dummy_opt(name), group=group)
+
+
+# NOTE(efried): Required for docs build.
+def list_opts():
+ return {}
diff --git a/gosbs/config.py b/gosbs/config.py
new file mode 100644
index 0000000..6defbc2
--- /dev/null
+++ b/gosbs/config.py
@@ -0,0 +1,50 @@
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+# Copyright 2012 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/config.py
+
+from oslo_log import log
+from oslo_utils import importutils
+
+from gosbs import middleware
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as sqlalchemy_api
+from gosbs import rpc
+from gosbs import version
+
+CONF = gosbs.conf.CONF
+
+def parse_args(argv, default_config_files=None, configure_db=True,
+ init_rpc=True):
+ log.register_options(CONF)
+ extra_default_log_levels = []
+
+ log.set_defaults(default_log_levels=log.get_default_log_levels() +
+ extra_default_log_levels)
+ rpc.set_defaults(control_exchange='gosbs')
+ middleware.set_defaults()
+
+ CONF(argv[1:],
+ project='gosbs',
+ version=version.version_string(),
+ default_config_files=default_config_files)
+
+ if init_rpc:
+ rpc.init(CONF)
+
+ if configure_db:
+ sqlalchemy_api.configure(CONF)
diff --git a/gosbs/context.py b/gosbs/context.py
new file mode 100644
index 0000000..20df39c
--- /dev/null
+++ b/gosbs/context.py
@@ -0,0 +1,562 @@
+# Copyright 2011 OpenStack Foundation
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/context.py
+
+"""RequestContext: context for requests that persist through all of nova."""
+
+from contextlib import contextmanager
+import copy
+import warnings
+
+import eventlet.queue
+import eventlet.timeout
+from keystoneauth1.access import service_catalog as ksa_service_catalog
+from keystoneauth1 import plugin
+from oslo_context import context
+from oslo_db.sqlalchemy import enginefacade
+from oslo_log import log as logging
+from oslo_utils import timeutils
+import six
+
+from gosbs import exception
+from gosbs.i18n import _
+from gosbs import objects
+from gosbs import policy
+from gosbs import utils
+
+LOG = logging.getLogger(__name__)
+# TODO(melwitt): This cache should be cleared whenever WSGIService receives a
+# SIGHUP and periodically based on an expiration time. Currently, none of the
+# cell caches are purged, so neither is this one, for now.
+CELL_CACHE = {}
+# NOTE(melwitt): Used for the scatter-gather utility to indicate we timed out
+# waiting for a result from a cell.
+did_not_respond_sentinel = object()
+# FIXME(danms): Keep a global cache of the cells we find the
+# first time we look. This needs to be refreshed on a timer or
+# trigger.
+CELLS = []
+# Timeout value for waiting for cells to respond
+CELL_TIMEOUT = 60
+
+
+class _ContextAuthPlugin(plugin.BaseAuthPlugin):
+ """A keystoneauth auth plugin that uses the values from the Context.
+
+ Ideally we would use the plugin provided by auth_token middleware however
+ this plugin isn't serialized yet so we construct one from the serialized
+ auth data.
+ """
+
+ def __init__(self, auth_token, sc):
+ super(_ContextAuthPlugin, self).__init__()
+
+ self.auth_token = auth_token
+ self.service_catalog = ksa_service_catalog.ServiceCatalogV2(sc)
+
+ def get_token(self, *args, **kwargs):
+ return self.auth_token
+
+ def get_endpoint(self, session, service_type=None, interface=None,
+ region_name=None, service_name=None, **kwargs):
+ return self.service_catalog.url_for(service_type=service_type,
+ service_name=service_name,
+ interface=interface,
+ region_name=region_name)
+
+
+@enginefacade.transaction_context_provider
+class RequestContext(context.RequestContext):
+ """Security context and request information.
+
+ Represents the user taking a given action within the system.
+
+ """
+
+ def __init__(self, user_id=None, project_id=None, is_admin=None,
+ read_deleted="no", remote_address=None, timestamp=None,
+ quota_class=None, service_catalog=None,
+ user_auth_plugin=None, **kwargs):
+ """:param read_deleted: 'no' indicates deleted records are hidden,
+ 'yes' indicates deleted records are visible,
+ 'only' indicates that *only* deleted records are visible.
+
+ :param overwrite: Set to False to ensure that the greenthread local
+ copy of the index is not overwritten.
+
+ :param instance_lock_checked: This is not used and will be removed
+ in a future release.
+
+ :param user_auth_plugin: The auth plugin for the current request's
+ authentication data.
+ """
+ if user_id:
+ kwargs['user_id'] = user_id
+ if project_id:
+ kwargs['project_id'] = project_id
+
+ if kwargs.pop('instance_lock_checked', None) is not None:
+ # TODO(mriedem): Let this be a hard failure in 19.0.0 (S).
+ warnings.warn("The 'instance_lock_checked' kwarg to "
+ "nova.context.RequestContext is no longer used and "
+ "will be removed in a future version.")
+
+ super(RequestContext, self).__init__(is_admin=is_admin, **kwargs)
+
+ self.read_deleted = read_deleted
+ self.remote_address = remote_address
+ if not timestamp:
+ timestamp = timeutils.utcnow()
+ if isinstance(timestamp, six.string_types):
+ timestamp = timeutils.parse_strtime(timestamp)
+ self.timestamp = timestamp
+
+ if service_catalog:
+ # Only include required parts of service_catalog
+ self.service_catalog = [s for s in service_catalog
+ if s.get('type') in ('image', 'block-storage', 'volumev3',
+ 'key-manager', 'placement', 'network')]
+ else:
+ # if list is empty or none
+ self.service_catalog = []
+
+ # NOTE(markmc): this attribute is currently only used by the
+ # rs_limits turnstile pre-processor.
+ # See https://lists.launchpad.net/openstack/msg12200.html
+ self.quota_class = quota_class
+
+ # NOTE(dheeraj): The following attributes are used by cellsv2 to store
+ # connection information for connecting to the target cell.
+ # It is only manipulated using the target_cell contextmanager
+ # provided by this module
+ self.db_connection = None
+ self.mq_connection = None
+ self.cell_uuid = None
+
+ self.user_auth_plugin = user_auth_plugin
+ if self.is_admin is None:
+ self.is_admin = policy.check_is_admin(self)
+
+ def get_auth_plugin(self):
+ if self.user_auth_plugin:
+ return self.user_auth_plugin
+ else:
+ return _ContextAuthPlugin(self.auth_token, self.service_catalog)
+
+ def _get_read_deleted(self):
+ return self._read_deleted
+
+ def _set_read_deleted(self, read_deleted):
+ if read_deleted not in ('no', 'yes', 'only'):
+ raise ValueError(_("read_deleted can only be one of 'no', "
+ "'yes' or 'only', not %r") % read_deleted)
+ self._read_deleted = read_deleted
+
+ def _del_read_deleted(self):
+ del self._read_deleted
+
+ read_deleted = property(_get_read_deleted, _set_read_deleted,
+ _del_read_deleted)
+
+ def to_dict(self):
+ values = super(RequestContext, self).to_dict()
+ # FIXME(dims): defensive hasattr() checks need to be
+ # removed once we figure out why we are seeing stack
+ # traces
+ values.update({
+ 'user_id': getattr(self, 'user_id', None),
+ 'project_id': getattr(self, 'project_id', None),
+ 'is_admin': getattr(self, 'is_admin', None),
+ 'read_deleted': getattr(self, 'read_deleted', 'no'),
+ 'remote_address': getattr(self, 'remote_address', None),
+ 'timestamp': utils.strtime(self.timestamp) if hasattr(
+ self, 'timestamp') else None,
+ 'request_id': getattr(self, 'request_id', None),
+ 'quota_class': getattr(self, 'quota_class', None),
+ 'user_name': getattr(self, 'user_name', None),
+ 'service_catalog': getattr(self, 'service_catalog', None),
+ 'project_name': getattr(self, 'project_name', None),
+ })
+ # NOTE(tonyb): This can be removed once we're certain to have a
+ # RequestContext contains 'is_admin_project', We can only get away with
+ # this because we "know" the default value of 'is_admin_project' which
+ # is very fragile.
+ values.update({
+ 'is_admin_project': getattr(self, 'is_admin_project', True),
+ })
+ return values
+
+ @classmethod
+ def from_dict(cls, values):
+ return super(RequestContext, cls).from_dict(
+ values,
+ user_id=values.get('user_id'),
+ project_id=values.get('project_id'),
+ # TODO(sdague): oslo.context has show_deleted, if
+ # possible, we should migrate to that in the future so we
+ # don't need to be different here.
+ read_deleted=values.get('read_deleted', 'no'),
+ remote_address=values.get('remote_address'),
+ timestamp=values.get('timestamp'),
+ quota_class=values.get('quota_class'),
+ service_catalog=values.get('service_catalog'),
+ )
+
+ def elevated(self, read_deleted=None):
+ """Return a version of this context with admin flag set."""
+ context = copy.copy(self)
+ # context.roles must be deepcopied to leave original roles
+ # without changes
+ context.roles = copy.deepcopy(self.roles)
+ context.is_admin = True
+
+ if 'admin' not in context.roles:
+ context.roles.append('admin')
+
+ if read_deleted is not None:
+ context.read_deleted = read_deleted
+
+ return context
+
+ def can(self, action, target=None, fatal=True):
+ """Verifies that the given action is valid on the target in this context.
+
+ :param action: string representing the action to be checked.
+ :param target: dictionary representing the object of the action
+ for object creation this should be a dictionary representing the
+ location of the object e.g. ``{'project_id': context.project_id}``.
+ If None, then this default target will be considered:
+ {'project_id': self.project_id, 'user_id': self.user_id}
+ :param fatal: if False, will return False when an exception.Forbidden
+ occurs.
+
+ :raises nova.exception.Forbidden: if verification fails and fatal is
+ True.
+
+ :return: returns a non-False value (not necessarily "True") if
+ authorized and False if not authorized and fatal is False.
+ """
+ if target is None:
+ target = {'project_id': self.project_id,
+ 'user_id': self.user_id}
+
+ try:
+ return policy.authorize(self, action, target)
+ except exception.Forbidden:
+ if fatal:
+ raise
+ return False
+
+ def to_policy_values(self):
+ policy = super(RequestContext, self).to_policy_values()
+ policy['is_admin'] = self.is_admin
+ return policy
+
+ def __str__(self):
+ return "<Context %s>" % self.to_dict()
+
+
+def get_context():
+ """A helper method to get a blank context.
+
+ Note that overwrite is False here so this context will not update the
+ greenthread-local stored context that is used when logging.
+ """
+ return RequestContext(user_id=None,
+ project_id=None,
+ is_admin=False,
+ overwrite=False)
+
+
+def get_admin_context(read_deleted="no"):
+ # NOTE(alaski): This method should only be used when an admin context is
+ # necessary for the entirety of the context lifetime. If that's not the
+ # case please use get_context(), or create the RequestContext manually, and
+ # use context.elevated() where necessary. Some periodic tasks may use
+ # get_admin_context so that their database calls are not filtered on
+ # project_id.
+ return RequestContext(user_id=None,
+ project_id=None,
+ is_admin=True,
+ read_deleted=read_deleted,
+ overwrite=False)
+
+
+def is_user_context(context):
+ """Indicates if the request context is a normal user."""
+ if not context:
+ return False
+ if context.is_admin:
+ return False
+ if not context.user_id or not context.project_id:
+ return False
+ return True
+
+
+def require_context(ctxt):
+ """Raise exception.Forbidden() if context is not a user or an
+ admin context.
+ """
+ if not ctxt.is_admin and not is_user_context(ctxt):
+ raise exception.Forbidden()
+
+
+def authorize_project_context(context, project_id):
+ """Ensures a request has permission to access the given project."""
+ if is_user_context(context):
+ if not context.project_id:
+ raise exception.Forbidden()
+ elif context.project_id != project_id:
+ raise exception.Forbidden()
+
+
+def authorize_user_context(context, user_id):
+ """Ensures a request has permission to access the given user."""
+ if is_user_context(context):
+ if not context.user_id:
+ raise exception.Forbidden()
+ elif context.user_id != user_id:
+ raise exception.Forbidden()
+
+
+def authorize_quota_class_context(context, class_name):
+ """Ensures a request has permission to access the given quota class."""
+ if is_user_context(context):
+ if not context.quota_class:
+ raise exception.Forbidden()
+ elif context.quota_class != class_name:
+ raise exception.Forbidden()
+
+
+def set_target_cell(context, cell_mapping):
+ """Adds database connection information to the context
+ for communicating with the given target_cell.
+
+ This is used for permanently targeting a cell in a context.
+ Use this when you want all subsequent code to target a cell.
+
+ Passing None for cell_mapping will untarget the context.
+
+ :param context: The RequestContext to add connection information
+ :param cell_mapping: An objects.CellMapping object or None
+ """
+ global CELL_CACHE
+ if cell_mapping is not None:
+ # avoid circular import
+ from nova.db import api as db
+ from nova import rpc
+
+ # Synchronize access to the cache by multiple API workers.
+ @utils.synchronized(cell_mapping.uuid)
+ def get_or_set_cached_cell_and_set_connections():
+ try:
+ cell_tuple = CELL_CACHE[cell_mapping.uuid]
+ except KeyError:
+ db_connection_string = cell_mapping.database_connection
+ context.db_connection = db.create_context_manager(
+ db_connection_string)
+ if not cell_mapping.transport_url.startswith('none'):
+ context.mq_connection = rpc.create_transport(
+ cell_mapping.transport_url)
+ context.cell_uuid = cell_mapping.uuid
+ CELL_CACHE[cell_mapping.uuid] = (context.db_connection,
+ context.mq_connection)
+ else:
+ context.db_connection = cell_tuple[0]
+ context.mq_connection = cell_tuple[1]
+ context.cell_uuid = cell_mapping.uuid
+
+ get_or_set_cached_cell_and_set_connections()
+ else:
+ context.db_connection = None
+ context.mq_connection = None
+ context.cell_uuid = None
+
+
+@contextmanager
+def target_cell(context, cell_mapping):
+ """Yields a new context with connection information for a specific cell.
+
+ This function yields a copy of the provided context, which is targeted to
+ the referenced cell for MQ and DB connections.
+
+ Passing None for cell_mapping will yield an untargetd copy of the context.
+
+ :param context: The RequestContext to add connection information
+ :param cell_mapping: An objects.CellMapping object or None
+ """
+ # Create a sanitized copy of context by serializing and deserializing it
+ # (like we would do over RPC). This help ensure that we have a clean
+ # copy of the context with all the tracked attributes, but without any
+ # of the hidden/private things we cache on a context. We do this to avoid
+ # unintentional sharing of cached thread-local data across threads.
+ # Specifically, this won't include any oslo_db-set transaction context, or
+ # any existing cell targeting.
+ cctxt = RequestContext.from_dict(context.to_dict())
+ set_target_cell(cctxt, cell_mapping)
+ yield cctxt
+
+
+def scatter_gather_cells(context, cell_mappings, timeout, fn, *args, **kwargs):
+ """Target cells in parallel and return their results.
+
+ The first parameter in the signature of the function to call for each cell
+ should be of type RequestContext.
+
+ :param context: The RequestContext for querying cells
+ :param cell_mappings: The CellMappings to target in parallel
+ :param timeout: The total time in seconds to wait for all the results to be
+ gathered
+ :param fn: The function to call for each cell
+ :param args: The args for the function to call for each cell, not including
+ the RequestContext
+ :param kwargs: The kwargs for the function to call for each cell
+ :returns: A dict {cell_uuid: result} containing the joined results. The
+ did_not_respond_sentinel will be returned if a cell did not
+ respond within the timeout. The exception object will
+ be returned if the call to a cell raised an exception. The
+ exception will be logged.
+ """
+ greenthreads = []
+ queue = eventlet.queue.LightQueue()
+ results = {}
+
+ def gather_result(cell_mapping, fn, context, *args, **kwargs):
+ cell_uuid = cell_mapping.uuid
+ try:
+ with target_cell(context, cell_mapping) as cctxt:
+ result = fn(cctxt, *args, **kwargs)
+ except Exception as e:
+ LOG.exception('Error gathering result from cell %s', cell_uuid)
+ result = e.__class__(e.args)
+ # The queue is already synchronized.
+ queue.put((cell_uuid, result))
+
+ for cell_mapping in cell_mappings:
+ greenthreads.append((cell_mapping.uuid,
+ utils.spawn(gather_result, cell_mapping,
+ fn, context, *args, **kwargs)))
+
+ with eventlet.timeout.Timeout(timeout, exception.CellTimeout):
+ try:
+ while len(results) != len(greenthreads):
+ cell_uuid, result = queue.get()
+ results[cell_uuid] = result
+ except exception.CellTimeout:
+ # NOTE(melwitt): We'll fill in did_not_respond_sentinels at the
+ # same time we kill/wait for the green threads.
+ pass
+
+ # Kill the green threads still pending and wait on those we know are done.
+ for cell_uuid, greenthread in greenthreads:
+ if cell_uuid not in results:
+ greenthread.kill()
+ results[cell_uuid] = did_not_respond_sentinel
+ LOG.warning('Timed out waiting for response from cell %s',
+ cell_uuid)
+ else:
+ greenthread.wait()
+
+ return results
+
+
+def load_cells():
+ global CELLS
+ if not CELLS:
+ CELLS = objects.CellMappingList.get_all(get_admin_context())
+ LOG.debug('Found %(count)i cells: %(cells)s',
+ dict(count=len(CELLS),
+ cells=','.join([c.identity for c in CELLS])))
+
+ if not CELLS:
+ LOG.error('No cells are configured, unable to continue')
+
+
+def is_cell_failure_sentinel(record):
+ return (record is did_not_respond_sentinel or
+ isinstance(record, Exception))
+
+
+def scatter_gather_skip_cell0(context, fn, *args, **kwargs):
+ """Target all cells except cell0 in parallel and return their results.
+
+ The first parameter in the signature of the function to call for
+ each cell should be of type RequestContext. There is a timeout for
+ waiting on all results to be gathered.
+
+ :param context: The RequestContext for querying cells
+ :param fn: The function to call for each cell
+ :param args: The args for the function to call for each cell, not including
+ the RequestContext
+ :param kwargs: The kwargs for the function to call for each cell
+ :returns: A dict {cell_uuid: result} containing the joined results. The
+ did_not_respond_sentinel will be returned if a cell did not
+ respond within the timeout. The exception object will
+ be returned if the call to a cell raised an exception. The
+ exception will be logged.
+ """
+ load_cells()
+ cell_mappings = [cell for cell in CELLS if not cell.is_cell0()]
+ return scatter_gather_cells(context, cell_mappings, CELL_TIMEOUT,
+ fn, *args, **kwargs)
+
+
+def scatter_gather_single_cell(context, cell_mapping, fn, *args, **kwargs):
+ """Target the provided cell and return its results or sentinels in case of
+ failure.
+
+ The first parameter in the signature of the function to call for each cell
+ should be of type RequestContext.
+
+ :param context: The RequestContext for querying cells
+ :param cell_mapping: The CellMapping to target
+ :param fn: The function to call for each cell
+ :param args: The args for the function to call for each cell, not including
+ the RequestContext
+ :param kwargs: The kwargs for the function to call for this cell
+ :returns: A dict {cell_uuid: result} containing the joined results. The
+ did_not_respond_sentinel will be returned if the cell did not
+ respond within the timeout. The exception object will
+ be returned if the call to the cell raised an exception. The
+ exception will be logged.
+ """
+ return scatter_gather_cells(context, [cell_mapping], CELL_TIMEOUT, fn,
+ *args, **kwargs)
+
+
+def scatter_gather_all_cells(context, fn, *args, **kwargs):
+ """Target all cells in parallel and return their results.
+
+ The first parameter in the signature of the function to call for
+ each cell should be of type RequestContext. There is a timeout for
+ waiting on all results to be gathered.
+
+ :param context: The RequestContext for querying cells
+ :param fn: The function to call for each cell
+ :param args: The args for the function to call for each cell, not including
+ the RequestContext
+ :param kwargs: The kwargs for the function to call for each cell
+ :returns: A dict {cell_uuid: result} containing the joined results. The
+ did_not_respond_sentinel will be returned if a cell did not
+ respond within the timeout. The exception object will
+ be returned if the call to a cell raised an exception. The
+ exception will be logged.
+ """
+ load_cells()
+ return scatter_gather_cells(context, CELLS, CELL_TIMEOUT,
+ fn, *args, **kwargs)
diff --git a/gosbs/db/__init__.py b/gosbs/db/__init__.py
new file mode 100644
index 0000000..8c26ee8
--- /dev/null
+++ b/gosbs/db/__init__.py
@@ -0,0 +1,13 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""Use nova.db.api instead. In the past this file imported * from there,
+which led to unwanted imports."""
diff --git a/gosbs/db/api.py b/gosbs/db/api.py
new file mode 100644
index 0000000..f8294a6
--- /dev/null
+++ b/gosbs/db/api.py
@@ -0,0 +1,1891 @@
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# origin https://github.com/openstack/nova/blob/master/nova/db/api.py
+
+"""Defines interface for DB access.
+
+Functions in this module are imported into the nova.db namespace. Call these
+functions from nova.db namespace, not the nova.db.api namespace.
+
+All functions in this module return objects that implement a dictionary-like
+interface. Currently, many of these objects are sqlalchemy objects that
+implement a dictionary interface. However, a future goal is to have all of
+these objects be simple dictionaries.
+
+"""
+
+from oslo_db import concurrency
+from oslo_log import log as logging
+
+import gosbs.conf
+from gosbs.db import constants
+
+
+CONF = gosbs.conf.CONF
+# NOTE(cdent): These constants are re-defined in this module to preserve
+# existing references to them.
+MAX_INT = constants.MAX_INT
+SQL_SP_FLOAT_MAX = constants.SQL_SP_FLOAT_MAX
+
+_BACKEND_MAPPING = {'sqlalchemy': 'gosbs.db.sqlalchemy.api'}
+
+
+IMPL = concurrency.TpoolDbapiWrapper(CONF, backend_mapping=_BACKEND_MAPPING)
+
+LOG = logging.getLogger(__name__)
+
+
+###################
+
+
+def constraint(**conditions):
+ """Return a constraint object suitable for use with some updates."""
+ return IMPL.constraint(**conditions)
+
+
+def equal_any(*values):
+ """Return an equality condition object suitable for use in a constraint.
+
+ Equal_any conditions require that a model object's attribute equal any
+ one of the given values.
+ """
+ return IMPL.equal_any(*values)
+
+
+def not_equal(*values):
+ """Return an inequality condition object suitable for use in a constraint.
+
+ Not_equal conditions require that a model object's attribute differs from
+ all of the given values.
+ """
+ return IMPL.not_equal(*values)
+
+
+def create_context_manager(connection):
+ """Return a context manager for a cell database connection."""
+ return IMPL.create_context_manager(connection=connection)
+
+
+###################
+
+
+def select_db_reader_mode(f):
+ """Decorator to select synchronous or asynchronous reader mode.
+
+ The kwarg argument 'use_slave' defines reader mode. Asynchronous reader
+ will be used if 'use_slave' is True and synchronous reader otherwise.
+ """
+ return IMPL.select_db_reader_mode(f)
+
+
+###################
+
+
+def service_destroy(context, service_id):
+ """Destroy the service or raise if it does not exist."""
+ return IMPL.service_destroy(context, service_id)
+
+
+def service_get(context, service_id):
+ """Get a service or raise if it does not exist."""
+ return IMPL.service_get(context, service_id)
+
+
+def service_get_by_uuid(context, service_uuid):
+ """Get a service by it's uuid or raise ServiceNotFound if it does not
+ exist.
+ """
+ return IMPL.service_get_by_uuid(context, service_uuid)
+
+
+def service_get_minimum_version(context, binary):
+ """Get the minimum service version in the database."""
+ return IMPL.service_get_minimum_version(context, binary)
+
+
+def service_get_by_host_and_topic(context, host, topic):
+ """Get a service by hostname and topic it listens to."""
+ return IMPL.service_get_by_host_and_topic(context, host, topic)
+
+
+def service_get_by_topic(context, topic):
+ """Get a service by hostname and topic it listens to."""
+ return IMPL.service_get_by_topic(context, topic)
+
+
+def service_get_by_host_and_binary(context, host, binary):
+ """Get a service by hostname and binary."""
+ return IMPL.service_get_by_host_and_binary(context, host, binary)
+
+
+def service_get_all(context, disabled=None):
+ """Get all services."""
+ return IMPL.service_get_all(context, disabled)
+
+
+def service_get_all_by_topic(context, topic):
+ """Get all services for a given topic."""
+ return IMPL.service_get_all_by_topic(context, topic)
+
+
+def service_get_all_by_binary(context, binary, include_disabled=False):
+ """Get services for a given binary.
+
+ Includes disabled services if 'include_disabled' parameter is True
+ """
+ return IMPL.service_get_all_by_binary(context, binary,
+ include_disabled=include_disabled)
+
+
+def service_get_all_computes_by_hv_type(context, hv_type,
+ include_disabled=False):
+ """Get all compute services for a given hypervisor type.
+
+ Includes disabled services if 'include_disabled' parameter is True.
+ """
+ return IMPL.service_get_all_computes_by_hv_type(context, hv_type,
+ include_disabled=include_disabled)
+
+
+def service_get_all_by_host(context, host):
+ """Get all services for a given host."""
+ return IMPL.service_get_all_by_host(context, host)
+
+
+def service_get_by_compute_host(context, host):
+ """Get the service entry for a given compute host.
+
+ Returns the service entry joined with the compute_node entry.
+ """
+ return IMPL.service_get_by_compute_host(context, host)
+
+
+def service_create(context, values):
+ """Create a service from the values dictionary."""
+ return IMPL.service_create(context, values)
+
+
+def service_update(context, service_id, values):
+ """Set the given properties on a service and update it.
+
+ Raises NotFound if service does not exist.
+
+ """
+ return IMPL.service_update(context, service_id, values)
+
+
+###################
+
+
+def compute_node_get(context, compute_id):
+ """Get a compute node by its id.
+
+ :param context: The security context
+ :param compute_id: ID of the compute node
+
+ :returns: Dictionary-like object containing properties of the compute node
+
+ Raises ComputeHostNotFound if compute node with the given ID doesn't exist.
+ """
+ return IMPL.compute_node_get(context, compute_id)
+
+
+# TODO(edleafe): remove once the compute node resource provider migration is
+# complete, and this distinction is no longer necessary.
+def compute_node_get_model(context, compute_id):
+ """Get a compute node sqlalchemy model object by its id.
+
+ :param context: The security context
+ :param compute_id: ID of the compute node
+
+ :returns: Sqlalchemy model object containing properties of the compute node
+
+ Raises ComputeHostNotFound if compute node with the given ID doesn't exist.
+ """
+ return IMPL.compute_node_get_model(context, compute_id)
+
+
+def compute_nodes_get_by_service_id(context, service_id):
+ """Get a list of compute nodes by their associated service id.
+
+ :param context: The security context
+ :param service_id: ID of the associated service
+
+ :returns: List of dictionary-like objects, each containing properties of
+ the compute node, including its corresponding service and
+ statistics
+
+ Raises ServiceNotFound if service with the given ID doesn't exist.
+ """
+ return IMPL.compute_nodes_get_by_service_id(context, service_id)
+
+
+def compute_node_get_by_host_and_nodename(context, host, nodename):
+ """Get a compute node by its associated host and nodename.
+
+ :param context: The security context (admin)
+ :param host: Name of the host
+ :param nodename: Name of the node
+
+ :returns: Dictionary-like object containing properties of the compute node,
+ including its statistics
+
+ Raises ComputeHostNotFound if host with the given name doesn't exist.
+ """
+ return IMPL.compute_node_get_by_host_and_nodename(context, host, nodename)
+
+
+def compute_node_get_all(context):
+ """Get all computeNodes.
+
+ :param context: The security context
+
+ :returns: List of dictionaries each containing compute node properties
+ """
+ return IMPL.compute_node_get_all(context)
+
+
+def compute_node_get_all_mapped_less_than(context, mapped_less_than):
+ """Get all ComputeNode objects with specific mapped values.
+
+ :param context: The security context
+ :param mapped_less_than: Get compute nodes with mapped less than this
+ value
+
+ :returns: List of dictionaries each containing compute node properties
+ """
+ return IMPL.compute_node_get_all_mapped_less_than(context,
+ mapped_less_than)
+
+
+def compute_node_get_all_by_pagination(context, limit=None, marker=None):
+ """Get compute nodes by pagination.
+ :param context: The security context
+ :param limit: Maximum number of items to return
+ :param marker: The last item of the previous page, the next results after
+ this value will be returned
+
+ :returns: List of dictionaries each containing compute node properties
+ """
+ return IMPL.compute_node_get_all_by_pagination(context,
+ limit=limit, marker=marker)
+
+
+def compute_node_get_all_by_host(context, host):
+ """Get compute nodes by host name
+
+ :param context: The security context (admin)
+ :param host: Name of the host
+
+ :returns: List of dictionaries each containing compute node properties
+ """
+ return IMPL.compute_node_get_all_by_host(context, host)
+
+
+def compute_node_search_by_hypervisor(context, hypervisor_match):
+ """Get compute nodes by hypervisor hostname.
+
+ :param context: The security context
+ :param hypervisor_match: The hypervisor hostname
+
+ :returns: List of dictionary-like objects each containing compute node
+ properties
+ """
+ return IMPL.compute_node_search_by_hypervisor(context, hypervisor_match)
+
+
+def compute_node_create(context, values):
+ """Create a compute node from the values dictionary.
+
+ :param context: The security context
+ :param values: Dictionary containing compute node properties
+
+ :returns: Dictionary-like object containing the properties of the created
+ node, including its corresponding service and statistics
+ """
+ return IMPL.compute_node_create(context, values)
+
+
+def compute_node_update(context, compute_id, values):
+ """Set the given properties on a compute node and update it.
+
+ :param context: The security context
+ :param compute_id: ID of the compute node
+ :param values: Dictionary containing compute node properties to be updated
+
+ :returns: Dictionary-like object containing the properties of the updated
+ compute node, including its corresponding service and statistics
+
+ Raises ComputeHostNotFound if compute node with the given ID doesn't exist.
+ """
+ return IMPL.compute_node_update(context, compute_id, values)
+
+
+def compute_node_delete(context, compute_id):
+ """Delete a compute node from the database.
+
+ :param context: The security context
+ :param compute_id: ID of the compute node
+
+ Raises ComputeHostNotFound if compute node with the given ID doesn't exist.
+ """
+ return IMPL.compute_node_delete(context, compute_id)
+
+
+def compute_node_statistics(context):
+ """Get aggregate statistics over all compute nodes.
+
+ :param context: The security context
+
+ :returns: Dictionary containing compute node characteristics summed up
+ over all the compute nodes, e.g. 'vcpus', 'free_ram_mb' etc.
+ """
+ return IMPL.compute_node_statistics(context)
+
+
+###################
+
+
+def certificate_create(context, values):
+ """Create a certificate from the values dictionary."""
+ return IMPL.certificate_create(context, values)
+
+
+def certificate_get_all_by_project(context, project_id):
+ """Get all certificates for a project."""
+ return IMPL.certificate_get_all_by_project(context, project_id)
+
+
+def certificate_get_all_by_user(context, user_id):
+ """Get all certificates for a user."""
+ return IMPL.certificate_get_all_by_user(context, user_id)
+
+
+def certificate_get_all_by_user_and_project(context, user_id, project_id):
+ """Get all certificates for a user and project."""
+ return IMPL.certificate_get_all_by_user_and_project(context,
+ user_id,
+ project_id)
+
+
+###################
+
+def floating_ip_get(context, id):
+ return IMPL.floating_ip_get(context, id)
+
+
+def floating_ip_get_pools(context):
+ """Returns a list of floating IP pools."""
+ return IMPL.floating_ip_get_pools(context)
+
+
+def floating_ip_allocate_address(context, project_id, pool,
+ auto_assigned=False):
+ """Allocate free floating IP from specified pool and return the address.
+
+ Raises if one is not available.
+
+ """
+ return IMPL.floating_ip_allocate_address(context, project_id, pool,
+ auto_assigned)
+
+
+def floating_ip_bulk_create(context, ips, want_result=True):
+ """Create a lot of floating IPs from the values dictionary.
+ :param want_result: If set to True, return floating IPs inserted
+ """
+ return IMPL.floating_ip_bulk_create(context, ips, want_result=want_result)
+
+
+def floating_ip_bulk_destroy(context, ips):
+ """Destroy a lot of floating IPs from the values dictionary."""
+ return IMPL.floating_ip_bulk_destroy(context, ips)
+
+
+def floating_ip_create(context, values):
+ """Create a floating IP from the values dictionary."""
+ return IMPL.floating_ip_create(context, values)
+
+
+def floating_ip_deallocate(context, address):
+ """Deallocate a floating IP by address."""
+ return IMPL.floating_ip_deallocate(context, address)
+
+
+def floating_ip_destroy(context, address):
+ """Destroy the floating_ip or raise if it does not exist."""
+ return IMPL.floating_ip_destroy(context, address)
+
+
+def floating_ip_disassociate(context, address):
+ """Disassociate a floating IP from a fixed IP by address.
+
+ :returns: the fixed IP record joined to network record or None
+ if the IP was not associated to an IP.
+
+ """
+ return IMPL.floating_ip_disassociate(context, address)
+
+
+def floating_ip_fixed_ip_associate(context, floating_address,
+ fixed_address, host):
+ """Associate a floating IP to a fixed_ip by address.
+
+ :returns: the fixed IP record joined to network record or None
+ if the IP was already associated to the fixed IP.
+ """
+
+ return IMPL.floating_ip_fixed_ip_associate(context,
+ floating_address,
+ fixed_address,
+ host)
+
+
+def floating_ip_get_all(context):
+ """Get all floating IPs."""
+ return IMPL.floating_ip_get_all(context)
+
+
+def floating_ip_get_all_by_host(context, host):
+ """Get all floating IPs by host."""
+ return IMPL.floating_ip_get_all_by_host(context, host)
+
+
+def floating_ip_get_all_by_project(context, project_id):
+ """Get all floating IPs by project."""
+ return IMPL.floating_ip_get_all_by_project(context, project_id)
+
+
+def floating_ip_get_by_address(context, address):
+ """Get a floating IP by address or raise if it doesn't exist."""
+ return IMPL.floating_ip_get_by_address(context, address)
+
+
+def floating_ip_get_by_fixed_address(context, fixed_address):
+ """Get a floating IPs by fixed address."""
+ return IMPL.floating_ip_get_by_fixed_address(context, fixed_address)
+
+
+def floating_ip_get_by_fixed_ip_id(context, fixed_ip_id):
+ """Get a floating IPs by fixed address."""
+ return IMPL.floating_ip_get_by_fixed_ip_id(context, fixed_ip_id)
+
+
+def floating_ip_update(context, address, values):
+ """Update a floating IP by address or raise if it doesn't exist."""
+ return IMPL.floating_ip_update(context, address, values)
+
+
+def dnsdomain_get_all(context):
+ """Get a list of all dnsdomains in our database."""
+ return IMPL.dnsdomain_get_all(context)
+
+
+def dnsdomain_register_for_zone(context, fqdomain, zone):
+ """Associated a DNS domain with an availability zone."""
+ return IMPL.dnsdomain_register_for_zone(context, fqdomain, zone)
+
+
+def dnsdomain_register_for_project(context, fqdomain, project):
+ """Associated a DNS domain with a project id."""
+ return IMPL.dnsdomain_register_for_project(context, fqdomain, project)
+
+
+def dnsdomain_unregister(context, fqdomain):
+ """Purge associations for the specified DNS zone."""
+ return IMPL.dnsdomain_unregister(context, fqdomain)
+
+
+def dnsdomain_get(context, fqdomain):
+ """Get the db record for the specified domain."""
+ return IMPL.dnsdomain_get(context, fqdomain)
+
+
+####################
+
+
+def migration_update(context, id, values):
+ """Update a migration instance."""
+ return IMPL.migration_update(context, id, values)
+
+
+def migration_create(context, values):
+ """Create a migration record."""
+ return IMPL.migration_create(context, values)
+
+
+def migration_get(context, migration_id):
+ """Finds a migration by the id."""
+ return IMPL.migration_get(context, migration_id)
+
+
+def migration_get_by_uuid(context, migration_uuid):
+ """Finds a migration by the migration uuid."""
+ return IMPL.migration_get_by_uuid(context, migration_uuid)
+
+
+def migration_get_by_id_and_instance(context, migration_id, instance_uuid):
+ """Finds a migration by the migration id and the instance uuid."""
+ return IMPL.migration_get_by_id_and_instance(context,
+ migration_id,
+ instance_uuid)
+
+
+def migration_get_by_instance_and_status(context, instance_uuid, status):
+ """Finds a migration by the instance uuid its migrating."""
+ return IMPL.migration_get_by_instance_and_status(context, instance_uuid,
+ status)
+
+
+def migration_get_unconfirmed_by_dest_compute(context, confirm_window,
+ dest_compute):
+ """Finds all unconfirmed migrations within the confirmation window for
+ a specific destination compute host.
+ """
+ return IMPL.migration_get_unconfirmed_by_dest_compute(context,
+ confirm_window, dest_compute)
+
+
+def migration_get_in_progress_by_host_and_node(context, host, node):
+ """Finds all migrations for the given host + node that are not yet
+ confirmed or reverted.
+ """
+ return IMPL.migration_get_in_progress_by_host_and_node(context, host, node)
+
+
+def migration_get_all_by_filters(context, filters, sort_keys=None,
+ sort_dirs=None, limit=None, marker=None):
+ """Finds all migrations using the provided filters."""
+ return IMPL.migration_get_all_by_filters(context, filters,
+ sort_keys=sort_keys,
+ sort_dirs=sort_dirs,
+ limit=limit, marker=marker)
+
+
+def migration_get_in_progress_by_instance(context, instance_uuid,
+ migration_type=None):
+ """Finds all migrations of an instance in progress."""
+ return IMPL.migration_get_in_progress_by_instance(context, instance_uuid,
+ migration_type)
+
+
+def migration_get_by_sort_filters(context, sort_keys, sort_dirs, values):
+ """Get the uuid of the first migration in a sort order.
+
+ Return the first migration (uuid) of the set where each column value
+ is greater than or equal to the matching one in @values, for each key
+ in @sort_keys.
+ """
+ return IMPL.migration_get_by_sort_filters(context, sort_keys, sort_dirs,
+ values)
+
+
+####################
+
+
+def fixed_ip_associate(context, address, instance_uuid, network_id=None,
+ reserved=False, virtual_interface_id=None):
+ """Associate fixed IP to instance.
+
+ Raises if fixed IP is not available.
+
+ """
+ return IMPL.fixed_ip_associate(context, address, instance_uuid, network_id,
+ reserved, virtual_interface_id)
+
+
+def fixed_ip_associate_pool(context, network_id, instance_uuid=None,
+ host=None, virtual_interface_id=None):
+ """Find free IP in network and associate it to instance or host.
+
+ Raises if one is not available.
+
+ """
+ return IMPL.fixed_ip_associate_pool(context, network_id,
+ instance_uuid, host,
+ virtual_interface_id)
+
+
+def fixed_ip_create(context, values):
+ """Create a fixed IP from the values dictionary."""
+ return IMPL.fixed_ip_create(context, values)
+
+
+def fixed_ip_bulk_create(context, ips):
+ """Create a lot of fixed IPs from the values dictionary."""
+ return IMPL.fixed_ip_bulk_create(context, ips)
+
+
+def fixed_ip_disassociate(context, address):
+ """Disassociate a fixed IP from an instance by address."""
+ return IMPL.fixed_ip_disassociate(context, address)
+
+
+def fixed_ip_disassociate_all_by_timeout(context, host, time):
+ """Disassociate old fixed IPs from host."""
+ return IMPL.fixed_ip_disassociate_all_by_timeout(context, host, time)
+
+
+def fixed_ip_get(context, id, get_network=False):
+ """Get fixed IP by id or raise if it does not exist.
+
+ If get_network is true, also return the associated network.
+ """
+ return IMPL.fixed_ip_get(context, id, get_network)
+
+
+def fixed_ip_get_all(context):
+ """Get all defined fixed IPs."""
+ return IMPL.fixed_ip_get_all(context)
+
+
+def fixed_ip_get_by_address(context, address, columns_to_join=None):
+ """Get a fixed IP by address or raise if it does not exist."""
+ return IMPL.fixed_ip_get_by_address(context, address,
+ columns_to_join=columns_to_join)
+
+
+def fixed_ip_get_by_floating_address(context, floating_address):
+ """Get a fixed IP by a floating address."""
+ return IMPL.fixed_ip_get_by_floating_address(context, floating_address)
+
+
+def fixed_ip_get_by_instance(context, instance_uuid):
+ """Get fixed IPs by instance or raise if none exist."""
+ return IMPL.fixed_ip_get_by_instance(context, instance_uuid)
+
+
+def fixed_ip_get_by_host(context, host):
+ """Get fixed IPs by compute host."""
+ return IMPL.fixed_ip_get_by_host(context, host)
+
+
+def fixed_ip_get_by_network_host(context, network_uuid, host):
+ """Get fixed IP for a host in a network."""
+ return IMPL.fixed_ip_get_by_network_host(context, network_uuid, host)
+
+
+def fixed_ips_by_virtual_interface(context, vif_id):
+ """Get fixed IPs by virtual interface or raise if none exist."""
+ return IMPL.fixed_ips_by_virtual_interface(context, vif_id)
+
+
+def fixed_ip_update(context, address, values):
+ """Create a fixed IP from the values dictionary."""
+ return IMPL.fixed_ip_update(context, address, values)
+
+
+####################
+
+
+def virtual_interface_create(context, values):
+ """Create a virtual interface record in the database."""
+ return IMPL.virtual_interface_create(context, values)
+
+
+def virtual_interface_update(context, address, values):
+ """Create a virtual interface record in the database."""
+ return IMPL.virtual_interface_update(context, address, values)
+
+
+def virtual_interface_get(context, vif_id):
+ """Gets a virtual interface from the table."""
+ return IMPL.virtual_interface_get(context, vif_id)
+
+
+def virtual_interface_get_by_address(context, address):
+ """Gets a virtual interface from the table filtering on address."""
+ return IMPL.virtual_interface_get_by_address(context, address)
+
+
+def virtual_interface_get_by_uuid(context, vif_uuid):
+ """Gets a virtual interface from the table filtering on vif uuid."""
+ return IMPL.virtual_interface_get_by_uuid(context, vif_uuid)
+
+
+def virtual_interface_get_by_instance(context, instance_id):
+ """Gets all virtual_interfaces for instance."""
+ return IMPL.virtual_interface_get_by_instance(context, instance_id)
+
+
+def virtual_interface_get_by_instance_and_network(context, instance_id,
+ network_id):
+ """Gets all virtual interfaces for instance."""
+ return IMPL.virtual_interface_get_by_instance_and_network(context,
+ instance_id,
+ network_id)
+
+
+def virtual_interface_delete_by_instance(context, instance_id):
+ """Delete virtual interface records associated with instance."""
+ return IMPL.virtual_interface_delete_by_instance(context, instance_id)
+
+
+def virtual_interface_delete(context, id):
+ """Delete virtual interface by id."""
+ return IMPL.virtual_interface_delete(context, id)
+
+
+def virtual_interface_get_all(context):
+ """Gets all virtual interfaces from the table."""
+ return IMPL.virtual_interface_get_all(context)
+
+
+####################
+
+
+def instance_create(context, values):
+ """Create an instance from the values dictionary."""
+ return IMPL.instance_create(context, values)
+
+
+def instance_destroy(context, instance_uuid, constraint=None):
+ """Destroy the instance or raise if it does not exist."""
+ return IMPL.instance_destroy(context, instance_uuid, constraint)
+
+
+def instance_get_by_uuid(context, uuid, columns_to_join=None):
+ """Get an instance or raise if it does not exist."""
+ return IMPL.instance_get_by_uuid(context, uuid, columns_to_join)
+
+
+def instance_get(context, instance_id, columns_to_join=None):
+ """Get an instance or raise if it does not exist."""
+ return IMPL.instance_get(context, instance_id,
+ columns_to_join=columns_to_join)
+
+
+def instance_get_all(context, columns_to_join=None):
+ """Get all instances."""
+ return IMPL.instance_get_all(context, columns_to_join=columns_to_join)
+
+
+def instance_get_all_uuids_by_host(context, host):
+ """Get a list of instance uuids on host."""
+ return IMPL.instance_get_all_uuids_by_host(context, host)
+
+
+def instance_get_all_by_filters(context, filters, sort_key='created_at',
+ sort_dir='desc', limit=None, marker=None,
+ columns_to_join=None):
+ """Get all instances that match all filters."""
+ # Note: This function exists for backwards compatibility since calls to
+ # the instance layer coming in over RPC may specify the single sort
+ # key/direction values; in this case, this function is invoked instead
+ # of the 'instance_get_all_by_filters_sort' function.
+ return IMPL.instance_get_all_by_filters(context, filters, sort_key,
+ sort_dir, limit=limit,
+ marker=marker,
+ columns_to_join=columns_to_join)
+
+
+def instance_get_all_by_filters_sort(context, filters, limit=None,
+ marker=None, columns_to_join=None,
+ sort_keys=None, sort_dirs=None):
+ """Get all instances that match all filters sorted by multiple keys.
+
+ sort_keys and sort_dirs must be a list of strings.
+ """
+ return IMPL.instance_get_all_by_filters_sort(
+ context, filters, limit=limit, marker=marker,
+ columns_to_join=columns_to_join, sort_keys=sort_keys,
+ sort_dirs=sort_dirs)
+
+
+def instance_get_by_sort_filters(context, sort_keys, sort_dirs, values):
+ """Get the uuid of the first instance in a sort order.
+
+ Return the first instance (uuid) of the set where each column value
+ is greater than or equal to the matching one in @values, for each key
+ in @sort_keys.
+ """
+ return IMPL.instance_get_by_sort_filters(context, sort_keys, sort_dirs,
+ values)
+
+
+def instance_get_active_by_window_joined(context, begin, end=None,
+ project_id=None, host=None,
+ columns_to_join=None, limit=None,
+ marker=None):
+ """Get instances and joins active during a certain time window.
+
+ Specifying a project_id will filter for a certain project.
+ Specifying a host will filter for instances on a given compute host.
+ """
+ return IMPL.instance_get_active_by_window_joined(context, begin, end,
+ project_id, host,
+ columns_to_join=columns_to_join,
+ limit=limit, marker=marker)
+
+
+def instance_get_all_by_host(context, host, columns_to_join=None):
+ """Get all instances belonging to a host."""
+ return IMPL.instance_get_all_by_host(context, host, columns_to_join)
+
+
+def instance_get_all_by_host_and_node(context, host, node,
+ columns_to_join=None):
+ """Get all instances belonging to a node."""
+ return IMPL.instance_get_all_by_host_and_node(
+ context, host, node, columns_to_join=columns_to_join)
+
+
+def instance_get_all_by_host_and_not_type(context, host, type_id=None):
+ """Get all instances belonging to a host with a different type_id."""
+ return IMPL.instance_get_all_by_host_and_not_type(context, host, type_id)
+
+
+def instance_get_all_by_grantee_security_groups(context, group_ids):
+ """Get instances with rules granted to them by a list of secgroups ids."""
+ return IMPL.instance_get_all_by_grantee_security_groups(context, group_ids)
+
+
+def instance_floating_address_get_all(context, instance_uuid):
+ """Get all floating IP addresses of an instance."""
+ return IMPL.instance_floating_address_get_all(context, instance_uuid)
+
+
+# NOTE(hanlind): This method can be removed as conductor RPC API moves to v2.0.
+def instance_get_all_hung_in_rebooting(context, reboot_window):
+ """Get all instances stuck in a rebooting state."""
+ return IMPL.instance_get_all_hung_in_rebooting(context, reboot_window)
+
+
+def instance_update(context, instance_uuid, values, expected=None):
+ """Set the given properties on an instance and update it.
+
+ Raises NotFound if instance does not exist.
+
+ """
+ return IMPL.instance_update(context, instance_uuid, values,
+ expected=expected)
+
+
+def instance_update_and_get_original(context, instance_uuid, values,
+ columns_to_join=None, expected=None):
+ """Set the given properties on an instance and update it. Return
+ a shallow copy of the original instance reference, as well as the
+ updated one.
+
+ :param context: = request context object
+ :param instance_uuid: = instance id or uuid
+ :param values: = dict containing column values
+
+ :returns: a tuple of the form (old_instance_ref, new_instance_ref)
+
+ Raises NotFound if instance does not exist.
+ """
+ rv = IMPL.instance_update_and_get_original(context, instance_uuid, values,
+ columns_to_join=columns_to_join,
+ expected=expected)
+ return rv
+
+
+def instance_add_security_group(context, instance_id, security_group_id):
+ """Associate the given security group with the given instance."""
+ return IMPL.instance_add_security_group(context, instance_id,
+ security_group_id)
+
+
+def instance_remove_security_group(context, instance_id, security_group_id):
+ """Disassociate the given security group from the given instance."""
+ return IMPL.instance_remove_security_group(context, instance_id,
+ security_group_id)
+
+
+####################
+
+
+def instance_info_cache_get(context, instance_uuid):
+ """Gets an instance info cache from the table.
+
+ :param instance_uuid: = uuid of the info cache's instance
+ """
+ return IMPL.instance_info_cache_get(context, instance_uuid)
+
+
+def instance_info_cache_update(context, instance_uuid, values):
+ """Update an instance info cache record in the table.
+
+ :param instance_uuid: = uuid of info cache's instance
+ :param values: = dict containing column values to update
+ """
+ return IMPL.instance_info_cache_update(context, instance_uuid, values)
+
+
+def instance_info_cache_delete(context, instance_uuid):
+ """Deletes an existing instance_info_cache record
+
+ :param instance_uuid: = uuid of the instance tied to the cache record
+ """
+ return IMPL.instance_info_cache_delete(context, instance_uuid)
+
+
+###################
+
+
+def instance_extra_get_by_instance_uuid(context, instance_uuid, columns=None):
+ """Get the instance extra record
+
+ :param instance_uuid: = uuid of the instance tied to the topology record
+ :param columns: A list of the columns to load, or None for 'all of them'
+ """
+ return IMPL.instance_extra_get_by_instance_uuid(
+ context, instance_uuid, columns=columns)
+
+
+def instance_extra_update_by_uuid(context, instance_uuid, updates):
+ """Update the instance extra record by instance uuid
+
+ :param instance_uuid: = uuid of the instance tied to the record
+ :param updates: A dict of updates to apply
+ """
+ return IMPL.instance_extra_update_by_uuid(context, instance_uuid,
+ updates)
+
+
+###################
+
+
+def key_pair_create(context, values):
+ """Create a key_pair from the values dictionary."""
+ return IMPL.key_pair_create(context, values)
+
+
+def key_pair_destroy(context, user_id, name):
+ """Destroy the key_pair or raise if it does not exist."""
+ return IMPL.key_pair_destroy(context, user_id, name)
+
+
+def key_pair_get(context, user_id, name):
+ """Get a key_pair or raise if it does not exist."""
+ return IMPL.key_pair_get(context, user_id, name)
+
+
+def key_pair_get_all_by_user(context, user_id, limit=None, marker=None):
+ """Get all key_pairs by user."""
+ return IMPL.key_pair_get_all_by_user(
+ context, user_id, limit=limit, marker=marker)
+
+
+def key_pair_count_by_user(context, user_id):
+ """Count number of key pairs for the given user ID."""
+ return IMPL.key_pair_count_by_user(context, user_id)
+
+
+####################
+
+
+def network_associate(context, project_id, network_id=None, force=False):
+ """Associate a free network to a project."""
+ return IMPL.network_associate(context, project_id, network_id, force)
+
+
+def network_count_reserved_ips(context, network_id):
+ """Return the number of reserved IPs in the network."""
+ return IMPL.network_count_reserved_ips(context, network_id)
+
+
+def network_create_safe(context, values):
+ """Create a network from the values dict.
+
+ The network is only returned if the create succeeds. If the create violates
+ constraints because the network already exists, no exception is raised.
+
+ """
+ return IMPL.network_create_safe(context, values)
+
+
+def network_delete_safe(context, network_id):
+ """Delete network with key network_id.
+
+ This method assumes that the network is not associated with any project
+
+ """
+ return IMPL.network_delete_safe(context, network_id)
+
+
+def network_disassociate(context, network_id, disassociate_host=True,
+ disassociate_project=True):
+ """Disassociate the network from project or host
+
+ Raises if it does not exist.
+ """
+ return IMPL.network_disassociate(context, network_id, disassociate_host,
+ disassociate_project)
+
+
+def network_get(context, network_id, project_only="allow_none"):
+ """Get a network or raise if it does not exist."""
+ return IMPL.network_get(context, network_id, project_only=project_only)
+
+
+def network_get_all(context, project_only="allow_none"):
+ """Return all defined networks."""
+ return IMPL.network_get_all(context, project_only)
+
+
+def network_get_all_by_uuids(context, network_uuids,
+ project_only="allow_none"):
+ """Return networks by ids."""
+ return IMPL.network_get_all_by_uuids(context, network_uuids,
+ project_only=project_only)
+
+
+def network_in_use_on_host(context, network_id, host=None):
+ """Indicates if a network is currently in use on host."""
+ return IMPL.network_in_use_on_host(context, network_id, host)
+
+
+def network_get_associated_fixed_ips(context, network_id, host=None):
+ """Get all network's IPs that have been associated."""
+ return IMPL.network_get_associated_fixed_ips(context, network_id, host)
+
+
+def network_get_by_uuid(context, uuid):
+ """Get a network by uuid or raise if it does not exist."""
+ return IMPL.network_get_by_uuid(context, uuid)
+
+
+def network_get_by_cidr(context, cidr):
+ """Get a network by cidr or raise if it does not exist."""
+ return IMPL.network_get_by_cidr(context, cidr)
+
+
+def network_get_all_by_host(context, host):
+ """All networks for which the given host is the network host."""
+ return IMPL.network_get_all_by_host(context, host)
+
+
+def network_set_host(context, network_id, host_id):
+ """Safely set the host for network."""
+ return IMPL.network_set_host(context, network_id, host_id)
+
+
+def network_update(context, network_id, values):
+ """Set the given properties on a network and update it.
+
+ Raises NotFound if network does not exist.
+
+ """
+ return IMPL.network_update(context, network_id, values)
+
+
+###############
+
+
+def quota_create(context, project_id, resource, limit, user_id=None):
+ """Create a quota for the given project and resource."""
+ return IMPL.quota_create(context, project_id, resource, limit,
+ user_id=user_id)
+
+
+def quota_get(context, project_id, resource, user_id=None):
+ """Retrieve a quota or raise if it does not exist."""
+ return IMPL.quota_get(context, project_id, resource, user_id=user_id)
+
+
+def quota_get_all_by_project_and_user(context, project_id, user_id):
+ """Retrieve all quotas associated with a given project and user."""
+ return IMPL.quota_get_all_by_project_and_user(context, project_id, user_id)
+
+
+def quota_get_all_by_project(context, project_id):
+ """Retrieve all quotas associated with a given project."""
+ return IMPL.quota_get_all_by_project(context, project_id)
+
+
+def quota_get_per_project_resources():
+ """Retrieve the names of resources whose quotas are calculated on a
+ per-project rather than a per-user basis.
+ """
+ return IMPL.quota_get_per_project_resources()
+
+
+def quota_get_all(context, project_id):
+ """Retrieve all user quotas associated with a given project."""
+ return IMPL.quota_get_all(context, project_id)
+
+
+def quota_update(context, project_id, resource, limit, user_id=None):
+ """Update a quota or raise if it does not exist."""
+ return IMPL.quota_update(context, project_id, resource, limit,
+ user_id=user_id)
+
+
+###################
+
+
+def quota_class_create(context, class_name, resource, limit):
+ """Create a quota class for the given name and resource."""
+ return IMPL.quota_class_create(context, class_name, resource, limit)
+
+
+def quota_class_get(context, class_name, resource):
+ """Retrieve a quota class or raise if it does not exist."""
+ return IMPL.quota_class_get(context, class_name, resource)
+
+
+def quota_class_get_default(context):
+ """Retrieve all default quotas."""
+ return IMPL.quota_class_get_default(context)
+
+
+def quota_class_get_all_by_name(context, class_name):
+ """Retrieve all quotas associated with a given quota class."""
+ return IMPL.quota_class_get_all_by_name(context, class_name)
+
+
+def quota_class_update(context, class_name, resource, limit):
+ """Update a quota class or raise if it does not exist."""
+ return IMPL.quota_class_update(context, class_name, resource, limit)
+
+
+###################
+
+
+def quota_destroy_all_by_project_and_user(context, project_id, user_id):
+ """Destroy all quotas associated with a given project and user."""
+ return IMPL.quota_destroy_all_by_project_and_user(context,
+ project_id, user_id)
+
+
+def quota_destroy_all_by_project(context, project_id):
+ """Destroy all quotas associated with a given project."""
+ return IMPL.quota_destroy_all_by_project(context, project_id)
+
+
+###################
+
+
+def ec2_volume_create(context, volume_id, forced_id=None):
+ return IMPL.ec2_volume_create(context, volume_id, forced_id)
+
+
+def ec2_volume_get_by_id(context, volume_id):
+ return IMPL.ec2_volume_get_by_id(context, volume_id)
+
+
+def ec2_volume_get_by_uuid(context, volume_uuid):
+ return IMPL.ec2_volume_get_by_uuid(context, volume_uuid)
+
+
+def ec2_snapshot_create(context, snapshot_id, forced_id=None):
+ return IMPL.ec2_snapshot_create(context, snapshot_id, forced_id)
+
+
+def ec2_snapshot_get_by_ec2_id(context, ec2_id):
+ return IMPL.ec2_snapshot_get_by_ec2_id(context, ec2_id)
+
+
+def ec2_snapshot_get_by_uuid(context, snapshot_uuid):
+ return IMPL.ec2_snapshot_get_by_uuid(context, snapshot_uuid)
+
+
+####################
+
+
+def block_device_mapping_create(context, values, legacy=True):
+ """Create an entry of block device mapping."""
+ return IMPL.block_device_mapping_create(context, values, legacy)
+
+
+def block_device_mapping_update(context, bdm_id, values, legacy=True):
+ """Update an entry of block device mapping."""
+ return IMPL.block_device_mapping_update(context, bdm_id, values, legacy)
+
+
+def block_device_mapping_update_or_create(context, values, legacy=True):
+ """Update an entry of block device mapping.
+
+ If not existed, create a new entry
+ """
+ return IMPL.block_device_mapping_update_or_create(context, values, legacy)
+
+
+def block_device_mapping_get_all_by_instance_uuids(context, instance_uuids):
+ """Get all block device mapping belonging to a list of instances."""
+ return IMPL.block_device_mapping_get_all_by_instance_uuids(context,
+ instance_uuids)
+
+
+def block_device_mapping_get_all_by_instance(context, instance_uuid):
+ """Get all block device mapping belonging to an instance."""
+ return IMPL.block_device_mapping_get_all_by_instance(context,
+ instance_uuid)
+
+
+def block_device_mapping_get_all_by_volume_id(context, volume_id,
+ columns_to_join=None):
+ """Get block device mapping for a given volume."""
+ return IMPL.block_device_mapping_get_all_by_volume_id(context, volume_id,
+ columns_to_join)
+
+
+def block_device_mapping_get_by_instance_and_volume_id(context, volume_id,
+ instance_uuid,
+ columns_to_join=None):
+ """Get block device mapping for a given volume ID and instance UUID."""
+ return IMPL.block_device_mapping_get_by_instance_and_volume_id(
+ context, volume_id, instance_uuid, columns_to_join)
+
+
+def block_device_mapping_destroy(context, bdm_id):
+ """Destroy the block device mapping."""
+ return IMPL.block_device_mapping_destroy(context, bdm_id)
+
+
+def block_device_mapping_destroy_by_instance_and_device(context, instance_uuid,
+ device_name):
+ """Destroy the block device mapping."""
+ return IMPL.block_device_mapping_destroy_by_instance_and_device(
+ context, instance_uuid, device_name)
+
+
+def block_device_mapping_destroy_by_instance_and_volume(context, instance_uuid,
+ volume_id):
+ """Destroy the block device mapping."""
+ return IMPL.block_device_mapping_destroy_by_instance_and_volume(
+ context, instance_uuid, volume_id)
+
+
+####################
+
+
+def security_group_get_all(context):
+ """Get all security groups."""
+ return IMPL.security_group_get_all(context)
+
+
+def security_group_get(context, security_group_id, columns_to_join=None):
+ """Get security group by its id."""
+ return IMPL.security_group_get(context, security_group_id,
+ columns_to_join)
+
+
+def security_group_get_by_name(context, project_id, group_name,
+ columns_to_join=None):
+ """Returns a security group with the specified name from a project."""
+ return IMPL.security_group_get_by_name(context, project_id, group_name,
+ columns_to_join=None)
+
+
+def security_group_get_by_project(context, project_id):
+ """Get all security groups belonging to a project."""
+ return IMPL.security_group_get_by_project(context, project_id)
+
+
+def security_group_get_by_instance(context, instance_uuid):
+ """Get security groups to which the instance is assigned."""
+ return IMPL.security_group_get_by_instance(context, instance_uuid)
+
+
+def security_group_in_use(context, group_id):
+ """Indicates if a security group is currently in use."""
+ return IMPL.security_group_in_use(context, group_id)
+
+
+def security_group_create(context, values):
+ """Create a new security group."""
+ return IMPL.security_group_create(context, values)
+
+
+def security_group_update(context, security_group_id, values,
+ columns_to_join=None):
+ """Update a security group."""
+ return IMPL.security_group_update(context, security_group_id, values,
+ columns_to_join=columns_to_join)
+
+
+def security_group_ensure_default(context):
+ """Ensure default security group exists for a project_id.
+
+ Returns a tuple with the first element being a bool indicating
+ if the default security group previously existed. Second
+ element is the dict used to create the default security group.
+ """
+ return IMPL.security_group_ensure_default(context)
+
+
+def security_group_destroy(context, security_group_id):
+ """Deletes a security group."""
+ return IMPL.security_group_destroy(context, security_group_id)
+
+
+####################
+
+
+def security_group_rule_create(context, values):
+ """Create a new security group."""
+ return IMPL.security_group_rule_create(context, values)
+
+
+def security_group_rule_get_by_security_group(context, security_group_id,
+ columns_to_join=None):
+ """Get all rules for a given security group."""
+ return IMPL.security_group_rule_get_by_security_group(
+ context, security_group_id, columns_to_join=columns_to_join)
+
+
+def security_group_rule_get_by_instance(context, instance_uuid):
+ """Get all rules for a given instance."""
+ return IMPL.security_group_rule_get_by_instance(context, instance_uuid)
+
+
+def security_group_rule_destroy(context, security_group_rule_id):
+ """Deletes a security group rule."""
+ return IMPL.security_group_rule_destroy(context, security_group_rule_id)
+
+
+def security_group_rule_get(context, security_group_rule_id):
+ """Gets a security group rule."""
+ return IMPL.security_group_rule_get(context, security_group_rule_id)
+
+
+def security_group_rule_count_by_group(context, security_group_id):
+ """Count rules in a given security group."""
+ return IMPL.security_group_rule_count_by_group(context, security_group_id)
+
+
+###################
+
+
+def security_group_default_rule_get(context, security_group_rule_default_id):
+ return IMPL.security_group_default_rule_get(context,
+ security_group_rule_default_id)
+
+
+def security_group_default_rule_destroy(context,
+ security_group_rule_default_id):
+ return IMPL.security_group_default_rule_destroy(
+ context, security_group_rule_default_id)
+
+
+def security_group_default_rule_create(context, values):
+ return IMPL.security_group_default_rule_create(context, values)
+
+
+def security_group_default_rule_list(context):
+ return IMPL.security_group_default_rule_list(context)
+
+
+###################
+
+
+def provider_fw_rule_create(context, rule):
+ """Add a firewall rule at the provider level (all hosts & instances)."""
+ return IMPL.provider_fw_rule_create(context, rule)
+
+
+def provider_fw_rule_get_all(context):
+ """Get all provider-level firewall rules."""
+ return IMPL.provider_fw_rule_get_all(context)
+
+
+def provider_fw_rule_destroy(context, rule_id):
+ """Delete a provider firewall rule from the database."""
+ return IMPL.provider_fw_rule_destroy(context, rule_id)
+
+
+###################
+
+
+def project_get_networks(context, project_id, associate=True):
+ """Return the network associated with the project.
+
+ If associate is true, it will attempt to associate a new
+ network if one is not found, otherwise it returns None.
+
+ """
+ return IMPL.project_get_networks(context, project_id, associate)
+
+
+###################
+
+
+def console_pool_create(context, values):
+ """Create console pool."""
+ return IMPL.console_pool_create(context, values)
+
+
+def console_pool_get_by_host_type(context, compute_host, proxy_host,
+ console_type):
+ """Fetch a console pool for a given proxy host, compute host, and type."""
+ return IMPL.console_pool_get_by_host_type(context,
+ compute_host,
+ proxy_host,
+ console_type)
+
+
+def console_pool_get_all_by_host_type(context, host, console_type):
+ """Fetch all pools for given proxy host and type."""
+ return IMPL.console_pool_get_all_by_host_type(context,
+ host,
+ console_type)
+
+
+def console_create(context, values):
+ """Create a console."""
+ return IMPL.console_create(context, values)
+
+
+def console_delete(context, console_id):
+ """Delete a console."""
+ return IMPL.console_delete(context, console_id)
+
+
+def console_get_by_pool_instance(context, pool_id, instance_uuid):
+ """Get console entry for a given instance and pool."""
+ return IMPL.console_get_by_pool_instance(context, pool_id, instance_uuid)
+
+
+def console_get_all_by_instance(context, instance_uuid, columns_to_join=None):
+ """Get consoles for a given instance."""
+ return IMPL.console_get_all_by_instance(context, instance_uuid,
+ columns_to_join)
+
+
+def console_get(context, console_id, instance_uuid=None):
+ """Get a specific console (possibly on a given instance)."""
+ return IMPL.console_get(context, console_id, instance_uuid)
+
+##################
+
+
+def pci_device_get_by_addr(context, node_id, dev_addr):
+ """Get PCI device by address."""
+ return IMPL.pci_device_get_by_addr(context, node_id, dev_addr)
+
+
+def pci_device_get_by_id(context, id):
+ """Get PCI device by id."""
+ return IMPL.pci_device_get_by_id(context, id)
+
+
+def pci_device_get_all_by_node(context, node_id):
+ """Get all PCI devices for one host."""
+ return IMPL.pci_device_get_all_by_node(context, node_id)
+
+
+def pci_device_get_all_by_instance_uuid(context, instance_uuid):
+ """Get PCI devices allocated to instance."""
+ return IMPL.pci_device_get_all_by_instance_uuid(context, instance_uuid)
+
+
+def pci_device_get_all_by_parent_addr(context, node_id, parent_addr):
+ """Get all PCI devices by parent address."""
+ return IMPL.pci_device_get_all_by_parent_addr(context, node_id,
+ parent_addr)
+
+
+def pci_device_destroy(context, node_id, address):
+ """Delete a PCI device record."""
+ return IMPL.pci_device_destroy(context, node_id, address)
+
+
+def pci_device_update(context, node_id, address, value):
+ """Update a pci device."""
+ return IMPL.pci_device_update(context, node_id, address, value)
+
+
+###################
+
+def cell_create(context, values):
+ """Create a new child Cell entry."""
+ return IMPL.cell_create(context, values)
+
+
+def cell_update(context, cell_name, values):
+ """Update a child Cell entry."""
+ return IMPL.cell_update(context, cell_name, values)
+
+
+def cell_delete(context, cell_name):
+ """Delete a child Cell."""
+ return IMPL.cell_delete(context, cell_name)
+
+
+def cell_get(context, cell_name):
+ """Get a specific child Cell."""
+ return IMPL.cell_get(context, cell_name)
+
+
+def cell_get_all(context):
+ """Get all child Cells."""
+ return IMPL.cell_get_all(context)
+
+
+####################
+
+
+def instance_metadata_get(context, instance_uuid):
+ """Get all metadata for an instance."""
+ return IMPL.instance_metadata_get(context, instance_uuid)
+
+
+def instance_metadata_delete(context, instance_uuid, key):
+ """Delete the given metadata item."""
+ IMPL.instance_metadata_delete(context, instance_uuid, key)
+
+
+def instance_metadata_update(context, instance_uuid, metadata, delete):
+ """Update metadata if it exists, otherwise create it."""
+ return IMPL.instance_metadata_update(context, instance_uuid,
+ metadata, delete)
+
+
+####################
+
+
+def instance_system_metadata_get(context, instance_uuid):
+ """Get all system metadata for an instance."""
+ return IMPL.instance_system_metadata_get(context, instance_uuid)
+
+
+def instance_system_metadata_update(context, instance_uuid, metadata, delete):
+ """Update metadata if it exists, otherwise create it."""
+ IMPL.instance_system_metadata_update(
+ context, instance_uuid, metadata, delete)
+
+
+####################
+
+
+def agent_build_create(context, values):
+ """Create a new agent build entry."""
+ return IMPL.agent_build_create(context, values)
+
+
+def agent_build_get_by_triple(context, hypervisor, os, architecture):
+ """Get agent build by hypervisor/OS/architecture triple."""
+ return IMPL.agent_build_get_by_triple(context, hypervisor, os,
+ architecture)
+
+
+def agent_build_get_all(context, hypervisor=None):
+ """Get all agent builds."""
+ return IMPL.agent_build_get_all(context, hypervisor)
+
+
+def agent_build_destroy(context, agent_update_id):
+ """Destroy agent build entry."""
+ IMPL.agent_build_destroy(context, agent_update_id)
+
+
+def agent_build_update(context, agent_build_id, values):
+ """Update agent build entry."""
+ IMPL.agent_build_update(context, agent_build_id, values)
+
+
+####################
+
+
+def bw_usage_get(context, uuid, start_period, mac):
+ """Return bw usage for instance and mac in a given audit period."""
+ return IMPL.bw_usage_get(context, uuid, start_period, mac)
+
+
+def bw_usage_get_by_uuids(context, uuids, start_period):
+ """Return bw usages for instance(s) in a given audit period."""
+ return IMPL.bw_usage_get_by_uuids(context, uuids, start_period)
+
+
+def bw_usage_update(context, uuid, mac, start_period, bw_in, bw_out,
+ last_ctr_in, last_ctr_out, last_refreshed=None,
+ update_cells=True):
+ """Update cached bandwidth usage for an instance's network based on mac
+ address. Creates new record if needed.
+ """
+ rv = IMPL.bw_usage_update(context, uuid, mac, start_period, bw_in,
+ bw_out, last_ctr_in, last_ctr_out, last_refreshed=last_refreshed)
+ if update_cells:
+ try:
+ cells_rpcapi.CellsAPI().bw_usage_update_at_top(context,
+ uuid, mac, start_period, bw_in, bw_out,
+ last_ctr_in, last_ctr_out, last_refreshed)
+ except Exception:
+ LOG.exception("Failed to notify cells of bw_usage update")
+ return rv
+
+
+###################
+
+
+def vol_get_usage_by_time(context, begin):
+ """Return volumes usage that have been updated after a specified time."""
+ return IMPL.vol_get_usage_by_time(context, begin)
+
+
+def vol_usage_update(context, id, rd_req, rd_bytes, wr_req, wr_bytes,
+ instance_id, project_id, user_id, availability_zone,
+ update_totals=False):
+ """Update cached volume usage for a volume
+
+ Creates new record if needed.
+ """
+ return IMPL.vol_usage_update(context, id, rd_req, rd_bytes, wr_req,
+ wr_bytes, instance_id, project_id, user_id,
+ availability_zone,
+ update_totals=update_totals)
+
+
+###################
+
+
+def s3_image_get(context, image_id):
+ """Find local s3 image represented by the provided id."""
+ return IMPL.s3_image_get(context, image_id)
+
+
+def s3_image_get_by_uuid(context, image_uuid):
+ """Find local s3 image represented by the provided uuid."""
+ return IMPL.s3_image_get_by_uuid(context, image_uuid)
+
+
+def s3_image_create(context, image_uuid):
+ """Create local s3 image represented by provided uuid."""
+ return IMPL.s3_image_create(context, image_uuid)
+
+
+####################
+
+
+def instance_fault_create(context, values):
+ """Create a new Instance Fault."""
+ return IMPL.instance_fault_create(context, values)
+
+
+def instance_fault_get_by_instance_uuids(context, instance_uuids,
+ latest=False):
+ """Get all instance faults for the provided instance_uuids."""
+ return IMPL.instance_fault_get_by_instance_uuids(context, instance_uuids,
+ latest=latest)
+
+
+####################
+
+
+def action_start(context, values):
+ """Start an action for an instance."""
+ return IMPL.action_start(context, values)
+
+
+def action_finish(context, values):
+ """Finish an action for an instance."""
+ return IMPL.action_finish(context, values)
+
+
+def actions_get(context, instance_uuid, limit=None, marker=None,
+ filters=None):
+ """Get all instance actions for the provided instance and filters."""
+ return IMPL.actions_get(context, instance_uuid, limit, marker, filters)
+
+
+def action_get_by_request_id(context, uuid, request_id):
+ """Get the action by request_id and given instance."""
+ return IMPL.action_get_by_request_id(context, uuid, request_id)
+
+
+def action_event_start(context, values):
+ """Start an event on an instance action."""
+ return IMPL.action_event_start(context, values)
+
+
+def action_event_finish(context, values):
+ """Finish an event on an instance action."""
+ return IMPL.action_event_finish(context, values)
+
+
+def action_events_get(context, action_id):
+ """Get the events by action id."""
+ return IMPL.action_events_get(context, action_id)
+
+
+def action_event_get_by_id(context, action_id, event_id):
+ return IMPL.action_event_get_by_id(context, action_id, event_id)
+
+
+####################
+
+
+def get_instance_uuid_by_ec2_id(context, ec2_id):
+ """Get uuid through ec2 id from instance_id_mappings table."""
+ return IMPL.get_instance_uuid_by_ec2_id(context, ec2_id)
+
+
+def ec2_instance_create(context, instance_uuid, id=None):
+ """Create the ec2 id to instance uuid mapping on demand."""
+ return IMPL.ec2_instance_create(context, instance_uuid, id)
+
+
+def ec2_instance_get_by_uuid(context, instance_uuid):
+ return IMPL.ec2_instance_get_by_uuid(context, instance_uuid)
+
+
+def ec2_instance_get_by_id(context, instance_id):
+ return IMPL.ec2_instance_get_by_id(context, instance_id)
+
+
+####################
+
+
+def task_log_end_task(context, task_name,
+ period_beginning,
+ period_ending,
+ host,
+ errors,
+ message=None):
+ """Mark a task as complete for a given host/time period."""
+ return IMPL.task_log_end_task(context, task_name,
+ period_beginning,
+ period_ending,
+ host,
+ errors,
+ message)
+
+
+def task_log_begin_task(context, task_name,
+ period_beginning,
+ period_ending,
+ host,
+ task_items=None,
+ message=None):
+ """Mark a task as started for a given host/time period."""
+ return IMPL.task_log_begin_task(context, task_name,
+ period_beginning,
+ period_ending,
+ host,
+ task_items,
+ message)
+
+
+def task_log_get_all(context, task_name, period_beginning,
+ period_ending, host=None, state=None):
+ return IMPL.task_log_get_all(context, task_name, period_beginning,
+ period_ending, host, state)
+
+
+def task_log_get(context, task_name, period_beginning,
+ period_ending, host, state=None):
+ return IMPL.task_log_get(context, task_name, period_beginning,
+ period_ending, host, state)
+
+
+####################
+
+
+def archive_deleted_rows(max_rows=None):
+ """Move up to max_rows rows from production tables to corresponding shadow
+ tables.
+
+ :returns: dict that maps table name to number of rows archived from that
+ table, for example:
+
+ ::
+
+ {
+ 'instances': 5,
+ 'block_device_mapping': 5,
+ 'pci_devices': 2,
+ }
+
+ """
+ return IMPL.archive_deleted_rows(max_rows=max_rows)
+
+
+def pcidevice_online_data_migration(context, max_count):
+ return IMPL.pcidevice_online_data_migration(context, max_count)
+
+
+def service_uuids_online_data_migration(context, max_count):
+ return IMPL.service_uuids_online_data_migration(context, max_count)
+
+
+####################
+
+
+def instance_tag_add(context, instance_uuid, tag):
+ """Add tag to the instance."""
+ return IMPL.instance_tag_add(context, instance_uuid, tag)
+
+
+def instance_tag_set(context, instance_uuid, tags):
+ """Replace all of the instance tags with specified list of tags."""
+ return IMPL.instance_tag_set(context, instance_uuid, tags)
+
+
+def instance_tag_get_by_instance_uuid(context, instance_uuid):
+ """Get all tags for a given instance."""
+ return IMPL.instance_tag_get_by_instance_uuid(context, instance_uuid)
+
+
+def instance_tag_delete(context, instance_uuid, tag):
+ """Delete specified tag from the instance."""
+ return IMPL.instance_tag_delete(context, instance_uuid, tag)
+
+
+def instance_tag_delete_all(context, instance_uuid):
+ """Delete all tags from the instance."""
+ return IMPL.instance_tag_delete_all(context, instance_uuid)
+
+
+def instance_tag_exists(context, instance_uuid, tag):
+ """Check if specified tag exist on the instance."""
+ return IMPL.instance_tag_exists(context, instance_uuid, tag)
+
+
+####################
+
+
+def console_auth_token_create(context, values):
+ """Create a console authorization."""
+ return IMPL.console_auth_token_create(context, values)
+
+
+def console_auth_token_get_valid(context, token_hash, instance_uuid=None):
+ """Get a valid console authorization by token_hash and instance_uuid.
+
+ The console authorizations expire at the time specified by their
+ 'expires' column. An expired console auth token will not be returned
+ to the caller - it is treated as if it does not exist.
+
+ If instance_uuid is specified, the token is validated against both
+ expiry and instance_uuid.
+
+ If instance_uuid is not specified, the token is validated against
+ expiry only.
+ """
+ return IMPL.console_auth_token_get_valid(context,
+ token_hash,
+ instance_uuid=instance_uuid)
+
+
+def console_auth_token_destroy_all_by_instance(context, instance_uuid):
+ """Delete all console authorizations belonging to the instance."""
+ return IMPL.console_auth_token_destroy_all_by_instance(context,
+ instance_uuid)
+
+
+def console_auth_token_destroy_expired_by_host(context, host):
+ """Delete expired console authorizations belonging to the host.
+
+ The console authorizations expire at the time specified by their
+ 'expires' column. This function is used to garbage collect expired
+ tokens associated with the given host.
+ """
+ return IMPL.console_auth_token_destroy_expired_by_host(context, host)
diff --git a/gosbs/db/base.py b/gosbs/db/base.py
new file mode 100644
index 0000000..9d61239
--- /dev/null
+++ b/gosbs/db/base.py
@@ -0,0 +1,29 @@
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/db/base.py
+
+"""Base class for classes that need database access."""
+
+import gosbs.db.api
+
+
+class Base(object):
+ """DB driver is injected in the init method."""
+
+ def __init__(self):
+ super(Base, self).__init__()
+ self.db = gosbs.db.api
diff --git a/gosbs/db/constants.py b/gosbs/db/constants.py
new file mode 100644
index 0000000..a082fba
--- /dev/null
+++ b/gosbs/db/constants.py
@@ -0,0 +1,25 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""Useful db-related constants. In their own file so they can be imported
+cleanly."""
+
+# The maximum value a signed INT type may have
+MAX_INT = 0x7FFFFFFF
+
+# NOTE(dosaboy): This is supposed to represent the maximum value that we can
+# place into a SQL single precision float so that we can check whether values
+# are oversize. Postgres and MySQL both define this as their max whereas Sqlite
+# uses dynamic typing so this would not apply. Different dbs react in different
+# ways to oversize values e.g. postgres will raise an exception while mysql
+# will round off the value. Nevertheless we may still want to know prior to
+# insert whether the value is oversize or not.
+SQL_SP_FLOAT_MAX = 3.40282e+38
diff --git a/gosbs/db/sqlalchemy/__init__.py b/gosbs/db/sqlalchemy/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gosbs/db/sqlalchemy/__init__.py
diff --git a/gosbs/db/sqlalchemy/api.py b/gosbs/db/sqlalchemy/api.py
new file mode 100644
index 0000000..6ce8589
--- /dev/null
+++ b/gosbs/db/sqlalchemy/api.py
@@ -0,0 +1,5897 @@
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/db/sqlalchemy/api.py
+
+"""Implementation of SQLAlchemy backend."""
+
+import collections
+import copy
+import datetime
+import functools
+import inspect
+import sys
+
+from oslo_db import api as oslo_db_api
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import enginefacade
+from oslo_db.sqlalchemy import update_match
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_log import log as logging
+from oslo_utils import importutils
+from oslo_utils import timeutils
+from oslo_utils import uuidutils
+import six
+from six.moves import range
+import sqlalchemy as sa
+from sqlalchemy import and_
+from sqlalchemy import Boolean
+from sqlalchemy.exc import NoSuchTableError
+from sqlalchemy.ext.compiler import compiles
+from sqlalchemy import Integer
+from sqlalchemy import MetaData
+from sqlalchemy import or_
+from sqlalchemy.orm import aliased
+from sqlalchemy.orm import contains_eager
+from sqlalchemy.orm import joinedload
+from sqlalchemy.orm import joinedload_all
+from sqlalchemy.orm import noload
+from sqlalchemy.orm import undefer
+from sqlalchemy.schema import Table
+from sqlalchemy import sql
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql.expression import cast
+from sqlalchemy.sql.expression import desc
+from sqlalchemy.sql.expression import UpdateBase
+from sqlalchemy.sql import false
+from sqlalchemy.sql import func
+from sqlalchemy.sql import null
+from sqlalchemy.sql import true
+
+import gosbs.conf
+import gosbs.context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs.i18n import _
+from gosbs import safe_utils
+
+profiler_sqlalchemy = importutils.try_import('osprofiler.sqlalchemy')
+
+CONF = gosbs.conf.CONF
+
+
+LOG = logging.getLogger(__name__)
+
+main_context_manager = enginefacade.transaction_context()
+api_context_manager = enginefacade.transaction_context()
+
+
+def _get_db_conf(conf_group, connection=None):
+ kw = dict(conf_group.items())
+ if connection is not None:
+ kw['connection'] = connection
+ return kw
+
+
+def _context_manager_from_context(context):
+ if context:
+ try:
+ return context.db_connection
+ except AttributeError:
+ pass
+
+
+def configure(conf):
+ main_context_manager.configure(**_get_db_conf(conf.database))
+ api_context_manager.configure(**_get_db_conf(conf.api_database))
+
+ if profiler_sqlalchemy and CONF.profiler.enabled \
+ and CONF.profiler.trace_sqlalchemy:
+
+ main_context_manager.append_on_engine_create(
+ lambda eng: profiler_sqlalchemy.add_tracing(sa, eng, "db"))
+ api_context_manager.append_on_engine_create(
+ lambda eng: profiler_sqlalchemy.add_tracing(sa, eng, "db"))
+
+
+def create_context_manager(connection=None):
+ """Create a database context manager object.
+
+ : param connection: The database connection string
+ """
+ ctxt_mgr = enginefacade.transaction_context()
+ ctxt_mgr.configure(**_get_db_conf(CONF.database, connection=connection))
+ return ctxt_mgr
+
+
+def get_context_manager(context):
+ """Get a database context manager object.
+
+ :param context: The request context that can contain a context manager
+ """
+ return _context_manager_from_context(context) or main_context_manager
+
+
+def get_engine(use_slave=False, context=None):
+ """Get a database engine object.
+
+ :param use_slave: Whether to use the slave connection
+ :param context: The request context that can contain a context manager
+ """
+ ctxt_mgr = get_context_manager(context)
+ if use_slave:
+ return ctxt_mgr.reader.get_engine()
+ return ctxt_mgr.writer.get_engine()
+
+
+def get_api_engine():
+ return api_context_manager.writer.get_engine()
+
+
+_SHADOW_TABLE_PREFIX = 'shadow_'
+_DEFAULT_QUOTA_NAME = 'default'
+PER_PROJECT_QUOTAS = ['fixed_ips', 'floating_ips', 'networks']
+
+
+def get_backend():
+ """The backend is this module itself."""
+ return sys.modules[__name__]
+
+
+def require_context(f):
+ """Decorator to require *any* user or admin context.
+
+ This does no authorization for user or project access matching, see
+ :py:func:`nova.context.authorize_project_context` and
+ :py:func:`nova.context.authorize_user_context`.
+
+ The first argument to the wrapped function must be the context.
+
+ """
+
+ @functools.wraps(f)
+ def wrapper(*args, **kwargs):
+ gosbs.context.require_context(args[0])
+ return f(*args, **kwargs)
+ return wrapper
+
+
+def require_instance_exists_using_uuid(f):
+ """Decorator to require the specified instance to exist.
+
+ Requires the wrapped function to use context and instance_uuid as
+ their first two arguments.
+ """
+ @functools.wraps(f)
+ def wrapper(context, instance_uuid, *args, **kwargs):
+ instance_get_by_uuid(context, instance_uuid)
+ return f(context, instance_uuid, *args, **kwargs)
+
+ return wrapper
+
+
+def select_db_reader_mode(f):
+ """Decorator to select synchronous or asynchronous reader mode.
+
+ The kwarg argument 'use_slave' defines reader mode. Asynchronous reader
+ will be used if 'use_slave' is True and synchronous reader otherwise.
+ If 'use_slave' is not specified default value 'False' will be used.
+
+ Wrapped function must have a context in the arguments.
+ """
+
+ @functools.wraps(f)
+ def wrapper(*args, **kwargs):
+ wrapped_func = safe_utils.get_wrapped_function(f)
+ keyed_args = inspect.getcallargs(wrapped_func, *args, **kwargs)
+
+ context = keyed_args['context']
+ use_slave = keyed_args.get('use_slave', False)
+
+ if use_slave:
+ reader_mode = get_context_manager(context).async_
+ else:
+ reader_mode = get_context_manager(context).reader
+
+ with reader_mode.using(context):
+ return f(*args, **kwargs)
+ return wrapper
+
+
+def pick_context_manager_writer(f):
+ """Decorator to use a writer db context manager.
+
+ The db context manager will be picked from the RequestContext.
+
+ Wrapped function must have a RequestContext in the arguments.
+ """
+ @functools.wraps(f)
+ def wrapped(context, *args, **kwargs):
+ ctxt_mgr = get_context_manager(context)
+ with ctxt_mgr.writer.using(context):
+ return f(context, *args, **kwargs)
+ return wrapped
+
+
+def pick_context_manager_reader(f):
+ """Decorator to use a reader db context manager.
+
+ The db context manager will be picked from the RequestContext.
+
+ Wrapped function must have a RequestContext in the arguments.
+ """
+ @functools.wraps(f)
+ def wrapped(context, *args, **kwargs):
+ ctxt_mgr = get_context_manager(context)
+ with ctxt_mgr.reader.using(context):
+ return f(context, *args, **kwargs)
+ return wrapped
+
+
+def pick_context_manager_reader_allow_async(f):
+ """Decorator to use a reader.allow_async db context manager.
+
+ The db context manager will be picked from the RequestContext.
+
+ Wrapped function must have a RequestContext in the arguments.
+ """
+ @functools.wraps(f)
+ def wrapped(context, *args, **kwargs):
+ ctxt_mgr = get_context_manager(context)
+ with ctxt_mgr.reader.allow_async.using(context):
+ return f(context, *args, **kwargs)
+ return wrapped
+
+
+def model_query(context, model,
+ args=None,
+ read_deleted=None,
+ project_only=False):
+ """Query helper that accounts for context's `read_deleted` field.
+
+ :param context: NovaContext of the query.
+ :param model: Model to query. Must be a subclass of ModelBase.
+ :param args: Arguments to query. If None - model is used.
+ :param read_deleted: If not None, overrides context's read_deleted field.
+ Permitted values are 'no', which does not return
+ deleted values; 'only', which only returns deleted
+ values; and 'yes', which does not filter deleted
+ values.
+ :param project_only: If set and context is user-type, then restrict
+ query to match the context's project_id. If set to
+ 'allow_none', restriction includes project_id = None.
+ """
+
+ if read_deleted is None:
+ read_deleted = context.read_deleted
+
+ query_kwargs = {}
+ if 'no' == read_deleted:
+ query_kwargs['deleted'] = False
+ elif 'only' == read_deleted:
+ query_kwargs['deleted'] = True
+ elif 'yes' == read_deleted:
+ pass
+ else:
+ raise ValueError(_("Unrecognized read_deleted value '%s'")
+ % read_deleted)
+
+ query = sqlalchemyutils.model_query(
+ model, context.session, args, **query_kwargs)
+
+ # We can't use oslo.db model_query's project_id here, as it doesn't allow
+ # us to return both our projects and unowned projects.
+ if gosbs.context.is_user_context(context) and project_only:
+ if project_only == 'allow_none':
+ query = query.\
+ filter(or_(model.project_id == context.project_id,
+ model.project_id == null()))
+ else:
+ query = query.filter_by(project_id=context.project_id)
+
+ return query
+
+
+def convert_objects_related_datetimes(values, *datetime_keys):
+ if not datetime_keys:
+ datetime_keys = ('created_at', 'deleted_at', 'updated_at')
+
+ for key in datetime_keys:
+ if key in values and values[key]:
+ if isinstance(values[key], six.string_types):
+ try:
+ values[key] = timeutils.parse_strtime(values[key])
+ except ValueError:
+ # Try alternate parsing since parse_strtime will fail
+ # with say converting '2015-05-28T19:59:38+00:00'
+ values[key] = timeutils.parse_isotime(values[key])
+ # NOTE(danms): Strip UTC timezones from datetimes, since they're
+ # stored that way in the database
+ values[key] = values[key].replace(tzinfo=None)
+ return values
+
+
+###################
+
+
+def constraint(**conditions):
+ return Constraint(conditions)
+
+
+def equal_any(*values):
+ return EqualityCondition(values)
+
+
+def not_equal(*values):
+ return InequalityCondition(values)
+
+
+class Constraint(object):
+
+ def __init__(self, conditions):
+ self.conditions = conditions
+
+ def apply(self, model, query):
+ for key, condition in self.conditions.items():
+ for clause in condition.clauses(getattr(model, key)):
+ query = query.filter(clause)
+ return query
+
+
+class EqualityCondition(object):
+
+ def __init__(self, values):
+ self.values = values
+
+ def clauses(self, field):
+ # method signature requires us to return an iterable even if for OR
+ # operator this will actually be a single clause
+ return [or_(*[field == value for value in self.values])]
+
+
+class InequalityCondition(object):
+
+ def __init__(self, values):
+ self.values = values
+
+ def clauses(self, field):
+ return [field != value for value in self.values]
+
+
+class DeleteFromSelect(UpdateBase):
+ def __init__(self, table, select, column):
+ self.table = table
+ self.select = select
+ self.column = column
+
+
+# NOTE(guochbo): some versions of MySQL doesn't yet support subquery with
+# 'LIMIT & IN/ALL/ANY/SOME' We need work around this with nesting select .
+@compiles(DeleteFromSelect)
+def visit_delete_from_select(element, compiler, **kw):
+ return "DELETE FROM %s WHERE %s in (SELECT T1.%s FROM (%s) as T1)" % (
+ compiler.process(element.table, asfrom=True),
+ compiler.process(element.column),
+ element.column.name,
+ compiler.process(element.select))
+
+###################
+
+
+@pick_context_manager_writer
+def service_destroy(context, service_id):
+ service = service_get(context, service_id)
+
+ model_query(context, models.Service).\
+ filter_by(id=service_id).\
+ soft_delete(synchronize_session=False)
+
+ # TODO(sbauza): Remove the service_id filter in a later release
+ # once we are sure that all compute nodes report the host field
+ model_query(context, models.ComputeNode).\
+ filter(or_(models.ComputeNode.service_id == service_id,
+ models.ComputeNode.host == service['host'])).\
+ soft_delete(synchronize_session=False)
+
+
+@pick_context_manager_reader
+def service_get(context, service_id):
+ query = model_query(context, models.Service).filter_by(id=service_id)
+
+ result = query.first()
+ if not result:
+ raise exception.ServiceNotFound(service_id=service_id)
+
+ return result
+
+
+@pick_context_manager_reader
+def service_get_by_uuid(context, service_uuid):
+ query = model_query(context, models.Service).filter_by(uuid=service_uuid)
+
+ result = query.first()
+ if not result:
+ raise exception.ServiceNotFound(service_id=service_uuid)
+
+ return result
+
+
+@pick_context_manager_reader_allow_async
+def service_get_minimum_version(context, binaries):
+ min_versions = context.session.query(
+ models.Service.binary,
+ func.min(models.Service.version)).\
+ filter(models.Service.binary.in_(binaries)).\
+ filter(models.Service.deleted == 0).\
+ filter(models.Service.forced_down == false()).\
+ group_by(models.Service.binary)
+ return dict(min_versions)
+
+
+@pick_context_manager_reader
+def service_get_all(context, disabled=None):
+ query = model_query(context, models.Service)
+
+ if disabled is not None:
+ query = query.filter_by(disabled=disabled)
+
+ return query.all()
+
+
+@pick_context_manager_reader
+def service_get_all_by_topic(context, topic):
+ return model_query(context, models.Service, read_deleted="no").\
+ filter_by(disabled=False).\
+ filter_by(topic=topic).\
+ all()
+
+
+@pick_context_manager_reader
+def service_get_by_host_and_topic(context, host, topic):
+ return model_query(context, models.Service, read_deleted="no").\
+ filter_by(disabled=False).\
+ filter_by(host=host).\
+ filter_by(topic=topic).\
+ first()
+
+
+@pick_context_manager_reader
+def service_get_by_topic(context, topic):
+ return model_query(context, models.Service, read_deleted="no").\
+ filter_by(disabled=False).\
+ filter_by(topic=topic).\
+ first()
+
+
+@pick_context_manager_reader
+def service_get_all_by_binary(context, binary, include_disabled=False):
+ query = model_query(context, models.Service, read_deleted="no").\
+ filter_by(binary=binary)
+ if not include_disabled:
+ query = query.filter_by(disabled=False)
+ return query.all()
+
+
+@pick_context_manager_reader
+def service_get_all_computes_by_hv_type(context, hv_type,
+ include_disabled=False):
+ query = model_query(context, models.Service, read_deleted="no").\
+ filter_by(binary='nova-scheduler')
+ if not include_disabled:
+ query = query.filter_by(disabled=False)
+ query = query.join(models.ComputeNode,
+ models.Service.host == models.ComputeNode.host).\
+ filter(models.ComputeNode.hypervisor_type == hv_type).\
+ distinct('host')
+ return query.all()
+
+
+@pick_context_manager_reader
+def service_get_by_host_and_binary(context, host, binary):
+ result = model_query(context, models.Service, read_deleted="no").\
+ filter_by(host=host).\
+ filter_by(binary=binary).\
+ first()
+
+ if not result:
+ raise exception.HostBinaryNotFound(host=host, binary=binary)
+
+ return result
+
+
+@pick_context_manager_reader
+def service_get_all_by_host(context, host):
+ return model_query(context, models.Service, read_deleted="no").\
+ filter_by(host=host).\
+ all()
+
+
+@pick_context_manager_reader_allow_async
+def service_get_by_compute_host(context, host):
+ result = model_query(context, models.Service, read_deleted="no").\
+ filter_by(host=host).\
+ filter_by(binary='gosbs-scheduler').\
+ first()
+
+ if not result:
+ raise exception.ComputeHostNotFound(host=host)
+
+ return result
+
+
+@pick_context_manager_writer
+def service_create(context, values):
+ service_ref = models.Service()
+ service_ref.update(values)
+ try:
+ service_ref.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'binary' in e.columns:
+ raise exception.ServiceBinaryExists(host=values.get('host'),
+ binary=values.get('binary'))
+ raise exception.ServiceTopicExists(host=values.get('host'),
+ topic=values.get('topic'))
+ return service_ref
+
+
+@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
+@pick_context_manager_writer
+def service_update(context, service_id, values):
+ service_ref = service_get(context, service_id)
+ # Only servicegroup.drivers.db.DbDriver._report_state() updates
+ # 'report_count', so if that value changes then store the timestamp
+ # as the last time we got a state report.
+ if 'report_count' in values:
+ if values['report_count'] > service_ref.report_count:
+ service_ref.last_seen_up = timeutils.utcnow()
+ service_ref.update(values)
+
+ return service_ref
+
+
+###################
+
+
+def _compute_node_select(context, filters=None, limit=None, marker=None):
+ if filters is None:
+ filters = {}
+
+ cn_tbl = sa.alias(models.ComputeNode.__table__, name='cn')
+ select = sa.select([cn_tbl])
+
+ if context.read_deleted == "no":
+ select = select.where(cn_tbl.c.deleted == 0)
+ if "compute_id" in filters:
+ select = select.where(cn_tbl.c.id == filters["compute_id"])
+ if "service_id" in filters:
+ select = select.where(cn_tbl.c.service_id == filters["service_id"])
+ if "host" in filters:
+ select = select.where(cn_tbl.c.host == filters["host"])
+ if "hypervisor_hostname" in filters:
+ hyp_hostname = filters["hypervisor_hostname"]
+ select = select.where(cn_tbl.c.hypervisor_hostname == hyp_hostname)
+ if "mapped" in filters:
+ select = select.where(cn_tbl.c.mapped < filters['mapped'])
+ if marker is not None:
+ try:
+ compute_node_get(context, marker)
+ except exception.ComputeHostNotFound:
+ raise exception.MarkerNotFound(marker=marker)
+ select = select.where(cn_tbl.c.id > marker)
+ if limit is not None:
+ select = select.limit(limit)
+ # Explicitly order by id, so we're not dependent on the native sort
+ # order of the underlying DB.
+ select = select.order_by(asc("id"))
+ return select
+
+
+def _compute_node_fetchall(context, filters=None, limit=None, marker=None):
+ select = _compute_node_select(context, filters, limit=limit, marker=marker)
+ engine = get_engine(context=context)
+ conn = engine.connect()
+
+ results = conn.execute(select).fetchall()
+
+ # Callers expect dict-like objects, not SQLAlchemy RowProxy objects...
+ results = [dict(r) for r in results]
+ conn.close()
+ return results
+
+
+@pick_context_manager_reader
+def compute_node_get(context, compute_id):
+ results = _compute_node_fetchall(context, {"compute_id": compute_id})
+ if not results:
+ raise exception.ComputeHostNotFound(host=compute_id)
+ return results[0]
+
+
+@pick_context_manager_reader
+def compute_node_get_model(context, compute_id):
+ # TODO(edleafe): remove once the compute node resource provider migration
+ # is complete, and this distinction is no longer necessary.
+ result = model_query(context, models.ComputeNode).\
+ filter_by(id=compute_id).\
+ first()
+ if not result:
+ raise exception.ComputeHostNotFound(host=compute_id)
+ return result
+
+
+@pick_context_manager_reader
+def compute_nodes_get_by_service_id(context, service_id):
+ results = _compute_node_fetchall(context, {"service_id": service_id})
+ if not results:
+ raise exception.ServiceNotFound(service_id=service_id)
+ return results
+
+
+@pick_context_manager_reader
+def compute_node_get_by_host_and_nodename(context, host, nodename):
+ results = _compute_node_fetchall(context,
+ {"host": host, "hypervisor_hostname": nodename})
+ if not results:
+ raise exception.ComputeHostNotFound(host=host)
+ return results[0]
+
+
+@pick_context_manager_reader_allow_async
+def compute_node_get_all_by_host(context, host):
+ results = _compute_node_fetchall(context, {"host": host})
+ if not results:
+ raise exception.ComputeHostNotFound(host=host)
+ return results
+
+
+@pick_context_manager_reader
+def compute_node_get_all(context):
+ return _compute_node_fetchall(context)
+
+
+@pick_context_manager_reader
+def compute_node_get_all_mapped_less_than(context, mapped_less_than):
+ return _compute_node_fetchall(context,
+ {'mapped': mapped_less_than})
+
+
+@pick_context_manager_reader
+def compute_node_get_all_by_pagination(context, limit=None, marker=None):
+ return _compute_node_fetchall(context, limit=limit, marker=marker)
+
+
+@pick_context_manager_reader
+def compute_node_search_by_hypervisor(context, hypervisor_match):
+ field = models.ComputeNode.hypervisor_hostname
+ return model_query(context, models.ComputeNode).\
+ filter(field.like('%%%s%%' % hypervisor_match)).\
+ all()
+
+
+@pick_context_manager_writer
+def compute_node_create(context, values):
+ """Creates a new ComputeNode and populates the capacity fields
+ with the most recent data.
+ """
+ convert_objects_related_datetimes(values)
+
+ compute_node_ref = models.ComputeNode()
+ compute_node_ref.update(values)
+ compute_node_ref.save(context.session)
+
+ return compute_node_ref
+
+
+@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
+@pick_context_manager_writer
+def compute_node_update(context, compute_id, values):
+ """Updates the ComputeNode record with the most recent data."""
+
+ compute_ref = compute_node_get_model(context, compute_id)
+ # Always update this, even if there's going to be no other
+ # changes in data. This ensures that we invalidate the
+ # scheduler cache of compute node data in case of races.
+ values['updated_at'] = timeutils.utcnow()
+ convert_objects_related_datetimes(values)
+ compute_ref.update(values)
+
+ return compute_ref
+
+
+@pick_context_manager_writer
+def compute_node_delete(context, compute_id):
+ """Delete a ComputeNode record."""
+ result = model_query(context, models.ComputeNode).\
+ filter_by(id=compute_id).\
+ soft_delete(synchronize_session=False)
+
+ if not result:
+ raise exception.ComputeHostNotFound(host=compute_id)
+
+
+@pick_context_manager_reader
+def compute_node_statistics(context):
+ """Compute statistics over all compute nodes."""
+ engine = get_engine(context=context)
+ services_tbl = models.Service.__table__
+
+ inner_sel = sa.alias(_compute_node_select(context), name='inner_sel')
+
+ # TODO(sbauza): Remove the service_id filter in a later release
+ # once we are sure that all compute nodes report the host field
+ j = sa.join(
+ inner_sel, services_tbl,
+ sql.and_(
+ sql.or_(
+ inner_sel.c.host == services_tbl.c.host,
+ inner_sel.c.service_id == services_tbl.c.id
+ ),
+ services_tbl.c.disabled == false(),
+ services_tbl.c.binary == 'gobs-scheduler',
+ services_tbl.c.deleted == 0
+ )
+ )
+
+ # NOTE(jaypipes): This COALESCE() stuff is temporary while the data
+ # migration to the new resource providers inventories and allocations
+ # tables is completed.
+ agg_cols = [
+ func.count().label('count'),
+ sql.func.sum(
+ inner_sel.c.vcpus
+ ).label('vcpus'),
+ sql.func.sum(
+ inner_sel.c.memory_mb
+ ).label('memory_mb'),
+ sql.func.sum(
+ inner_sel.c.local_gb
+ ).label('local_gb'),
+ sql.func.sum(
+ inner_sel.c.vcpus_used
+ ).label('vcpus_used'),
+ sql.func.sum(
+ inner_sel.c.memory_mb_used
+ ).label('memory_mb_used'),
+ sql.func.sum(
+ inner_sel.c.local_gb_used
+ ).label('local_gb_used'),
+ sql.func.sum(
+ inner_sel.c.free_ram_mb
+ ).label('free_ram_mb'),
+ sql.func.sum(
+ inner_sel.c.free_disk_gb
+ ).label('free_disk_gb'),
+ sql.func.sum(
+ inner_sel.c.current_workload
+ ).label('current_workload'),
+ sql.func.sum(
+ inner_sel.c.running_vms
+ ).label('running_vms'),
+ sql.func.sum(
+ inner_sel.c.disk_available_least
+ ).label('disk_available_least'),
+ ]
+ select = sql.select(agg_cols).select_from(j)
+ conn = engine.connect()
+
+ results = conn.execute(select).fetchone()
+
+ # Build a dict of the info--making no assumptions about result
+ fields = ('count', 'vcpus', 'memory_mb', 'local_gb', 'vcpus_used',
+ 'memory_mb_used', 'local_gb_used', 'free_ram_mb', 'free_disk_gb',
+ 'current_workload', 'running_vms', 'disk_available_least')
+ results = {field: int(results[idx] or 0)
+ for idx, field in enumerate(fields)}
+ conn.close()
+ return results
+
+
+###################
+
+
+@pick_context_manager_writer
+def certificate_create(context, values):
+ certificate_ref = models.Certificate()
+ for (key, value) in values.items():
+ certificate_ref[key] = value
+ certificate_ref.save(context.session)
+ return certificate_ref
+
+
+@pick_context_manager_reader
+def certificate_get_all_by_project(context, project_id):
+ return model_query(context, models.Certificate, read_deleted="no").\
+ filter_by(project_id=project_id).\
+ all()
+
+
+@pick_context_manager_reader
+def certificate_get_all_by_user(context, user_id):
+ return model_query(context, models.Certificate, read_deleted="no").\
+ filter_by(user_id=user_id).\
+ all()
+
+
+@pick_context_manager_reader
+def certificate_get_all_by_user_and_project(context, user_id, project_id):
+ return model_query(context, models.Certificate, read_deleted="no").\
+ filter_by(user_id=user_id).\
+ filter_by(project_id=project_id).\
+ all()
+
+
+###################
+
+
+@require_context
+@pick_context_manager_reader
+def floating_ip_get(context, id):
+ try:
+ result = model_query(context, models.FloatingIp, project_only=True).\
+ filter_by(id=id).\
+ options(joinedload_all('fixed_ip.instance')).\
+ first()
+
+ if not result:
+ raise exception.FloatingIpNotFound(id=id)
+ except db_exc.DBError:
+ LOG.warning("Invalid floating IP ID %s in request", id)
+ raise exception.InvalidID(id=id)
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def floating_ip_get_pools(context):
+ pools = []
+ for result in model_query(context, models.FloatingIp,
+ (models.FloatingIp.pool,)).distinct():
+ pools.append({'name': result[0]})
+ return pools
+
+
+@require_context
+@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
+@pick_context_manager_writer
+def floating_ip_allocate_address(context, project_id, pool,
+ auto_assigned=False):
+ nova.context.authorize_project_context(context, project_id)
+ floating_ip_ref = model_query(context, models.FloatingIp,
+ read_deleted="no").\
+ filter_by(fixed_ip_id=None).\
+ filter_by(project_id=None).\
+ filter_by(pool=pool).\
+ first()
+
+ if not floating_ip_ref:
+ raise exception.NoMoreFloatingIps()
+
+ params = {'project_id': project_id, 'auto_assigned': auto_assigned}
+
+ rows_update = model_query(context, models.FloatingIp, read_deleted="no").\
+ filter_by(id=floating_ip_ref['id']).\
+ filter_by(fixed_ip_id=None).\
+ filter_by(project_id=None).\
+ filter_by(pool=pool).\
+ update(params, synchronize_session='evaluate')
+
+ if not rows_update:
+ LOG.debug('The row was updated in a concurrent transaction, '
+ 'we will fetch another one')
+ raise db_exc.RetryRequest(exception.FloatingIpAllocateFailed())
+
+ return floating_ip_ref['address']
+
+
+@require_context
+@pick_context_manager_writer
+def floating_ip_bulk_create(context, ips, want_result=True):
+ try:
+ tab = models.FloatingIp().__table__
+ context.session.execute(tab.insert(), ips)
+ except db_exc.DBDuplicateEntry as e:
+ raise exception.FloatingIpExists(address=e.value)
+
+ if want_result:
+ return model_query(context, models.FloatingIp).filter(
+ models.FloatingIp.address.in_(
+ [ip['address'] for ip in ips])).all()
+
+
+def _ip_range_splitter(ips, block_size=256):
+ """Yields blocks of IPs no more than block_size elements long."""
+ out = []
+ count = 0
+ for ip in ips:
+ out.append(ip['address'])
+ count += 1
+
+ if count > block_size - 1:
+ yield out
+ out = []
+ count = 0
+
+ if out:
+ yield out
+
+
+@require_context
+@pick_context_manager_writer
+def floating_ip_bulk_destroy(context, ips):
+ project_id_to_quota_count = collections.defaultdict(int)
+ for ip_block in _ip_range_splitter(ips):
+ # Find any floating IPs that were not auto_assigned and
+ # thus need quota released.
+ query = model_query(context, models.FloatingIp).\
+ filter(models.FloatingIp.address.in_(ip_block)).\
+ filter_by(auto_assigned=False)
+ for row in query.all():
+ # The count is negative since we release quota by
+ # reserving negative quota.
+ project_id_to_quota_count[row['project_id']] -= 1
+ # Delete the floating IPs.
+ model_query(context, models.FloatingIp).\
+ filter(models.FloatingIp.address.in_(ip_block)).\
+ soft_delete(synchronize_session='fetch')
+
+
+@require_context
+@pick_context_manager_writer
+def floating_ip_create(context, values):
+ floating_ip_ref = models.FloatingIp()
+ floating_ip_ref.update(values)
+ try:
+ floating_ip_ref.save(context.session)
+ except db_exc.DBDuplicateEntry:
+ raise exception.FloatingIpExists(address=values['address'])
+ return floating_ip_ref
+
+
+def _floating_ip_count_by_project(context, project_id):
+ nova.context.authorize_project_context(context, project_id)
+ # TODO(tr3buchet): why leave auto_assigned floating IPs out?
+ return model_query(context, models.FloatingIp, read_deleted="no").\
+ filter_by(project_id=project_id).\
+ filter_by(auto_assigned=False).\
+ count()
+
+
+@require_context
+@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
+@pick_context_manager_writer
+def floating_ip_fixed_ip_associate(context, floating_address,
+ fixed_address, host):
+ fixed_ip_ref = model_query(context, models.FixedIp).\
+ filter_by(address=fixed_address).\
+ options(joinedload('network')).\
+ first()
+ if not fixed_ip_ref:
+ raise exception.FixedIpNotFoundForAddress(address=fixed_address)
+ rows = model_query(context, models.FloatingIp).\
+ filter_by(address=floating_address).\
+ filter(models.FloatingIp.project_id ==
+ context.project_id).\
+ filter(or_(models.FloatingIp.fixed_ip_id ==
+ fixed_ip_ref['id'],
+ models.FloatingIp.fixed_ip_id.is_(None))).\
+ update({'fixed_ip_id': fixed_ip_ref['id'], 'host': host})
+
+ if not rows:
+ raise exception.FloatingIpAssociateFailed(address=floating_address)
+
+ return fixed_ip_ref
+
+
+@require_context
+@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
+@pick_context_manager_writer
+def floating_ip_deallocate(context, address):
+ return model_query(context, models.FloatingIp).\
+ filter_by(address=address).\
+ filter(and_(models.FloatingIp.project_id != null()),
+ models.FloatingIp.fixed_ip_id == null()).\
+ update({'project_id': None,
+ 'host': None,
+ 'auto_assigned': False},
+ synchronize_session=False)
+
+
+@require_context
+@pick_context_manager_writer
+def floating_ip_destroy(context, address):
+ model_query(context, models.FloatingIp).\
+ filter_by(address=address).\
+ delete()
+
+
+@require_context
+@pick_context_manager_writer
+def floating_ip_disassociate(context, address):
+ floating_ip_ref = model_query(context,
+ models.FloatingIp).\
+ filter_by(address=address).\
+ first()
+ if not floating_ip_ref:
+ raise exception.FloatingIpNotFoundForAddress(address=address)
+
+ fixed_ip_ref = model_query(context, models.FixedIp).\
+ filter_by(id=floating_ip_ref['fixed_ip_id']).\
+ options(joinedload('network')).\
+ first()
+ floating_ip_ref.fixed_ip_id = None
+ floating_ip_ref.host = None
+
+ return fixed_ip_ref
+
+
+def _floating_ip_get_all(context):
+ return model_query(context, models.FloatingIp, read_deleted="no")
+
+
+@pick_context_manager_reader
+def floating_ip_get_all(context):
+ floating_ip_refs = _floating_ip_get_all(context).\
+ options(joinedload('fixed_ip')).\
+ all()
+ if not floating_ip_refs:
+ raise exception.NoFloatingIpsDefined()
+ return floating_ip_refs
+
+
+@pick_context_manager_reader
+def floating_ip_get_all_by_host(context, host):
+ floating_ip_refs = _floating_ip_get_all(context).\
+ filter_by(host=host).\
+ options(joinedload('fixed_ip')).\
+ all()
+ if not floating_ip_refs:
+ raise exception.FloatingIpNotFoundForHost(host=host)
+ return floating_ip_refs
+
+
+@require_context
+@pick_context_manager_reader
+def floating_ip_get_all_by_project(context, project_id):
+ nova.context.authorize_project_context(context, project_id)
+ # TODO(tr3buchet): why do we not want auto_assigned floating IPs here?
+ return _floating_ip_get_all(context).\
+ filter_by(project_id=project_id).\
+ filter_by(auto_assigned=False).\
+ options(joinedload_all('fixed_ip.instance')).\
+ all()
+
+
+@require_context
+@pick_context_manager_reader
+def floating_ip_get_by_address(context, address):
+ return _floating_ip_get_by_address(context, address)
+
+
+def _floating_ip_get_by_address(context, address):
+
+ # if address string is empty explicitly set it to None
+ if not address:
+ address = None
+ try:
+ result = model_query(context, models.FloatingIp).\
+ filter_by(address=address).\
+ options(joinedload_all('fixed_ip.instance')).\
+ first()
+
+ if not result:
+ raise exception.FloatingIpNotFoundForAddress(address=address)
+ except db_exc.DBError:
+ msg = _("Invalid floating IP %s in request") % address
+ LOG.warning(msg)
+ raise exception.InvalidIpAddressError(msg)
+
+ # If the floating IP has a project ID set, check to make sure
+ # the non-admin user has access.
+ if result.project_id and nova.context.is_user_context(context):
+ nova.context.authorize_project_context(context, result.project_id)
+
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def floating_ip_get_by_fixed_address(context, fixed_address):
+ return model_query(context, models.FloatingIp).\
+ outerjoin(models.FixedIp,
+ models.FixedIp.id ==
+ models.FloatingIp.fixed_ip_id).\
+ filter(models.FixedIp.address == fixed_address).\
+ all()
+
+
+@require_context
+@pick_context_manager_reader
+def floating_ip_get_by_fixed_ip_id(context, fixed_ip_id):
+ return model_query(context, models.FloatingIp).\
+ filter_by(fixed_ip_id=fixed_ip_id).\
+ all()
+
+
+@require_context
+@pick_context_manager_writer
+def floating_ip_update(context, address, values):
+ float_ip_ref = _floating_ip_get_by_address(context, address)
+ float_ip_ref.update(values)
+ try:
+ float_ip_ref.save(context.session)
+ except db_exc.DBDuplicateEntry:
+ raise exception.FloatingIpExists(address=values['address'])
+ return float_ip_ref
+
+
+###################
+
+
+@require_context
+@pick_context_manager_reader
+def dnsdomain_get(context, fqdomain):
+ return model_query(context, models.DNSDomain, read_deleted="no").\
+ filter_by(domain=fqdomain).\
+ with_lockmode('update').\
+ first()
+
+
+def _dnsdomain_get_or_create(context, fqdomain):
+ domain_ref = dnsdomain_get(context, fqdomain)
+ if not domain_ref:
+ dns_ref = models.DNSDomain()
+ dns_ref.update({'domain': fqdomain,
+ 'availability_zone': None,
+ 'project_id': None})
+ return dns_ref
+
+ return domain_ref
+
+
+@pick_context_manager_writer
+def dnsdomain_register_for_zone(context, fqdomain, zone):
+ domain_ref = _dnsdomain_get_or_create(context, fqdomain)
+ domain_ref.scope = 'private'
+ domain_ref.availability_zone = zone
+ context.session.add(domain_ref)
+
+
+@pick_context_manager_writer
+def dnsdomain_register_for_project(context, fqdomain, project):
+ domain_ref = _dnsdomain_get_or_create(context, fqdomain)
+ domain_ref.scope = 'public'
+ domain_ref.project_id = project
+ context.session.add(domain_ref)
+
+
+@pick_context_manager_writer
+def dnsdomain_unregister(context, fqdomain):
+ model_query(context, models.DNSDomain).\
+ filter_by(domain=fqdomain).\
+ delete()
+
+
+@pick_context_manager_reader
+def dnsdomain_get_all(context):
+ return model_query(context, models.DNSDomain, read_deleted="no").all()
+
+
+###################
+
+
+@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
+@pick_context_manager_writer
+def fixed_ip_associate(context, address, instance_uuid, network_id=None,
+ reserved=False, virtual_interface_id=None):
+ """Keyword arguments:
+ reserved -- should be a boolean value(True or False), exact value will be
+ used to filter on the fixed IP address
+ """
+ if not uuidutils.is_uuid_like(instance_uuid):
+ raise exception.InvalidUUID(uuid=instance_uuid)
+
+ network_or_none = or_(models.FixedIp.network_id == network_id,
+ models.FixedIp.network_id == null())
+ fixed_ip_ref = model_query(context, models.FixedIp, read_deleted="no").\
+ filter(network_or_none).\
+ filter_by(reserved=reserved).\
+ filter_by(address=address).\
+ first()
+
+ if fixed_ip_ref is None:
+ raise exception.FixedIpNotFoundForNetwork(address=address,
+ network_uuid=network_id)
+ if fixed_ip_ref.instance_uuid:
+ raise exception.FixedIpAlreadyInUse(address=address,
+ instance_uuid=instance_uuid)
+
+ params = {'instance_uuid': instance_uuid,
+ 'allocated': virtual_interface_id is not None}
+ if not fixed_ip_ref.network_id:
+ params['network_id'] = network_id
+ if virtual_interface_id:
+ params['virtual_interface_id'] = virtual_interface_id
+
+ rows_updated = model_query(context, models.FixedIp, read_deleted="no").\
+ filter_by(id=fixed_ip_ref.id).\
+ filter(network_or_none).\
+ filter_by(reserved=reserved).\
+ filter_by(address=address).\
+ update(params, synchronize_session='evaluate')
+
+ if not rows_updated:
+ LOG.debug('The row was updated in a concurrent transaction, '
+ 'we will fetch another row')
+ raise db_exc.RetryRequest(
+ exception.FixedIpAssociateFailed(net=network_id))
+
+ return fixed_ip_ref
+
+
+@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
+@pick_context_manager_writer
+def fixed_ip_associate_pool(context, network_id, instance_uuid=None,
+ host=None, virtual_interface_id=None):
+ """allocate a fixed ip out of a fixed ip network pool.
+
+ This allocates an unallocated fixed ip out of a specified
+ network. We sort by updated_at to hand out the oldest address in
+ the list.
+
+ """
+ if instance_uuid and not uuidutils.is_uuid_like(instance_uuid):
+ raise exception.InvalidUUID(uuid=instance_uuid)
+
+ network_or_none = or_(models.FixedIp.network_id == network_id,
+ models.FixedIp.network_id == null())
+ fixed_ip_ref = model_query(context, models.FixedIp, read_deleted="no").\
+ filter(network_or_none).\
+ filter_by(reserved=False).\
+ filter_by(instance_uuid=None).\
+ filter_by(host=None).\
+ filter_by(leased=False).\
+ order_by(asc(models.FixedIp.updated_at)).\
+ first()
+
+ if not fixed_ip_ref:
+ raise exception.NoMoreFixedIps(net=network_id)
+
+ params = {'allocated': virtual_interface_id is not None}
+ if fixed_ip_ref['network_id'] is None:
+ params['network_id'] = network_id
+ if instance_uuid:
+ params['instance_uuid'] = instance_uuid
+ if host:
+ params['host'] = host
+ if virtual_interface_id:
+ params['virtual_interface_id'] = virtual_interface_id
+
+ rows_updated = model_query(context, models.FixedIp, read_deleted="no").\
+ filter_by(id=fixed_ip_ref['id']).\
+ filter_by(network_id=fixed_ip_ref['network_id']).\
+ filter_by(reserved=False).\
+ filter_by(instance_uuid=None).\
+ filter_by(host=None).\
+ filter_by(leased=False).\
+ filter_by(address=fixed_ip_ref['address']).\
+ update(params, synchronize_session='evaluate')
+
+ if not rows_updated:
+ LOG.debug('The row was updated in a concurrent transaction, '
+ 'we will fetch another row')
+ raise db_exc.RetryRequest(
+ exception.FixedIpAssociateFailed(net=network_id))
+
+ return fixed_ip_ref
+
+
+@require_context
+@pick_context_manager_writer
+def fixed_ip_create(context, values):
+ fixed_ip_ref = models.FixedIp()
+ fixed_ip_ref.update(values)
+ try:
+ fixed_ip_ref.save(context.session)
+ except db_exc.DBDuplicateEntry:
+ raise exception.FixedIpExists(address=values['address'])
+ return fixed_ip_ref
+
+
+@require_context
+@pick_context_manager_writer
+def fixed_ip_bulk_create(context, ips):
+ try:
+ tab = models.FixedIp.__table__
+ context.session.execute(tab.insert(), ips)
+ except db_exc.DBDuplicateEntry as e:
+ raise exception.FixedIpExists(address=e.value)
+
+
+@require_context
+@pick_context_manager_writer
+def fixed_ip_disassociate(context, address):
+ _fixed_ip_get_by_address(context, address).update(
+ {'instance_uuid': None,
+ 'virtual_interface_id': None})
+
+
+@pick_context_manager_writer
+def fixed_ip_disassociate_all_by_timeout(context, host, time):
+ # NOTE(vish): only update fixed ips that "belong" to this
+ # host; i.e. the network host or the instance
+ # host matches. Two queries necessary because
+ # join with update doesn't work.
+ host_filter = or_(and_(models.Instance.host == host,
+ models.Network.multi_host == true()),
+ models.Network.host == host)
+ result = model_query(context, models.FixedIp, (models.FixedIp.id,),
+ read_deleted="no").\
+ filter(models.FixedIp.allocated == false()).\
+ filter(models.FixedIp.updated_at < time).\
+ join((models.Network,
+ models.Network.id == models.FixedIp.network_id)).\
+ join((models.Instance,
+ models.Instance.uuid == models.FixedIp.instance_uuid)).\
+ filter(host_filter).\
+ all()
+ fixed_ip_ids = [fip[0] for fip in result]
+ if not fixed_ip_ids:
+ return 0
+ result = model_query(context, models.FixedIp).\
+ filter(models.FixedIp.id.in_(fixed_ip_ids)).\
+ update({'instance_uuid': None,
+ 'leased': False,
+ 'updated_at': timeutils.utcnow()},
+ synchronize_session='fetch')
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def fixed_ip_get(context, id, get_network=False):
+ query = model_query(context, models.FixedIp).filter_by(id=id)
+ if get_network:
+ query = query.options(joinedload('network'))
+ result = query.first()
+ if not result:
+ raise exception.FixedIpNotFound(id=id)
+
+ # FIXME(sirp): shouldn't we just use project_only here to restrict the
+ # results?
+ if (nova.context.is_user_context(context) and
+ result['instance_uuid'] is not None):
+ instance = instance_get_by_uuid(context.elevated(read_deleted='yes'),
+ result['instance_uuid'])
+ nova.context.authorize_project_context(context, instance.project_id)
+
+ return result
+
+
+@pick_context_manager_reader
+def fixed_ip_get_all(context):
+ result = model_query(context, models.FixedIp, read_deleted="yes").all()
+ if not result:
+ raise exception.NoFixedIpsDefined()
+
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def fixed_ip_get_by_address(context, address, columns_to_join=None):
+ return _fixed_ip_get_by_address(context, address,
+ columns_to_join=columns_to_join)
+
+
+def _fixed_ip_get_by_address(context, address, columns_to_join=None):
+ if columns_to_join is None:
+ columns_to_join = []
+
+ try:
+ result = model_query(context, models.FixedIp)
+ for column in columns_to_join:
+ result = result.options(joinedload_all(column))
+ result = result.filter_by(address=address).first()
+ if not result:
+ raise exception.FixedIpNotFoundForAddress(address=address)
+ except db_exc.DBError:
+ msg = _("Invalid fixed IP Address %s in request") % address
+ LOG.warning(msg)
+ raise exception.FixedIpInvalid(msg)
+
+ # NOTE(sirp): shouldn't we just use project_only here to restrict the
+ # results?
+ if (nova.context.is_user_context(context) and
+ result['instance_uuid'] is not None):
+ instance = _instance_get_by_uuid(
+ context.elevated(read_deleted='yes'),
+ result['instance_uuid'])
+ nova.context.authorize_project_context(context,
+ instance.project_id)
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def fixed_ip_get_by_floating_address(context, floating_address):
+ return model_query(context, models.FixedIp).\
+ join(models.FloatingIp,
+ models.FloatingIp.fixed_ip_id ==
+ models.FixedIp.id).\
+ filter(models.FloatingIp.address == floating_address).\
+ first()
+ # NOTE(tr3buchet) please don't invent an exception here, None is fine
+
+
+@require_context
+@pick_context_manager_reader
+def fixed_ip_get_by_instance(context, instance_uuid):
+ if not uuidutils.is_uuid_like(instance_uuid):
+ raise exception.InvalidUUID(uuid=instance_uuid)
+
+ vif_and = and_(models.VirtualInterface.id ==
+ models.FixedIp.virtual_interface_id,
+ models.VirtualInterface.deleted == 0)
+ result = model_query(context, models.FixedIp, read_deleted="no").\
+ filter_by(instance_uuid=instance_uuid).\
+ outerjoin(models.VirtualInterface, vif_and).\
+ options(contains_eager("virtual_interface")).\
+ options(joinedload('network')).\
+ options(joinedload('floating_ips')).\
+ order_by(asc(models.VirtualInterface.created_at),
+ asc(models.VirtualInterface.id)).\
+ all()
+
+ if not result:
+ raise exception.FixedIpNotFoundForInstance(instance_uuid=instance_uuid)
+
+ return result
+
+
+@pick_context_manager_reader
+def fixed_ip_get_by_host(context, host):
+ instance_uuids = _instance_get_all_uuids_by_host(context, host)
+ if not instance_uuids:
+ return []
+
+ return model_query(context, models.FixedIp).\
+ filter(models.FixedIp.instance_uuid.in_(instance_uuids)).\
+ all()
+
+
+@require_context
+@pick_context_manager_reader
+def fixed_ip_get_by_network_host(context, network_id, host):
+ result = model_query(context, models.FixedIp, read_deleted="no").\
+ filter_by(network_id=network_id).\
+ filter_by(host=host).\
+ first()
+
+ if not result:
+ raise exception.FixedIpNotFoundForNetworkHost(network_id=network_id,
+ host=host)
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def fixed_ips_by_virtual_interface(context, vif_id):
+ result = model_query(context, models.FixedIp, read_deleted="no").\
+ filter_by(virtual_interface_id=vif_id).\
+ options(joinedload('network')).\
+ options(joinedload('floating_ips')).\
+ all()
+
+ return result
+
+
+@require_context
+@pick_context_manager_writer
+def fixed_ip_update(context, address, values):
+ _fixed_ip_get_by_address(context, address).update(values)
+
+
+def _fixed_ip_count_by_project(context, project_id):
+ nova.context.authorize_project_context(context, project_id)
+ return model_query(context, models.FixedIp, (models.FixedIp.id,),
+ read_deleted="no").\
+ join((models.Instance,
+ models.Instance.uuid == models.FixedIp.instance_uuid)).\
+ filter(models.Instance.project_id == project_id).\
+ count()
+
+
+###################
+
+
+@require_context
+@pick_context_manager_writer
+def virtual_interface_create(context, values):
+ """Create a new virtual interface record in the database.
+
+ :param values: = dict containing column values
+ """
+ try:
+ vif_ref = models.VirtualInterface()
+ vif_ref.update(values)
+ vif_ref.save(context.session)
+ except db_exc.DBError:
+ LOG.exception("VIF creation failed with a database error.")
+ raise exception.VirtualInterfaceCreateException()
+
+ return vif_ref
+
+
+def _virtual_interface_query(context):
+ return model_query(context, models.VirtualInterface, read_deleted="no")
+
+
+@require_context
+@pick_context_manager_writer
+def virtual_interface_update(context, address, values):
+ vif_ref = virtual_interface_get_by_address(context, address)
+ vif_ref.update(values)
+ vif_ref.save(context.session)
+ return vif_ref
+
+
+@require_context
+@pick_context_manager_reader
+def virtual_interface_get(context, vif_id):
+ """Gets a virtual interface from the table.
+
+ :param vif_id: = id of the virtual interface
+ """
+ vif_ref = _virtual_interface_query(context).\
+ filter_by(id=vif_id).\
+ first()
+ return vif_ref
+
+
+@require_context
+@pick_context_manager_reader
+def virtual_interface_get_by_address(context, address):
+ """Gets a virtual interface from the table.
+
+ :param address: = the address of the interface you're looking to get
+ """
+ try:
+ vif_ref = _virtual_interface_query(context).\
+ filter_by(address=address).\
+ first()
+ except db_exc.DBError:
+ msg = _("Invalid virtual interface address %s in request") % address
+ LOG.warning(msg)
+ raise exception.InvalidIpAddressError(msg)
+ return vif_ref
+
+
+@require_context
+@pick_context_manager_reader
+def virtual_interface_get_by_uuid(context, vif_uuid):
+ """Gets a virtual interface from the table.
+
+ :param vif_uuid: the uuid of the interface you're looking to get
+ """
+ vif_ref = _virtual_interface_query(context).\
+ filter_by(uuid=vif_uuid).\
+ first()
+ return vif_ref
+
+
+@require_context
+@require_instance_exists_using_uuid
+@pick_context_manager_reader_allow_async
+def virtual_interface_get_by_instance(context, instance_uuid):
+ """Gets all virtual interfaces for instance.
+
+ :param instance_uuid: = uuid of the instance to retrieve vifs for
+ """
+ vif_refs = _virtual_interface_query(context).\
+ filter_by(instance_uuid=instance_uuid).\
+ order_by(asc("created_at"), asc("id")).\
+ all()
+ return vif_refs
+
+
+@require_context
+@pick_context_manager_reader
+def virtual_interface_get_by_instance_and_network(context, instance_uuid,
+ network_id):
+ """Gets virtual interface for instance that's associated with network."""
+ vif_ref = _virtual_interface_query(context).\
+ filter_by(instance_uuid=instance_uuid).\
+ filter_by(network_id=network_id).\
+ first()
+ return vif_ref
+
+
+@require_context
+@pick_context_manager_writer
+def virtual_interface_delete_by_instance(context, instance_uuid):
+ """Delete virtual interface records that are associated
+ with the instance given by instance_id.
+
+ :param instance_uuid: = uuid of instance
+ """
+ _virtual_interface_query(context).\
+ filter_by(instance_uuid=instance_uuid).\
+ soft_delete()
+
+
+@require_context
+@pick_context_manager_writer
+def virtual_interface_delete(context, id):
+ """Delete virtual interface records.
+
+ :param id: id of the interface
+ """
+ _virtual_interface_query(context).\
+ filter_by(id=id).\
+ soft_delete()
+
+
+@require_context
+@pick_context_manager_reader
+def virtual_interface_get_all(context):
+ """Get all vifs."""
+ vif_refs = _virtual_interface_query(context).all()
+ return vif_refs
+
+
+###################
+
+
+def _metadata_refs(metadata_dict, meta_class):
+ metadata_refs = []
+ if metadata_dict:
+ for k, v in metadata_dict.items():
+ metadata_ref = meta_class()
+ metadata_ref['key'] = k
+ metadata_ref['value'] = v
+ metadata_refs.append(metadata_ref)
+ return metadata_refs
+
+
+def _validate_unique_server_name(context, name):
+ if not CONF.osapi_compute_unique_server_name_scope:
+ return
+
+ lowername = name.lower()
+ base_query = model_query(context, models.Instance, read_deleted='no').\
+ filter(func.lower(models.Instance.hostname) == lowername)
+
+ if CONF.osapi_compute_unique_server_name_scope == 'project':
+ instance_with_same_name = base_query.\
+ filter_by(project_id=context.project_id).\
+ count()
+
+ elif CONF.osapi_compute_unique_server_name_scope == 'global':
+ instance_with_same_name = base_query.count()
+
+ else:
+ return
+
+ if instance_with_same_name > 0:
+ raise exception.InstanceExists(name=lowername)
+
+
+def _handle_objects_related_type_conversions(values):
+ """Make sure that certain things in values (which may have come from
+ an objects.instance.Instance object) are in suitable form for the
+ database.
+ """
+ # NOTE(danms): Make sure IP addresses are passed as strings to
+ # the database engine
+ for key in ('access_ip_v4', 'access_ip_v6'):
+ if key in values and values[key] is not None:
+ values[key] = str(values[key])
+
+ datetime_keys = ('created_at', 'deleted_at', 'updated_at',
+ 'launched_at', 'terminated_at')
+ convert_objects_related_datetimes(values, *datetime_keys)
+
+
+def _check_instance_exists_in_project(context, instance_uuid):
+ if not model_query(context, models.Instance, read_deleted="no",
+ project_only=True).filter_by(
+ uuid=instance_uuid).first():
+ raise exception.InstanceNotFound(instance_id=instance_uuid)
+
+
+@require_context
+@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
+@pick_context_manager_writer
+def instance_create(context, values):
+ """Create a new Instance record in the database.
+
+ context - request context object
+ values - dict containing column values.
+ """
+
+ security_group_ensure_default(context)
+
+ values = values.copy()
+ values['metadata'] = _metadata_refs(
+ values.get('metadata'), models.InstanceMetadata)
+
+ values['system_metadata'] = _metadata_refs(
+ values.get('system_metadata'), models.InstanceSystemMetadata)
+ _handle_objects_related_type_conversions(values)
+
+ instance_ref = models.Instance()
+ if not values.get('uuid'):
+ values['uuid'] = uuidutils.generate_uuid()
+ instance_ref['info_cache'] = models.InstanceInfoCache()
+ info_cache = values.pop('info_cache', None)
+ if info_cache is not None:
+ instance_ref['info_cache'].update(info_cache)
+ security_groups = values.pop('security_groups', [])
+ instance_ref['extra'] = models.InstanceExtra()
+ instance_ref['extra'].update(
+ {'numa_topology': None,
+ 'pci_requests': None,
+ 'vcpu_model': None,
+ 'trusted_certs': None,
+ })
+ instance_ref['extra'].update(values.pop('extra', {}))
+ instance_ref.update(values)
+
+ def _get_sec_group_models(security_groups):
+ models = []
+ default_group = _security_group_ensure_default(context)
+ if 'default' in security_groups:
+ models.append(default_group)
+ # Generate a new list, so we don't modify the original
+ security_groups = [x for x in security_groups if x != 'default']
+ if security_groups:
+ models.extend(_security_group_get_by_names(
+ context, security_groups))
+ return models
+
+ if 'hostname' in values:
+ _validate_unique_server_name(context, values['hostname'])
+ instance_ref.security_groups = _get_sec_group_models(security_groups)
+ context.session.add(instance_ref)
+
+ # create the instance uuid to ec2_id mapping entry for instance
+ ec2_instance_create(context, instance_ref['uuid'])
+
+ # Parity with the return value of instance_get_all_by_filters_sort()
+ # Obviously a newly-created instance record can't already have a fault
+ # record because of the FK constraint, so this is fine.
+ instance_ref.fault = None
+
+ return instance_ref
+
+
+@require_context
+@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
+@pick_context_manager_writer
+def instance_destroy(context, instance_uuid, constraint=None):
+ if uuidutils.is_uuid_like(instance_uuid):
+ instance_ref = _instance_get_by_uuid(context, instance_uuid)
+ else:
+ raise exception.InvalidUUID(instance_uuid)
+
+ query = model_query(context, models.Instance).\
+ filter_by(uuid=instance_uuid)
+ if constraint is not None:
+ query = constraint.apply(models.Instance, query)
+ count = query.soft_delete()
+ if count == 0:
+ raise exception.ConstraintNotMet()
+ model_query(context, models.SecurityGroupInstanceAssociation).\
+ filter_by(instance_uuid=instance_uuid).\
+ soft_delete()
+ model_query(context, models.InstanceInfoCache).\
+ filter_by(instance_uuid=instance_uuid).\
+ soft_delete()
+ model_query(context, models.InstanceMetadata).\
+ filter_by(instance_uuid=instance_uuid).\
+ soft_delete()
+ model_query(context, models.InstanceFault).\
+ filter_by(instance_uuid=instance_uuid).\
+ soft_delete()
+ model_query(context, models.InstanceExtra).\
+ filter_by(instance_uuid=instance_uuid).\
+ soft_delete()
+ model_query(context, models.InstanceSystemMetadata).\
+ filter_by(instance_uuid=instance_uuid).\
+ soft_delete()
+ model_query(context, models.BlockDeviceMapping).\
+ filter_by(instance_uuid=instance_uuid).\
+ soft_delete()
+ model_query(context, models.Migration).\
+ filter_by(instance_uuid=instance_uuid).\
+ soft_delete()
+ model_query(context, models.InstanceIdMapping).filter_by(
+ uuid=instance_uuid).soft_delete()
+ # NOTE(snikitin): We can't use model_query here, because there is no
+ # column 'deleted' in 'tags' or 'console_auth_tokens' tables.
+ context.session.query(models.Tag).filter_by(
+ resource_id=instance_uuid).delete()
+ context.session.query(models.ConsoleAuthToken).filter_by(
+ instance_uuid=instance_uuid).delete()
+ # NOTE(cfriesen): We intentionally do not soft-delete entries in the
+ # instance_actions or instance_actions_events tables because they
+ # can be used by operators to find out what actions were performed on a
+ # deleted instance. Both of these tables are special-cased in
+ # _archive_deleted_rows_for_table().
+
+ return instance_ref
+
+
+@require_context
+@pick_context_manager_reader_allow_async
+def instance_get_by_uuid(context, uuid, columns_to_join=None):
+ return _instance_get_by_uuid(context, uuid,
+ columns_to_join=columns_to_join)
+
+
+def _instance_get_by_uuid(context, uuid, columns_to_join=None):
+ result = _build_instance_get(context, columns_to_join=columns_to_join).\
+ filter_by(uuid=uuid).\
+ first()
+
+ if not result:
+ raise exception.InstanceNotFound(instance_id=uuid)
+
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def instance_get(context, instance_id, columns_to_join=None):
+ try:
+ result = _build_instance_get(context, columns_to_join=columns_to_join
+ ).filter_by(id=instance_id).first()
+
+ if not result:
+ raise exception.InstanceNotFound(instance_id=instance_id)
+
+ return result
+ except db_exc.DBError:
+ # NOTE(sdague): catch all in case the db engine chokes on the
+ # id because it's too long of an int to store.
+ LOG.warning("Invalid instance id %s in request", instance_id)
+ raise exception.InvalidID(id=instance_id)
+
+
+def _build_instance_get(context, columns_to_join=None):
+ query = model_query(context, models.Instance, project_only=True).\
+ options(joinedload_all('security_groups.rules')).\
+ options(joinedload('info_cache'))
+ if columns_to_join is None:
+ columns_to_join = ['metadata', 'system_metadata']
+ for column in columns_to_join:
+ if column in ['info_cache', 'security_groups']:
+ # Already always joined above
+ continue
+ if 'extra.' in column:
+ query = query.options(undefer(column))
+ else:
+ query = query.options(joinedload(column))
+ # NOTE(alaski) Stop lazy loading of columns not needed.
+ for col in ['metadata', 'system_metadata']:
+ if col not in columns_to_join:
+ query = query.options(noload(col))
+ return query
+
+
+def _instances_fill_metadata(context, instances, manual_joins=None):
+ """Selectively fill instances with manually-joined metadata. Note that
+ instance will be converted to a dict.
+
+ :param context: security context
+ :param instances: list of instances to fill
+ :param manual_joins: list of tables to manually join (can be any
+ combination of 'metadata' and 'system_metadata' or
+ None to take the default of both)
+ """
+ uuids = [inst['uuid'] for inst in instances]
+
+ if manual_joins is None:
+ manual_joins = ['metadata', 'system_metadata']
+
+ meta = collections.defaultdict(list)
+ if 'metadata' in manual_joins:
+ for row in _instance_metadata_get_multi(context, uuids):
+ meta[row['instance_uuid']].append(row)
+
+ sys_meta = collections.defaultdict(list)
+ if 'system_metadata' in manual_joins:
+ for row in _instance_system_metadata_get_multi(context, uuids):
+ sys_meta[row['instance_uuid']].append(row)
+
+ pcidevs = collections.defaultdict(list)
+ if 'pci_devices' in manual_joins:
+ for row in _instance_pcidevs_get_multi(context, uuids):
+ pcidevs[row['instance_uuid']].append(row)
+
+ if 'fault' in manual_joins:
+ faults = instance_fault_get_by_instance_uuids(context, uuids,
+ latest=True)
+ else:
+ faults = {}
+
+ filled_instances = []
+ for inst in instances:
+ inst = dict(inst)
+ inst['system_metadata'] = sys_meta[inst['uuid']]
+ inst['metadata'] = meta[inst['uuid']]
+ if 'pci_devices' in manual_joins:
+ inst['pci_devices'] = pcidevs[inst['uuid']]
+ inst_faults = faults.get(inst['uuid'])
+ inst['fault'] = inst_faults and inst_faults[0] or None
+ filled_instances.append(inst)
+
+ return filled_instances
+
+
+def _manual_join_columns(columns_to_join):
+ """Separate manually joined columns from columns_to_join
+
+ If columns_to_join contains 'metadata', 'system_metadata', 'fault', or
+ 'pci_devices' those columns are removed from columns_to_join and added
+ to a manual_joins list to be used with the _instances_fill_metadata method.
+
+ The columns_to_join formal parameter is copied and not modified, the return
+ tuple has the modified columns_to_join list to be used with joinedload in
+ a model query.
+
+ :param:columns_to_join: List of columns to join in a model query.
+ :return: tuple of (manual_joins, columns_to_join)
+ """
+ manual_joins = []
+ columns_to_join_new = copy.copy(columns_to_join)
+ for column in ('metadata', 'system_metadata', 'pci_devices', 'fault'):
+ if column in columns_to_join_new:
+ columns_to_join_new.remove(column)
+ manual_joins.append(column)
+ return manual_joins, columns_to_join_new
+
+
+@require_context
+@pick_context_manager_reader
+def instance_get_all(context, columns_to_join=None):
+ if columns_to_join is None:
+ columns_to_join_new = ['info_cache', 'security_groups']
+ manual_joins = ['metadata', 'system_metadata']
+ else:
+ manual_joins, columns_to_join_new = (
+ _manual_join_columns(columns_to_join))
+ query = model_query(context, models.Instance)
+ for column in columns_to_join_new:
+ query = query.options(joinedload(column))
+ if not context.is_admin:
+ # If we're not admin context, add appropriate filter..
+ if context.project_id:
+ query = query.filter_by(project_id=context.project_id)
+ else:
+ query = query.filter_by(user_id=context.user_id)
+ instances = query.all()
+ return _instances_fill_metadata(context, instances, manual_joins)
+
+
+@require_context
+@pick_context_manager_reader_allow_async
+def instance_get_all_by_filters(context, filters, sort_key, sort_dir,
+ limit=None, marker=None, columns_to_join=None):
+ """Return instances matching all filters sorted by the primary key.
+
+ See instance_get_all_by_filters_sort for more information.
+ """
+ # Invoke the API with the multiple sort keys and directions using the
+ # single sort key/direction
+ return instance_get_all_by_filters_sort(context, filters, limit=limit,
+ marker=marker,
+ columns_to_join=columns_to_join,
+ sort_keys=[sort_key],
+ sort_dirs=[sort_dir])
+
+
+def _get_query_nova_resource_by_changes_time(query, filters, model_object):
+ """Filter resources by changes-since or changes-before.
+
+ Special keys are used to tweek the query further::
+
+ | 'changes-since' - only return resources updated after
+ | 'changes-before' - only return resources updated before
+
+ Return query results.
+
+ :param query: query to apply filters to.
+ :param filters: dictionary of filters with regex values.
+ :param model_object: object of the operation target.
+ """
+ for change_filter in ['changes-since', 'changes-before']:
+ if filters and filters.get(change_filter):
+ changes_filter_time = timeutils.normalize_time(
+ filters.get(change_filter))
+ updated_at = getattr(model_object, 'updated_at')
+ if change_filter == 'changes-since':
+ query = query.filter(updated_at >= changes_filter_time)
+ else:
+ query = query.filter(updated_at <= changes_filter_time)
+ return query
+
+
+@require_context
+@pick_context_manager_reader_allow_async
+def instance_get_all_by_filters_sort(context, filters, limit=None, marker=None,
+ columns_to_join=None, sort_keys=None,
+ sort_dirs=None):
+ """Return instances that match all filters sorted by the given keys.
+ Deleted instances will be returned by default, unless there's a filter that
+ says otherwise.
+
+ Depending on the name of a filter, matching for that filter is
+ performed using either exact matching or as regular expression
+ matching. Exact matching is applied for the following filters::
+
+ | ['project_id', 'user_id', 'image_ref',
+ | 'vm_state', 'instance_type_id', 'uuid',
+ | 'metadata', 'host', 'system_metadata']
+
+
+ A third type of filter (also using exact matching), filters
+ based on instance metadata tags when supplied under a special
+ key named 'filter'::
+
+ | filters = {
+ | 'filter': [
+ | {'name': 'tag-key', 'value': '<metakey>'},
+ | {'name': 'tag-value', 'value': '<metaval>'},
+ | {'name': 'tag:<metakey>', 'value': '<metaval>'}
+ | ]
+ | }
+
+ Special keys are used to tweek the query further::
+
+ | 'changes-since' - only return instances updated after
+ | 'changes-before' - only return instances updated before
+ | 'deleted' - only return (or exclude) deleted instances
+ | 'soft_deleted' - modify behavior of 'deleted' to either
+ | include or exclude instances whose
+ | vm_state is SOFT_DELETED.
+
+ A fourth type of filter (also using exact matching), filters
+ based on instance tags (not metadata tags). There are two types
+ of these tags:
+
+ `tags` -- One or more strings that will be used to filter results
+ in an AND expression: T1 AND T2
+
+ `tags-any` -- One or more strings that will be used to filter results in
+ an OR expression: T1 OR T2
+
+ `not-tags` -- One or more strings that will be used to filter results in
+ an NOT AND expression: NOT (T1 AND T2)
+
+ `not-tags-any` -- One or more strings that will be used to filter results
+ in an NOT OR expression: NOT (T1 OR T2)
+
+ Tags should be represented as list::
+
+ | filters = {
+ | 'tags': [some-tag, some-another-tag],
+ | 'tags-any: [some-any-tag, some-another-any-tag],
+ | 'not-tags: [some-not-tag, some-another-not-tag],
+ | 'not-tags-any: [some-not-any-tag, some-another-not-any-tag]
+ | }
+
+ """
+ # NOTE(mriedem): If the limit is 0 there is no point in even going
+ # to the database since nothing is going to be returned anyway.
+ if limit == 0:
+ return []
+
+ sort_keys, sort_dirs = process_sort_params(sort_keys,
+ sort_dirs,
+ default_dir='desc')
+
+ if columns_to_join is None:
+ columns_to_join_new = ['info_cache', 'security_groups']
+ manual_joins = ['metadata', 'system_metadata']
+ else:
+ manual_joins, columns_to_join_new = (
+ _manual_join_columns(columns_to_join))
+
+ query_prefix = context.session.query(models.Instance)
+ for column in columns_to_join_new:
+ if 'extra.' in column:
+ query_prefix = query_prefix.options(undefer(column))
+ else:
+ query_prefix = query_prefix.options(joinedload(column))
+
+ # Note: order_by is done in the sqlalchemy.utils.py paginate_query(),
+ # no need to do it here as well
+
+ # Make a copy of the filters dictionary to use going forward, as we'll
+ # be modifying it and we shouldn't affect the caller's use of it.
+ filters = copy.deepcopy(filters)
+
+ model_object = models.Instance
+ query_prefix = _get_query_nova_resource_by_changes_time(query_prefix,
+ filters,
+ model_object)
+
+ if 'deleted' in filters:
+ # Instances can be soft or hard deleted and the query needs to
+ # include or exclude both
+ deleted = filters.pop('deleted')
+ if deleted:
+ if filters.pop('soft_deleted', True):
+ delete = or_(
+ models.Instance.deleted == models.Instance.id,
+ models.Instance.vm_state == vm_states.SOFT_DELETED
+ )
+ query_prefix = query_prefix.\
+ filter(delete)
+ else:
+ query_prefix = query_prefix.\
+ filter(models.Instance.deleted == models.Instance.id)
+ else:
+ query_prefix = query_prefix.\
+ filter_by(deleted=0)
+ if not filters.pop('soft_deleted', False):
+ # It would be better to have vm_state not be nullable
+ # but until then we test it explicitly as a workaround.
+ not_soft_deleted = or_(
+ models.Instance.vm_state != vm_states.SOFT_DELETED,
+ models.Instance.vm_state == null()
+ )
+ query_prefix = query_prefix.filter(not_soft_deleted)
+
+ if 'cleaned' in filters:
+ cleaned = 1 if filters.pop('cleaned') else 0
+ query_prefix = query_prefix.filter(models.Instance.cleaned == cleaned)
+
+ if 'tags' in filters:
+ tags = filters.pop('tags')
+ # We build a JOIN ladder expression for each tag, JOIN'ing
+ # the first tag to the instances table, and each subsequent
+ # tag to the last JOIN'd tags table
+ first_tag = tags.pop(0)
+ query_prefix = query_prefix.join(models.Instance.tags)
+ query_prefix = query_prefix.filter(models.Tag.tag == first_tag)
+
+ for tag in tags:
+ tag_alias = aliased(models.Tag)
+ query_prefix = query_prefix.join(tag_alias,
+ models.Instance.tags)
+ query_prefix = query_prefix.filter(tag_alias.tag == tag)
+
+ if 'tags-any' in filters:
+ tags = filters.pop('tags-any')
+ tag_alias = aliased(models.Tag)
+ query_prefix = query_prefix.join(tag_alias, models.Instance.tags)
+ query_prefix = query_prefix.filter(tag_alias.tag.in_(tags))
+
+ if 'not-tags' in filters:
+ tags = filters.pop('not-tags')
+ first_tag = tags.pop(0)
+ subq = query_prefix.session.query(models.Tag.resource_id)
+ subq = subq.join(models.Instance.tags)
+ subq = subq.filter(models.Tag.tag == first_tag)
+
+ for tag in tags:
+ tag_alias = aliased(models.Tag)
+ subq = subq.join(tag_alias, models.Instance.tags)
+ subq = subq.filter(tag_alias.tag == tag)
+
+ query_prefix = query_prefix.filter(~models.Instance.uuid.in_(subq))
+
+ if 'not-tags-any' in filters:
+ tags = filters.pop('not-tags-any')
+ query_prefix = query_prefix.filter(~models.Instance.tags.any(
+ models.Tag.tag.in_(tags)))
+
+ if not context.is_admin:
+ # If we're not admin context, add appropriate filter..
+ if context.project_id:
+ filters['project_id'] = context.project_id
+ else:
+ filters['user_id'] = context.user_id
+
+ # Filters for exact matches that we can do along with the SQL query...
+ # For other filters that don't match this, we will do regexp matching
+ exact_match_filter_names = ['project_id', 'user_id', 'image_ref',
+ 'vm_state', 'instance_type_id', 'uuid',
+ 'metadata', 'host', 'task_state',
+ 'system_metadata']
+
+ # Filter the query
+ query_prefix = _exact_instance_filter(query_prefix,
+ filters, exact_match_filter_names)
+ if query_prefix is None:
+ return []
+ query_prefix = _regex_instance_filter(query_prefix, filters)
+
+ # paginate query
+ if marker is not None:
+ try:
+ marker = _instance_get_by_uuid(
+ context.elevated(read_deleted='yes'), marker)
+ except exception.InstanceNotFound:
+ raise exception.MarkerNotFound(marker=marker)
+ try:
+ query_prefix = sqlalchemyutils.paginate_query(query_prefix,
+ models.Instance, limit,
+ sort_keys,
+ marker=marker,
+ sort_dirs=sort_dirs)
+ except db_exc.InvalidSortKey:
+ raise exception.InvalidSortKey()
+
+ return _instances_fill_metadata(context, query_prefix.all(), manual_joins)
+
+
+@require_context
+@pick_context_manager_reader_allow_async
+def instance_get_by_sort_filters(context, sort_keys, sort_dirs, values):
+ """Attempt to get a single instance based on a combination of sort
+ keys, directions and filter values. This is used to try to find a
+ marker instance when we don't have a marker uuid.
+
+ This returns just a uuid of the instance that matched.
+ """
+
+ model = models.Instance
+ return _model_get_uuid_by_sort_filters(context, model, sort_keys,
+ sort_dirs, values)
+
+
+def _model_get_uuid_by_sort_filters(context, model, sort_keys, sort_dirs,
+ values):
+ query = context.session.query(model.uuid)
+
+ # NOTE(danms): Below is a re-implementation of our
+ # oslo_db.sqlalchemy.utils.paginate_query() utility. We can't use that
+ # directly because it does not return the marker and we need it to.
+ # The below is basically the same algorithm, stripped down to just what
+ # we need, and augmented with the filter criteria required for us to
+ # get back the instance that would correspond to our query.
+
+ # This is our position in sort_keys,sort_dirs,values for the loop below
+ key_index = 0
+
+ # We build a list of criteria to apply to the query, which looks
+ # approximately like this (assuming all ascending):
+ #
+ # OR(row.key1 > val1,
+ # AND(row.key1 == val1, row.key2 > val2),
+ # AND(row.key1 == val1, row.key2 == val2, row.key3 >= val3),
+ # )
+ #
+ # The final key is compared with the "or equal" variant so that
+ # a complete match instance is still returned.
+ criteria = []
+
+ for skey, sdir, val in zip(sort_keys, sort_dirs, values):
+ # Apply ordering to our query for the key, direction we're processing
+ if sdir == 'desc':
+ query = query.order_by(desc(getattr(model, skey)))
+ else:
+ query = query.order_by(asc(getattr(model, skey)))
+
+ # Build a list of equivalence requirements on keys we've already
+ # processed through the loop. In other words, if we're adding
+ # key2 > val2, make sure that key1 == val1
+ crit_attrs = []
+ for equal_attr in range(0, key_index):
+ crit_attrs.append(
+ (getattr(model, sort_keys[equal_attr]) == values[equal_attr]))
+
+ model_attr = getattr(model, skey)
+ if isinstance(model_attr.type, Boolean):
+ model_attr = cast(model_attr, Integer)
+ val = int(val)
+
+ if skey == sort_keys[-1]:
+ # If we are the last key, then we should use or-equal to
+ # allow a complete match to be returned
+ if sdir == 'asc':
+ crit = (model_attr >= val)
+ else:
+ crit = (model_attr <= val)
+ else:
+ # If we're not the last key, then strict greater or less than
+ # so we order strictly.
+ if sdir == 'asc':
+ crit = (model_attr > val)
+ else:
+ crit = (model_attr < val)
+
+ # AND together all the above
+ crit_attrs.append(crit)
+ criteria.append(and_(*crit_attrs))
+ key_index += 1
+
+ # OR together all the ANDs
+ query = query.filter(or_(*criteria))
+
+ # We can't raise InstanceNotFound because we don't have a uuid to
+ # be looking for, so just return nothing if no match.
+ result = query.limit(1).first()
+ if result:
+ # We're querying for a single column, which means we get back a
+ # tuple of one thing. Strip that out and just return the uuid
+ # for our caller.
+ return result[0]
+ else:
+ return result
+
+
+def _db_connection_type(db_connection):
+ """Returns a lowercase symbol for the db type.
+
+ This is useful when we need to change what we are doing per DB
+ (like handling regexes). In a CellsV2 world it probably needs to
+ do something better than use the database configuration string.
+ """
+
+ db_string = db_connection.split(':')[0].split('+')[0]
+ return db_string.lower()
+
+
+def _safe_regex_mysql(raw_string):
+ """Make regex safe to mysql.
+
+ Certain items like '|' are interpreted raw by mysql REGEX. If you
+ search for a single | then you trigger an error because it's
+ expecting content on either side.
+
+ For consistency sake we escape all '|'. This does mean we wouldn't
+ support something like foo|bar to match completely different
+ things, however, one can argue putting such complicated regex into
+ name search probably means you are doing this wrong.
+ """
+ return raw_string.replace('|', '\\|')
+
+
+def _get_regexp_ops(connection):
+ """Return safety filter and db opts for regex."""
+ regexp_op_map = {
+ 'postgresql': '~',
+ 'mysql': 'REGEXP',
+ 'sqlite': 'REGEXP'
+ }
+ regex_safe_filters = {
+ 'mysql': _safe_regex_mysql
+ }
+ db_type = _db_connection_type(connection)
+
+ return (regex_safe_filters.get(db_type, lambda x: x),
+ regexp_op_map.get(db_type, 'LIKE'))
+
+
+def _regex_instance_filter(query, filters):
+
+ """Applies regular expression filtering to an Instance query.
+
+ Returns the updated query.
+
+ :param query: query to apply filters to
+ :param filters: dictionary of filters with regex values
+ """
+
+ model = models.Instance
+ safe_regex_filter, db_regexp_op = _get_regexp_ops(CONF.database.connection)
+ for filter_name in filters:
+ try:
+ column_attr = getattr(model, filter_name)
+ except AttributeError:
+ continue
+ if 'property' == type(column_attr).__name__:
+ continue
+ filter_val = filters[filter_name]
+ # Sometimes the REGEX filter value is not a string
+ if not isinstance(filter_val, six.string_types):
+ filter_val = str(filter_val)
+ if db_regexp_op == 'LIKE':
+ query = query.filter(column_attr.op(db_regexp_op)(
+ u'%' + filter_val + u'%'))
+ else:
+ filter_val = safe_regex_filter(filter_val)
+ query = query.filter(column_attr.op(db_regexp_op)(
+ filter_val))
+ return query
+
+
+def _exact_instance_filter(query, filters, legal_keys):
+ """Applies exact match filtering to an Instance query.
+
+ Returns the updated query. Modifies filters argument to remove
+ filters consumed.
+
+ :param query: query to apply filters to
+ :param filters: dictionary of filters; values that are lists,
+ tuples, sets, or frozensets cause an 'IN' test to
+ be performed, while exact matching ('==' operator)
+ is used for other values
+ :param legal_keys: list of keys to apply exact filtering to
+ """
+
+ filter_dict = {}
+ model = models.Instance
+
+ # Walk through all the keys
+ for key in legal_keys:
+ # Skip ones we're not filtering on
+ if key not in filters:
+ continue
+
+ # OK, filtering on this key; what value do we search for?
+ value = filters.pop(key)
+
+ if key in ('metadata', 'system_metadata'):
+ column_attr = getattr(model, key)
+ if isinstance(value, list):
+ for item in value:
+ for k, v in item.items():
+ query = query.filter(column_attr.any(key=k))
+ query = query.filter(column_attr.any(value=v))
+
+ else:
+ for k, v in value.items():
+ query = query.filter(column_attr.any(key=k))
+ query = query.filter(column_attr.any(value=v))
+ elif isinstance(value, (list, tuple, set, frozenset)):
+ if not value:
+ return None # empty IN-predicate; short circuit
+ # Looking for values in a list; apply to query directly
+ column_attr = getattr(model, key)
+ query = query.filter(column_attr.in_(value))
+ else:
+ # OK, simple exact match; save for later
+ filter_dict[key] = value
+
+ # Apply simple exact matches
+ if filter_dict:
+ query = query.filter(*[getattr(models.Instance, k) == v
+ for k, v in filter_dict.items()])
+ return query
+
+
+def process_sort_params(sort_keys, sort_dirs,
+ default_keys=['created_at', 'id'],
+ default_dir='asc'):
+ """Process the sort parameters to include default keys.
+
+ Creates a list of sort keys and a list of sort directions. Adds the default
+ keys to the end of the list if they are not already included.
+
+ When adding the default keys to the sort keys list, the associated
+ direction is:
+ 1) The first element in the 'sort_dirs' list (if specified), else
+ 2) 'default_dir' value (Note that 'asc' is the default value since this is
+ the default in sqlalchemy.utils.paginate_query)
+
+ :param sort_keys: List of sort keys to include in the processed list
+ :param sort_dirs: List of sort directions to include in the processed list
+ :param default_keys: List of sort keys that need to be included in the
+ processed list, they are added at the end of the list
+ if not already specified.
+ :param default_dir: Sort direction associated with each of the default
+ keys that are not supplied, used when they are added
+ to the processed list
+ :returns: list of sort keys, list of sort directions
+ :raise exception.InvalidInput: If more sort directions than sort keys
+ are specified or if an invalid sort
+ direction is specified
+ """
+ # Determine direction to use for when adding default keys
+ if sort_dirs and len(sort_dirs) != 0:
+ default_dir_value = sort_dirs[0]
+ else:
+ default_dir_value = default_dir
+
+ # Create list of keys (do not modify the input list)
+ if sort_keys:
+ result_keys = list(sort_keys)
+ else:
+ result_keys = []
+
+ # If a list of directions is not provided, use the default sort direction
+ # for all provided keys
+ if sort_dirs:
+ result_dirs = []
+ # Verify sort direction
+ for sort_dir in sort_dirs:
+ if sort_dir not in ('asc', 'desc'):
+ msg = _("Unknown sort direction, must be 'desc' or 'asc'")
+ raise exception.InvalidInput(reason=msg)
+ result_dirs.append(sort_dir)
+ else:
+ result_dirs = [default_dir_value for _sort_key in result_keys]
+
+ # Ensure that the key and direction length match
+ while len(result_dirs) < len(result_keys):
+ result_dirs.append(default_dir_value)
+ # Unless more direction are specified, which is an error
+ if len(result_dirs) > len(result_keys):
+ msg = _("Sort direction size exceeds sort key size")
+ raise exception.InvalidInput(reason=msg)
+
+ # Ensure defaults are included
+ for key in default_keys:
+ if key not in result_keys:
+ result_keys.append(key)
+ result_dirs.append(default_dir_value)
+
+ return result_keys, result_dirs
+
+
+@require_context
+@pick_context_manager_reader_allow_async
+def instance_get_active_by_window_joined(context, begin, end=None,
+ project_id=None, host=None,
+ columns_to_join=None, limit=None,
+ marker=None):
+ """Return instances and joins that were active during window."""
+ query = context.session.query(models.Instance)
+
+ if columns_to_join is None:
+ columns_to_join_new = ['info_cache', 'security_groups']
+ manual_joins = ['metadata', 'system_metadata']
+ else:
+ manual_joins, columns_to_join_new = (
+ _manual_join_columns(columns_to_join))
+
+ for column in columns_to_join_new:
+ if 'extra.' in column:
+ query = query.options(undefer(column))
+ else:
+ query = query.options(joinedload(column))
+
+ query = query.filter(or_(models.Instance.terminated_at == null(),
+ models.Instance.terminated_at > begin))
+ if end:
+ query = query.filter(models.Instance.launched_at < end)
+ if project_id:
+ query = query.filter_by(project_id=project_id)
+ if host:
+ query = query.filter_by(host=host)
+
+ if marker is not None:
+ try:
+ marker = _instance_get_by_uuid(
+ context.elevated(read_deleted='yes'), marker)
+ except exception.InstanceNotFound:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(
+ query, models.Instance, limit, ['project_id', 'uuid'], marker=marker)
+
+ return _instances_fill_metadata(context, query.all(), manual_joins)
+
+
+def _instance_get_all_query(context, project_only=False, joins=None):
+ if joins is None:
+ joins = ['info_cache', 'security_groups']
+
+ query = model_query(context,
+ models.Instance,
+ project_only=project_only)
+ for column in joins:
+ if 'extra.' in column:
+ query = query.options(undefer(column))
+ else:
+ query = query.options(joinedload(column))
+ return query
+
+
+@pick_context_manager_reader_allow_async
+def instance_get_all_by_host(context, host, columns_to_join=None):
+ query = _instance_get_all_query(context, joins=columns_to_join)
+ return _instances_fill_metadata(context,
+ query.filter_by(host=host).all(),
+ manual_joins=columns_to_join)
+
+
+def _instance_get_all_uuids_by_host(context, host):
+ """Return a list of the instance uuids on a given host.
+
+ Returns a list of UUIDs, not Instance model objects.
+ """
+ uuids = []
+ for tuple in model_query(context, models.Instance, (models.Instance.uuid,),
+ read_deleted="no").\
+ filter_by(host=host).\
+ all():
+ uuids.append(tuple[0])
+ return uuids
+
+
+@pick_context_manager_reader
+def instance_get_all_uuids_by_host(context, host):
+ return _instance_get_all_uuids_by_host(context, host)
+
+
+@pick_context_manager_reader
+def instance_get_all_by_host_and_node(context, host, node,
+ columns_to_join=None):
+ if columns_to_join is None:
+ manual_joins = []
+ else:
+ candidates = ['system_metadata', 'metadata']
+ manual_joins = [x for x in columns_to_join if x in candidates]
+ columns_to_join = list(set(columns_to_join) - set(candidates))
+ return _instances_fill_metadata(context,
+ _instance_get_all_query(
+ context,
+ joins=columns_to_join).filter_by(host=host).
+ filter_by(node=node).all(), manual_joins=manual_joins)
+
+
+@pick_context_manager_reader
+def instance_get_all_by_host_and_not_type(context, host, type_id=None):
+ return _instances_fill_metadata(context,
+ _instance_get_all_query(context).filter_by(host=host).
+ filter(models.Instance.instance_type_id != type_id).all())
+
+
+@pick_context_manager_reader
+def instance_get_all_by_grantee_security_groups(context, group_ids):
+ if not group_ids:
+ return []
+ return _instances_fill_metadata(context,
+ _instance_get_all_query(context).
+ join(models.Instance.security_groups).
+ filter(models.SecurityGroup.rules.any(
+ models.SecurityGroupIngressRule.group_id.in_(group_ids))).
+ all())
+
+
+@require_context
+@pick_context_manager_reader
+def instance_floating_address_get_all(context, instance_uuid):
+ if not uuidutils.is_uuid_like(instance_uuid):
+ raise exception.InvalidUUID(uuid=instance_uuid)
+
+ floating_ips = model_query(context,
+ models.FloatingIp,
+ (models.FloatingIp.address,)).\
+ join(models.FloatingIp.fixed_ip).\
+ filter_by(instance_uuid=instance_uuid)
+
+ return [floating_ip.address for floating_ip in floating_ips]
+
+
+# NOTE(hanlind): This method can be removed as conductor RPC API moves to v2.0.
+@pick_context_manager_reader
+def instance_get_all_hung_in_rebooting(context, reboot_window):
+ reboot_window = (timeutils.utcnow() -
+ datetime.timedelta(seconds=reboot_window))
+
+ # NOTE(danms): this is only used in the _poll_rebooting_instances()
+ # call in compute/manager, so we can avoid the metadata lookups
+ # explicitly
+ return _instances_fill_metadata(context,
+ model_query(context, models.Instance).
+ filter(models.Instance.updated_at <= reboot_window).
+ filter_by(task_state=task_states.REBOOTING).all(),
+ manual_joins=[])
+
+
+def _retry_instance_update():
+ """Wrap with oslo_db_api.wrap_db_retry, and also retry on
+ UnknownInstanceUpdateConflict.
+ """
+ exception_checker = \
+ lambda exc: isinstance(exc, (exception.UnknownInstanceUpdateConflict,))
+ return oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True,
+ exception_checker=exception_checker)
+
+
+@require_context
+@_retry_instance_update()
+@pick_context_manager_writer
+def instance_update(context, instance_uuid, values, expected=None):
+ return _instance_update(context, instance_uuid, values, expected)
+
+
+@require_context
+@_retry_instance_update()
+@pick_context_manager_writer
+def instance_update_and_get_original(context, instance_uuid, values,
+ columns_to_join=None, expected=None):
+ """Set the given properties on an instance and update it. Return
+ a shallow copy of the original instance reference, as well as the
+ updated one.
+
+ :param context: = request context object
+ :param instance_uuid: = instance uuid
+ :param values: = dict containing column values
+
+ If "expected_task_state" exists in values, the update can only happen
+ when the task state before update matches expected_task_state. Otherwise
+ a UnexpectedTaskStateError is thrown.
+
+ :returns: a tuple of the form (old_instance_ref, new_instance_ref)
+
+ Raises NotFound if instance does not exist.
+ """
+ instance_ref = _instance_get_by_uuid(context, instance_uuid,
+ columns_to_join=columns_to_join)
+ return (copy.copy(instance_ref), _instance_update(
+ context, instance_uuid, values, expected, original=instance_ref))
+
+
+# NOTE(danms): This updates the instance's metadata list in-place and in
+# the database to avoid stale data and refresh issues. It assumes the
+# delete=True behavior of instance_metadata_update(...)
+def _instance_metadata_update_in_place(context, instance, metadata_type, model,
+ metadata):
+ metadata = dict(metadata)
+ to_delete = []
+ for keyvalue in instance[metadata_type]:
+ key = keyvalue['key']
+ if key in metadata:
+ keyvalue['value'] = metadata.pop(key)
+ elif key not in metadata:
+ to_delete.append(keyvalue)
+
+ # NOTE: we have to hard_delete here otherwise we will get more than one
+ # system_metadata record when we read deleted for an instance;
+ # regular metadata doesn't have the same problem because we don't
+ # allow reading deleted regular metadata anywhere.
+ if metadata_type == 'system_metadata':
+ for condemned in to_delete:
+ context.session.delete(condemned)
+ instance[metadata_type].remove(condemned)
+ else:
+ for condemned in to_delete:
+ condemned.soft_delete(context.session)
+
+ for key, value in metadata.items():
+ newitem = model()
+ newitem.update({'key': key, 'value': value,
+ 'instance_uuid': instance['uuid']})
+ context.session.add(newitem)
+ instance[metadata_type].append(newitem)
+
+
+def _instance_update(context, instance_uuid, values, expected, original=None):
+ if not uuidutils.is_uuid_like(instance_uuid):
+ raise exception.InvalidUUID(instance_uuid)
+
+ if expected is None:
+ expected = {}
+ else:
+ # Coerce all single values to singleton lists
+ expected = {k: [None] if v is None else sqlalchemyutils.to_list(v)
+ for (k, v) in expected.items()}
+
+ # Extract 'expected_' values from values dict, as these aren't actually
+ # updates
+ for field in ('task_state', 'vm_state'):
+ expected_field = 'expected_%s' % field
+ if expected_field in values:
+ value = values.pop(expected_field, None)
+ # Coerce all single values to singleton lists
+ if value is None:
+ expected[field] = [None]
+ else:
+ expected[field] = sqlalchemyutils.to_list(value)
+
+ # Values which need to be updated separately
+ metadata = values.pop('metadata', None)
+ system_metadata = values.pop('system_metadata', None)
+
+ _handle_objects_related_type_conversions(values)
+
+ # Hostname is potentially unique, but this is enforced in code rather
+ # than the DB. The query below races, but the number of users of
+ # osapi_compute_unique_server_name_scope is small, and a robust fix
+ # will be complex. This is intentionally left as is for the moment.
+ if 'hostname' in values:
+ _validate_unique_server_name(context, values['hostname'])
+
+ compare = models.Instance(uuid=instance_uuid, **expected)
+ try:
+ instance_ref = model_query(context, models.Instance,
+ project_only=True).\
+ update_on_match(compare, 'uuid', values)
+ except update_match.NoRowsMatched:
+ # Update failed. Try to find why and raise a specific error.
+
+ # We should get here only because our expected values were not current
+ # when update_on_match executed. Having failed, we now have a hint that
+ # the values are out of date and should check them.
+
+ # This code is made more complex because we are using repeatable reads.
+ # If we have previously read the original instance in the current
+ # transaction, reading it again will return the same data, even though
+ # the above update failed because it has changed: it is not possible to
+ # determine what has changed in this transaction. In this case we raise
+ # UnknownInstanceUpdateConflict, which will cause the operation to be
+ # retried in a new transaction.
+
+ # Because of the above, if we have previously read the instance in the
+ # current transaction it will have been passed as 'original', and there
+ # is no point refreshing it. If we have not previously read the
+ # instance, we can fetch it here and we will get fresh data.
+ if original is None:
+ original = _instance_get_by_uuid(context, instance_uuid)
+
+ conflicts_expected = {}
+ conflicts_actual = {}
+ for (field, expected_values) in expected.items():
+ actual = original[field]
+ if actual not in expected_values:
+ conflicts_expected[field] = expected_values
+ conflicts_actual[field] = actual
+
+ # Exception properties
+ exc_props = {
+ 'instance_uuid': instance_uuid,
+ 'expected': conflicts_expected,
+ 'actual': conflicts_actual
+ }
+
+ # There was a conflict, but something (probably the MySQL read view,
+ # but possibly an exceptionally unlikely second race) is preventing us
+ # from seeing what it is. When we go round again we'll get a fresh
+ # transaction and a fresh read view.
+ if len(conflicts_actual) == 0:
+ raise exception.UnknownInstanceUpdateConflict(**exc_props)
+
+ # Task state gets special handling for convenience. We raise the
+ # specific error UnexpectedDeletingTaskStateError or
+ # UnexpectedTaskStateError as appropriate
+ if 'task_state' in conflicts_actual:
+ conflict_task_state = conflicts_actual['task_state']
+ if conflict_task_state == task_states.DELETING:
+ exc = exception.UnexpectedDeletingTaskStateError
+ else:
+ exc = exception.UnexpectedTaskStateError
+
+ # Everything else is an InstanceUpdateConflict
+ else:
+ exc = exception.InstanceUpdateConflict
+
+ raise exc(**exc_props)
+
+ if metadata is not None:
+ _instance_metadata_update_in_place(context, instance_ref,
+ 'metadata',
+ models.InstanceMetadata,
+ metadata)
+
+ if system_metadata is not None:
+ _instance_metadata_update_in_place(context, instance_ref,
+ 'system_metadata',
+ models.InstanceSystemMetadata,
+ system_metadata)
+
+ return instance_ref
+
+
+@pick_context_manager_writer
+def instance_add_security_group(context, instance_uuid, security_group_id):
+ """Associate the given security group with the given instance."""
+ sec_group_ref = models.SecurityGroupInstanceAssociation()
+ sec_group_ref.update({'instance_uuid': instance_uuid,
+ 'security_group_id': security_group_id})
+ sec_group_ref.save(context.session)
+
+
+@require_context
+@pick_context_manager_writer
+def instance_remove_security_group(context, instance_uuid, security_group_id):
+ """Disassociate the given security group from the given instance."""
+ model_query(context, models.SecurityGroupInstanceAssociation).\
+ filter_by(instance_uuid=instance_uuid).\
+ filter_by(security_group_id=security_group_id).\
+ soft_delete()
+
+
+###################
+
+
+@require_context
+@pick_context_manager_reader
+def instance_info_cache_get(context, instance_uuid):
+ """Gets an instance info cache from the table.
+
+ :param instance_uuid: = uuid of the info cache's instance
+ """
+ return model_query(context, models.InstanceInfoCache).\
+ filter_by(instance_uuid=instance_uuid).\
+ first()
+
+
+@require_context
+@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
+@pick_context_manager_writer
+def instance_info_cache_update(context, instance_uuid, values):
+ """Update an instance info cache record in the table.
+
+ :param instance_uuid: = uuid of info cache's instance
+ :param values: = dict containing column values to update
+ """
+ convert_objects_related_datetimes(values)
+
+ info_cache = model_query(context, models.InstanceInfoCache).\
+ filter_by(instance_uuid=instance_uuid).\
+ first()
+ needs_create = False
+ if info_cache and info_cache['deleted']:
+ raise exception.InstanceInfoCacheNotFound(
+ instance_uuid=instance_uuid)
+ elif not info_cache:
+ # NOTE(tr3buchet): just in case someone blows away an instance's
+ # cache entry, re-create it.
+ values['instance_uuid'] = instance_uuid
+ info_cache = models.InstanceInfoCache(**values)
+ needs_create = True
+
+ try:
+ with get_context_manager(context).writer.savepoint.using(context):
+ if needs_create:
+ info_cache.save(context.session)
+ else:
+ info_cache.update(values)
+ except db_exc.DBDuplicateEntry:
+ # NOTE(sirp): Possible race if two greenthreads attempt to
+ # recreate the instance cache entry at the same time. First one
+ # wins.
+ pass
+
+ return info_cache
+
+
+@require_context
+@pick_context_manager_writer
+def instance_info_cache_delete(context, instance_uuid):
+ """Deletes an existing instance_info_cache record
+
+ :param instance_uuid: = uuid of the instance tied to the cache record
+ """
+ model_query(context, models.InstanceInfoCache).\
+ filter_by(instance_uuid=instance_uuid).\
+ soft_delete()
+
+
+###################
+
+
+def _instance_extra_create(context, values):
+ inst_extra_ref = models.InstanceExtra()
+ inst_extra_ref.update(values)
+ inst_extra_ref.save(context.session)
+ return inst_extra_ref
+
+
+@pick_context_manager_writer
+def instance_extra_update_by_uuid(context, instance_uuid, values):
+ rows_updated = model_query(context, models.InstanceExtra).\
+ filter_by(instance_uuid=instance_uuid).\
+ update(values)
+ if not rows_updated:
+ LOG.debug("Created instance_extra for %s", instance_uuid)
+ create_values = copy.copy(values)
+ create_values["instance_uuid"] = instance_uuid
+ _instance_extra_create(context, create_values)
+ rows_updated = 1
+ return rows_updated
+
+
+@pick_context_manager_reader
+def instance_extra_get_by_instance_uuid(context, instance_uuid,
+ columns=None):
+ query = model_query(context, models.InstanceExtra).\
+ filter_by(instance_uuid=instance_uuid)
+ if columns is None:
+ columns = ['numa_topology', 'pci_requests', 'flavor', 'vcpu_model',
+ 'trusted_certs', 'migration_context']
+ for column in columns:
+ query = query.options(undefer(column))
+ instance_extra = query.first()
+ return instance_extra
+
+
+###################
+
+
+@require_context
+@pick_context_manager_writer
+def key_pair_create(context, values):
+ try:
+ key_pair_ref = models.KeyPair()
+ key_pair_ref.update(values)
+ key_pair_ref.save(context.session)
+ return key_pair_ref
+ except db_exc.DBDuplicateEntry:
+ raise exception.KeyPairExists(key_name=values['name'])
+
+
+@require_context
+@pick_context_manager_writer
+def key_pair_destroy(context, user_id, name):
+ result = model_query(context, models.KeyPair).\
+ filter_by(user_id=user_id).\
+ filter_by(name=name).\
+ soft_delete()
+ if not result:
+ raise exception.KeypairNotFound(user_id=user_id, name=name)
+
+
+@require_context
+@pick_context_manager_reader
+def key_pair_get(context, user_id, name):
+ result = model_query(context, models.KeyPair).\
+ filter_by(user_id=user_id).\
+ filter_by(name=name).\
+ first()
+
+ if not result:
+ raise exception.KeypairNotFound(user_id=user_id, name=name)
+
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def key_pair_get_all_by_user(context, user_id, limit=None, marker=None):
+ marker_row = None
+ if marker is not None:
+ marker_row = model_query(context, models.KeyPair, read_deleted="no").\
+ filter_by(name=marker).filter_by(user_id=user_id).first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = model_query(context, models.KeyPair, read_deleted="no").\
+ filter_by(user_id=user_id)
+
+ query = sqlalchemyutils.paginate_query(
+ query, models.KeyPair, limit, ['name'], marker=marker_row)
+
+ return query.all()
+
+
+@require_context
+@pick_context_manager_reader
+def key_pair_count_by_user(context, user_id):
+ return model_query(context, models.KeyPair, read_deleted="no").\
+ filter_by(user_id=user_id).\
+ count()
+
+
+###################
+
+@pick_context_manager_writer
+def network_associate(context, project_id, network_id=None, force=False):
+ """Associate a project with a network.
+
+ called by project_get_networks under certain conditions
+ and network manager add_network_to_project()
+
+ only associate if the project doesn't already have a network
+ or if force is True
+
+ force solves race condition where a fresh project has multiple instance
+ builds simultaneously picked up by multiple network hosts which attempt
+ to associate the project with multiple networks
+ force should only be used as a direct consequence of user request
+ all automated requests should not use force
+ """
+ def network_query(project_filter, id=None):
+ filter_kwargs = {'project_id': project_filter}
+ if id is not None:
+ filter_kwargs['id'] = id
+ return model_query(context, models.Network, read_deleted="no").\
+ filter_by(**filter_kwargs).\
+ with_lockmode('update').\
+ first()
+
+ if not force:
+ # find out if project has a network
+ network_ref = network_query(project_id)
+
+ if force or not network_ref:
+ # in force mode or project doesn't have a network so associate
+ # with a new network
+
+ # get new network
+ network_ref = network_query(None, network_id)
+ if not network_ref:
+ raise exception.NoMoreNetworks()
+
+ # associate with network
+ # NOTE(vish): if with_lockmode isn't supported, as in sqlite,
+ # then this has concurrency issues
+ network_ref['project_id'] = project_id
+ context.session.add(network_ref)
+ return network_ref
+
+
+def _network_ips_query(context, network_id):
+ return model_query(context, models.FixedIp, read_deleted="no").\
+ filter_by(network_id=network_id)
+
+
+@pick_context_manager_reader
+def network_count_reserved_ips(context, network_id):
+ return _network_ips_query(context, network_id).\
+ filter_by(reserved=True).\
+ count()
+
+
+@pick_context_manager_writer
+def network_create_safe(context, values):
+ network_ref = models.Network()
+ network_ref['uuid'] = uuidutils.generate_uuid()
+ network_ref.update(values)
+
+ try:
+ network_ref.save(context.session)
+ return network_ref
+ except db_exc.DBDuplicateEntry:
+ raise exception.DuplicateVlan(vlan=values['vlan'])
+
+
+@pick_context_manager_writer
+def network_delete_safe(context, network_id):
+ result = model_query(context, models.FixedIp, read_deleted="no").\
+ filter_by(network_id=network_id).\
+ filter_by(allocated=True).\
+ count()
+ if result != 0:
+ raise exception.NetworkInUse(network_id=network_id)
+ network_ref = _network_get(context, network_id=network_id)
+
+ model_query(context, models.FixedIp, read_deleted="no").\
+ filter_by(network_id=network_id).\
+ soft_delete()
+
+ context.session.delete(network_ref)
+
+
+@pick_context_manager_writer
+def network_disassociate(context, network_id, disassociate_host,
+ disassociate_project):
+ net_update = {}
+ if disassociate_project:
+ net_update['project_id'] = None
+ if disassociate_host:
+ net_update['host'] = None
+ network_update(context, network_id, net_update)
+
+
+def _network_get(context, network_id, project_only='allow_none'):
+ result = model_query(context, models.Network, project_only=project_only).\
+ filter_by(id=network_id).\
+ first()
+
+ if not result:
+ raise exception.NetworkNotFound(network_id=network_id)
+
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def network_get(context, network_id, project_only='allow_none'):
+ return _network_get(context, network_id, project_only=project_only)
+
+
+@require_context
+@pick_context_manager_reader
+def network_get_all(context, project_only):
+ result = model_query(context, models.Network, read_deleted="no",
+ project_only=project_only).all()
+
+ if not result:
+ raise exception.NoNetworksFound()
+
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def network_get_all_by_uuids(context, network_uuids, project_only):
+ result = model_query(context, models.Network, read_deleted="no",
+ project_only=project_only).\
+ filter(models.Network.uuid.in_(network_uuids)).\
+ all()
+
+ if not result:
+ raise exception.NoNetworksFound()
+
+ # check if the result contains all the networks
+ # we are looking for
+ for network_uuid in network_uuids:
+ for network in result:
+ if network['uuid'] == network_uuid:
+ break
+ else:
+ if project_only:
+ raise exception.NetworkNotFoundForProject(
+ network_uuid=network_uuid, project_id=context.project_id)
+ raise exception.NetworkNotFound(network_id=network_uuid)
+
+ return result
+
+
+def _get_associated_fixed_ips_query(context, network_id, host=None):
+ # NOTE(vish): The ugly joins here are to solve a performance issue and
+ # should be removed once we can add and remove leases
+ # without regenerating the whole list
+ vif_and = and_(models.VirtualInterface.id ==
+ models.FixedIp.virtual_interface_id,
+ models.VirtualInterface.deleted == 0)
+ inst_and = and_(models.Instance.uuid == models.FixedIp.instance_uuid,
+ models.Instance.deleted == 0)
+ # NOTE(vish): This subquery left joins the minimum interface id for each
+ # instance. If the join succeeds (i.e. the 11th column is not
+ # null), then the fixed ip is on the first interface.
+ subq = context.session.query(
+ func.min(models.VirtualInterface.id).label("id"),
+ models.VirtualInterface.instance_uuid).\
+ group_by(models.VirtualInterface.instance_uuid).subquery()
+ subq_and = and_(subq.c.id == models.FixedIp.virtual_interface_id,
+ subq.c.instance_uuid == models.VirtualInterface.instance_uuid)
+ query = context.session.query(
+ models.FixedIp.address,
+ models.FixedIp.instance_uuid,
+ models.FixedIp.network_id,
+ models.FixedIp.virtual_interface_id,
+ models.VirtualInterface.address,
+ models.Instance.hostname,
+ models.Instance.updated_at,
+ models.Instance.created_at,
+ models.FixedIp.allocated,
+ models.FixedIp.leased,
+ subq.c.id).\
+ filter(models.FixedIp.deleted == 0).\
+ filter(models.FixedIp.network_id == network_id).\
+ join((models.VirtualInterface, vif_and)).\
+ join((models.Instance, inst_and)).\
+ outerjoin((subq, subq_and)).\
+ filter(models.FixedIp.instance_uuid != null()).\
+ filter(models.FixedIp.virtual_interface_id != null())
+ if host:
+ query = query.filter(models.Instance.host == host)
+ return query
+
+
+@pick_context_manager_reader
+def network_get_associated_fixed_ips(context, network_id, host=None):
+ # FIXME(sirp): since this returns fixed_ips, this would be better named
+ # fixed_ip_get_all_by_network.
+ query = _get_associated_fixed_ips_query(context, network_id, host)
+ result = query.all()
+ data = []
+ for datum in result:
+ cleaned = {}
+ cleaned['address'] = datum[0]
+ cleaned['instance_uuid'] = datum[1]
+ cleaned['network_id'] = datum[2]
+ cleaned['vif_id'] = datum[3]
+ cleaned['vif_address'] = datum[4]
+ cleaned['instance_hostname'] = datum[5]
+ cleaned['instance_updated'] = datum[6]
+ cleaned['instance_created'] = datum[7]
+ cleaned['allocated'] = datum[8]
+ cleaned['leased'] = datum[9]
+ # NOTE(vish): default_route is True if this fixed ip is on the first
+ # interface its instance.
+ cleaned['default_route'] = datum[10] is not None
+ data.append(cleaned)
+ return data
+
+
+@pick_context_manager_reader
+def network_in_use_on_host(context, network_id, host):
+ query = _get_associated_fixed_ips_query(context, network_id, host)
+ return query.count() > 0
+
+
+def _network_get_query(context):
+ return model_query(context, models.Network, read_deleted="no")
+
+
+@pick_context_manager_reader
+def network_get_by_uuid(context, uuid):
+ result = _network_get_query(context).filter_by(uuid=uuid).first()
+
+ if not result:
+ raise exception.NetworkNotFoundForUUID(uuid=uuid)
+
+ return result
+
+
+@pick_context_manager_reader
+def network_get_by_cidr(context, cidr):
+ result = _network_get_query(context).\
+ filter(or_(models.Network.cidr == cidr,
+ models.Network.cidr_v6 == cidr)).\
+ first()
+
+ if not result:
+ raise exception.NetworkNotFoundForCidr(cidr=cidr)
+
+ return result
+
+
+@pick_context_manager_reader
+def network_get_all_by_host(context, host):
+ fixed_host_filter = or_(models.FixedIp.host == host,
+ and_(models.FixedIp.instance_uuid != null(),
+ models.Instance.host == host))
+ fixed_ip_query = model_query(context, models.FixedIp,
+ (models.FixedIp.network_id,)).\
+ outerjoin((models.Instance,
+ models.Instance.uuid ==
+ models.FixedIp.instance_uuid)).\
+ filter(fixed_host_filter)
+ # NOTE(vish): return networks that have host set
+ # or that have a fixed ip with host set
+ # or that have an instance with host set
+ host_filter = or_(models.Network.host == host,
+ models.Network.id.in_(fixed_ip_query.subquery()))
+ return _network_get_query(context).filter(host_filter).all()
+
+
+@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
+@pick_context_manager_writer
+def network_set_host(context, network_id, host_id):
+ network_ref = _network_get_query(context).\
+ filter_by(id=network_id).\
+ first()
+
+ if not network_ref:
+ raise exception.NetworkNotFound(network_id=network_id)
+
+ if network_ref.host:
+ return None
+
+ rows_updated = _network_get_query(context).\
+ filter_by(id=network_id).\
+ filter_by(host=None).\
+ update({'host': host_id})
+
+ if not rows_updated:
+ LOG.debug('The row was updated in a concurrent transaction, '
+ 'we will fetch another row')
+ raise db_exc.RetryRequest(
+ exception.NetworkSetHostFailed(network_id=network_id))
+
+
+@require_context
+@pick_context_manager_writer
+def network_update(context, network_id, values):
+ network_ref = _network_get(context, network_id)
+ network_ref.update(values)
+ try:
+ network_ref.save(context.session)
+ except db_exc.DBDuplicateEntry:
+ raise exception.DuplicateVlan(vlan=values['vlan'])
+ return network_ref
+
+
+###################
+
+
+@require_context
+@pick_context_manager_reader
+def quota_get(context, project_id, resource, user_id=None):
+ model = models.ProjectUserQuota if user_id else models.Quota
+ query = model_query(context, model).\
+ filter_by(project_id=project_id).\
+ filter_by(resource=resource)
+ if user_id:
+ query = query.filter_by(user_id=user_id)
+
+ result = query.first()
+ if not result:
+ if user_id:
+ raise exception.ProjectUserQuotaNotFound(project_id=project_id,
+ user_id=user_id)
+ else:
+ raise exception.ProjectQuotaNotFound(project_id=project_id)
+
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def quota_get_all_by_project_and_user(context, project_id, user_id):
+ user_quotas = model_query(context, models.ProjectUserQuota,
+ (models.ProjectUserQuota.resource,
+ models.ProjectUserQuota.hard_limit)).\
+ filter_by(project_id=project_id).\
+ filter_by(user_id=user_id).\
+ all()
+
+ result = {'project_id': project_id, 'user_id': user_id}
+ for user_quota in user_quotas:
+ result[user_quota.resource] = user_quota.hard_limit
+
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def quota_get_all_by_project(context, project_id):
+ rows = model_query(context, models.Quota, read_deleted="no").\
+ filter_by(project_id=project_id).\
+ all()
+
+ result = {'project_id': project_id}
+ for row in rows:
+ result[row.resource] = row.hard_limit
+
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def quota_get_all(context, project_id):
+ result = model_query(context, models.ProjectUserQuota).\
+ filter_by(project_id=project_id).\
+ all()
+
+ return result
+
+
+def quota_get_per_project_resources():
+ return PER_PROJECT_QUOTAS
+
+
+@pick_context_manager_writer
+def quota_create(context, project_id, resource, limit, user_id=None):
+ per_user = user_id and resource not in PER_PROJECT_QUOTAS
+ quota_ref = models.ProjectUserQuota() if per_user else models.Quota()
+ if per_user:
+ quota_ref.user_id = user_id
+ quota_ref.project_id = project_id
+ quota_ref.resource = resource
+ quota_ref.hard_limit = limit
+ try:
+ quota_ref.save(context.session)
+ except db_exc.DBDuplicateEntry:
+ raise exception.QuotaExists(project_id=project_id, resource=resource)
+ return quota_ref
+
+
+@pick_context_manager_writer
+def quota_update(context, project_id, resource, limit, user_id=None):
+ per_user = user_id and resource not in PER_PROJECT_QUOTAS
+ model = models.ProjectUserQuota if per_user else models.Quota
+ query = model_query(context, model).\
+ filter_by(project_id=project_id).\
+ filter_by(resource=resource)
+ if per_user:
+ query = query.filter_by(user_id=user_id)
+
+ result = query.update({'hard_limit': limit})
+ if not result:
+ if per_user:
+ raise exception.ProjectUserQuotaNotFound(project_id=project_id,
+ user_id=user_id)
+ else:
+ raise exception.ProjectQuotaNotFound(project_id=project_id)
+
+
+###################
+
+
+@require_context
+@pick_context_manager_reader
+def quota_class_get(context, class_name, resource):
+ result = model_query(context, models.QuotaClass, read_deleted="no").\
+ filter_by(class_name=class_name).\
+ filter_by(resource=resource).\
+ first()
+
+ if not result:
+ raise exception.QuotaClassNotFound(class_name=class_name)
+
+ return result
+
+
+@pick_context_manager_reader
+def quota_class_get_default(context):
+ rows = model_query(context, models.QuotaClass, read_deleted="no").\
+ filter_by(class_name=_DEFAULT_QUOTA_NAME).\
+ all()
+
+ result = {'class_name': _DEFAULT_QUOTA_NAME}
+ for row in rows:
+ result[row.resource] = row.hard_limit
+
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def quota_class_get_all_by_name(context, class_name):
+ rows = model_query(context, models.QuotaClass, read_deleted="no").\
+ filter_by(class_name=class_name).\
+ all()
+
+ result = {'class_name': class_name}
+ for row in rows:
+ result[row.resource] = row.hard_limit
+
+ return result
+
+
+@pick_context_manager_writer
+def quota_class_create(context, class_name, resource, limit):
+ quota_class_ref = models.QuotaClass()
+ quota_class_ref.class_name = class_name
+ quota_class_ref.resource = resource
+ quota_class_ref.hard_limit = limit
+ quota_class_ref.save(context.session)
+ return quota_class_ref
+
+
+@pick_context_manager_writer
+def quota_class_update(context, class_name, resource, limit):
+ result = model_query(context, models.QuotaClass, read_deleted="no").\
+ filter_by(class_name=class_name).\
+ filter_by(resource=resource).\
+ update({'hard_limit': limit})
+
+ if not result:
+ raise exception.QuotaClassNotFound(class_name=class_name)
+
+
+###################
+
+
+@pick_context_manager_writer
+def quota_destroy_all_by_project_and_user(context, project_id, user_id):
+ model_query(context, models.ProjectUserQuota, read_deleted="no").\
+ filter_by(project_id=project_id).\
+ filter_by(user_id=user_id).\
+ soft_delete(synchronize_session=False)
+
+
+@pick_context_manager_writer
+def quota_destroy_all_by_project(context, project_id):
+ model_query(context, models.Quota, read_deleted="no").\
+ filter_by(project_id=project_id).\
+ soft_delete(synchronize_session=False)
+
+ model_query(context, models.ProjectUserQuota, read_deleted="no").\
+ filter_by(project_id=project_id).\
+ soft_delete(synchronize_session=False)
+
+
+###################
+
+
+def _ec2_volume_get_query(context):
+ return model_query(context, models.VolumeIdMapping, read_deleted='yes')
+
+
+def _ec2_snapshot_get_query(context):
+ return model_query(context, models.SnapshotIdMapping, read_deleted='yes')
+
+
+@require_context
+@pick_context_manager_writer
+def ec2_volume_create(context, volume_uuid, id=None):
+ """Create ec2 compatible volume by provided uuid."""
+ ec2_volume_ref = models.VolumeIdMapping()
+ ec2_volume_ref.update({'uuid': volume_uuid})
+ if id is not None:
+ ec2_volume_ref.update({'id': id})
+
+ ec2_volume_ref.save(context.session)
+
+ return ec2_volume_ref
+
+
+@require_context
+@pick_context_manager_reader
+def ec2_volume_get_by_uuid(context, volume_uuid):
+ result = _ec2_volume_get_query(context).\
+ filter_by(uuid=volume_uuid).\
+ first()
+
+ if not result:
+ raise exception.VolumeNotFound(volume_id=volume_uuid)
+
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def ec2_volume_get_by_id(context, volume_id):
+ result = _ec2_volume_get_query(context).\
+ filter_by(id=volume_id).\
+ first()
+
+ if not result:
+ raise exception.VolumeNotFound(volume_id=volume_id)
+
+ return result
+
+
+@require_context
+@pick_context_manager_writer
+def ec2_snapshot_create(context, snapshot_uuid, id=None):
+ """Create ec2 compatible snapshot by provided uuid."""
+ ec2_snapshot_ref = models.SnapshotIdMapping()
+ ec2_snapshot_ref.update({'uuid': snapshot_uuid})
+ if id is not None:
+ ec2_snapshot_ref.update({'id': id})
+
+ ec2_snapshot_ref.save(context.session)
+
+ return ec2_snapshot_ref
+
+
+@require_context
+@pick_context_manager_reader
+def ec2_snapshot_get_by_ec2_id(context, ec2_id):
+ result = _ec2_snapshot_get_query(context).\
+ filter_by(id=ec2_id).\
+ first()
+
+ if not result:
+ raise exception.SnapshotNotFound(snapshot_id=ec2_id)
+
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def ec2_snapshot_get_by_uuid(context, snapshot_uuid):
+ result = _ec2_snapshot_get_query(context).\
+ filter_by(uuid=snapshot_uuid).\
+ first()
+
+ if not result:
+ raise exception.SnapshotNotFound(snapshot_id=snapshot_uuid)
+
+ return result
+
+
+###################
+
+
+def _block_device_mapping_get_query(context, columns_to_join=None):
+ if columns_to_join is None:
+ columns_to_join = []
+
+ query = model_query(context, models.BlockDeviceMapping)
+
+ for column in columns_to_join:
+ query = query.options(joinedload(column))
+
+ return query
+
+
+def _scrub_empty_str_values(dct, keys_to_scrub):
+ """Remove any keys found in sequence keys_to_scrub from the dict
+ if they have the value ''.
+ """
+ for key in keys_to_scrub:
+ if key in dct and dct[key] == '':
+ del dct[key]
+
+
+def _from_legacy_values(values, legacy, allow_updates=False):
+ if legacy:
+ if allow_updates and block_device.is_safe_for_update(values):
+ return values
+ else:
+ return block_device.BlockDeviceDict.from_legacy(values)
+ else:
+ return values
+
+
+def _set_or_validate_uuid(values):
+ uuid = values.get('uuid')
+
+ # values doesn't contain uuid, or it's blank
+ if not uuid:
+ values['uuid'] = uuidutils.generate_uuid()
+
+ # values contains a uuid
+ else:
+ if not uuidutils.is_uuid_like(uuid):
+ raise exception.InvalidUUID(uuid=uuid)
+
+
+@require_context
+@pick_context_manager_writer
+def block_device_mapping_create(context, values, legacy=True):
+ _scrub_empty_str_values(values, ['volume_size'])
+ values = _from_legacy_values(values, legacy)
+ convert_objects_related_datetimes(values)
+
+ _set_or_validate_uuid(values)
+
+ bdm_ref = models.BlockDeviceMapping()
+ bdm_ref.update(values)
+ bdm_ref.save(context.session)
+ return bdm_ref
+
+
+@require_context
+@pick_context_manager_writer
+def block_device_mapping_update(context, bdm_id, values, legacy=True):
+ _scrub_empty_str_values(values, ['volume_size'])
+ values = _from_legacy_values(values, legacy, allow_updates=True)
+ convert_objects_related_datetimes(values)
+
+ query = _block_device_mapping_get_query(context).filter_by(id=bdm_id)
+ query.update(values)
+ return query.first()
+
+
+@pick_context_manager_writer
+def block_device_mapping_update_or_create(context, values, legacy=True):
+ # TODO(mdbooth): Remove this method entirely. Callers should know whether
+ # they require update or create, and call the appropriate method.
+
+ _scrub_empty_str_values(values, ['volume_size'])
+ values = _from_legacy_values(values, legacy, allow_updates=True)
+ convert_objects_related_datetimes(values)
+
+ result = None
+ # NOTE(xqueralt,danms): Only update a BDM when device_name or
+ # uuid was provided. Prefer the uuid, if available, but fall
+ # back to device_name if no uuid is provided, which can happen
+ # for BDMs created before we had a uuid. We allow empty device
+ # names so they will be set later by the manager.
+ if 'uuid' in values:
+ query = _block_device_mapping_get_query(context)
+ result = query.filter_by(instance_uuid=values['instance_uuid'],
+ uuid=values['uuid']).one_or_none()
+
+ if not result and values['device_name']:
+ query = _block_device_mapping_get_query(context)
+ result = query.filter_by(instance_uuid=values['instance_uuid'],
+ device_name=values['device_name']).first()
+
+ if result:
+ result.update(values)
+ else:
+ # Either the device_name or uuid doesn't exist in the database yet, or
+ # neither was provided. Both cases mean creating a new BDM.
+ _set_or_validate_uuid(values)
+ result = models.BlockDeviceMapping(**values)
+ result.save(context.session)
+
+ # NOTE(xqueralt): Prevent from having multiple swap devices for the
+ # same instance. This will delete all the existing ones.
+ if block_device.new_format_is_swap(values):
+ query = _block_device_mapping_get_query(context)
+ query = query.filter_by(instance_uuid=values['instance_uuid'],
+ source_type='blank', guest_format='swap')
+ query = query.filter(models.BlockDeviceMapping.id != result.id)
+ query.soft_delete()
+
+ return result
+
+
+@require_context
+@pick_context_manager_reader_allow_async
+def block_device_mapping_get_all_by_instance_uuids(context, instance_uuids):
+ if not instance_uuids:
+ return []
+ return _block_device_mapping_get_query(context).filter(
+ models.BlockDeviceMapping.instance_uuid.in_(instance_uuids)).all()
+
+
+@require_context
+@pick_context_manager_reader_allow_async
+def block_device_mapping_get_all_by_instance(context, instance_uuid):
+ return _block_device_mapping_get_query(context).\
+ filter_by(instance_uuid=instance_uuid).\
+ all()
+
+
+@require_context
+@pick_context_manager_reader
+def block_device_mapping_get_all_by_volume_id(context, volume_id,
+ columns_to_join=None):
+ return _block_device_mapping_get_query(context,
+ columns_to_join=columns_to_join).\
+ filter_by(volume_id=volume_id).\
+ all()
+
+
+@require_context
+@pick_context_manager_reader
+def block_device_mapping_get_by_instance_and_volume_id(context, volume_id,
+ instance_uuid,
+ columns_to_join=None):
+ return _block_device_mapping_get_query(context,
+ columns_to_join=columns_to_join).\
+ filter_by(volume_id=volume_id).\
+ filter_by(instance_uuid=instance_uuid).\
+ first()
+
+
+@require_context
+@pick_context_manager_writer
+def block_device_mapping_destroy(context, bdm_id):
+ _block_device_mapping_get_query(context).\
+ filter_by(id=bdm_id).\
+ soft_delete()
+
+
+@require_context
+@pick_context_manager_writer
+def block_device_mapping_destroy_by_instance_and_volume(context, instance_uuid,
+ volume_id):
+ _block_device_mapping_get_query(context).\
+ filter_by(instance_uuid=instance_uuid).\
+ filter_by(volume_id=volume_id).\
+ soft_delete()
+
+
+@require_context
+@pick_context_manager_writer
+def block_device_mapping_destroy_by_instance_and_device(context, instance_uuid,
+ device_name):
+ _block_device_mapping_get_query(context).\
+ filter_by(instance_uuid=instance_uuid).\
+ filter_by(device_name=device_name).\
+ soft_delete()
+
+
+###################
+
+
+@require_context
+@pick_context_manager_writer
+def security_group_create(context, values):
+ security_group_ref = models.SecurityGroup()
+ # FIXME(devcamcar): Unless I do this, rules fails with lazy load exception
+ # once save() is called. This will get cleaned up in next orm pass.
+ security_group_ref.rules
+ security_group_ref.update(values)
+ try:
+ with get_context_manager(context).writer.savepoint.using(context):
+ security_group_ref.save(context.session)
+ except db_exc.DBDuplicateEntry:
+ raise exception.SecurityGroupExists(
+ project_id=values['project_id'],
+ security_group_name=values['name'])
+ return security_group_ref
+
+
+def _security_group_get_query(context, read_deleted=None,
+ project_only=False, join_rules=True):
+ query = model_query(context, models.SecurityGroup,
+ read_deleted=read_deleted, project_only=project_only)
+ if join_rules:
+ query = query.options(joinedload_all('rules.grantee_group'))
+ return query
+
+
+def _security_group_get_by_names(context, group_names):
+ """Get security group models for a project by a list of names.
+ Raise SecurityGroupNotFoundForProject for a name not found.
+ """
+ query = _security_group_get_query(context, read_deleted="no",
+ join_rules=False).\
+ filter_by(project_id=context.project_id).\
+ filter(models.SecurityGroup.name.in_(group_names))
+ sg_models = query.all()
+ if len(sg_models) == len(group_names):
+ return sg_models
+ # Find the first one missing and raise
+ group_names_from_models = [x.name for x in sg_models]
+ for group_name in group_names:
+ if group_name not in group_names_from_models:
+ raise exception.SecurityGroupNotFoundForProject(
+ project_id=context.project_id, security_group_id=group_name)
+ # Not Reached
+
+
+@require_context
+@pick_context_manager_reader
+def security_group_get_all(context):
+ return _security_group_get_query(context).all()
+
+
+@require_context
+@pick_context_manager_reader
+def security_group_get(context, security_group_id, columns_to_join=None):
+ join_rules = columns_to_join and 'rules' in columns_to_join
+ if join_rules:
+ columns_to_join.remove('rules')
+ query = _security_group_get_query(context, project_only=True,
+ join_rules=join_rules).\
+ filter_by(id=security_group_id)
+
+ if columns_to_join is None:
+ columns_to_join = []
+ for column in columns_to_join:
+ if column.startswith('instances'):
+ query = query.options(joinedload_all(column))
+
+ result = query.first()
+ if not result:
+ raise exception.SecurityGroupNotFound(
+ security_group_id=security_group_id)
+
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def security_group_get_by_name(context, project_id, group_name,
+ columns_to_join=None):
+ query = _security_group_get_query(context,
+ read_deleted="no", join_rules=False).\
+ filter_by(project_id=project_id).\
+ filter_by(name=group_name)
+
+ if columns_to_join is None:
+ columns_to_join = ['instances', 'rules.grantee_group']
+
+ for column in columns_to_join:
+ query = query.options(joinedload_all(column))
+
+ result = query.first()
+ if not result:
+ raise exception.SecurityGroupNotFoundForProject(
+ project_id=project_id, security_group_id=group_name)
+
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def security_group_get_by_project(context, project_id):
+ return _security_group_get_query(context, read_deleted="no").\
+ filter_by(project_id=project_id).\
+ all()
+
+
+@require_context
+@pick_context_manager_reader
+def security_group_get_by_instance(context, instance_uuid):
+ return _security_group_get_query(context, read_deleted="no").\
+ join(models.SecurityGroup.instances).\
+ filter_by(uuid=instance_uuid).\
+ all()
+
+
+@require_context
+@pick_context_manager_reader
+def security_group_in_use(context, group_id):
+ # Are there any instances that haven't been deleted
+ # that include this group?
+ inst_assoc = model_query(context,
+ models.SecurityGroupInstanceAssociation,
+ read_deleted="no").\
+ filter_by(security_group_id=group_id).\
+ all()
+ for ia in inst_assoc:
+ num_instances = model_query(context, models.Instance,
+ read_deleted="no").\
+ filter_by(uuid=ia.instance_uuid).\
+ count()
+ if num_instances:
+ return True
+
+ return False
+
+
+@require_context
+@pick_context_manager_writer
+def security_group_update(context, security_group_id, values,
+ columns_to_join=None):
+ query = model_query(context, models.SecurityGroup).filter_by(
+ id=security_group_id)
+ if columns_to_join:
+ for column in columns_to_join:
+ query = query.options(joinedload_all(column))
+ security_group_ref = query.first()
+
+ if not security_group_ref:
+ raise exception.SecurityGroupNotFound(
+ security_group_id=security_group_id)
+ security_group_ref.update(values)
+ name = security_group_ref['name']
+ project_id = security_group_ref['project_id']
+ try:
+ security_group_ref.save(context.session)
+ except db_exc.DBDuplicateEntry:
+ raise exception.SecurityGroupExists(
+ project_id=project_id,
+ security_group_name=name)
+ return security_group_ref
+
+
+def security_group_ensure_default(context):
+ """Ensure default security group exists for a project_id."""
+
+ try:
+ # NOTE(rpodolyaka): create the default security group, if it doesn't
+ # exist. This must be done in a separate transaction, so that
+ # this one is not aborted in case a concurrent one succeeds first
+ # and the unique constraint for security group names is violated
+ # by a concurrent INSERT
+ with get_context_manager(context).writer.independent.using(context):
+ return _security_group_ensure_default(context)
+ except exception.SecurityGroupExists:
+ # NOTE(rpodolyaka): a concurrent transaction has succeeded first,
+ # suppress the error and proceed
+ return security_group_get_by_name(context, context.project_id,
+ 'default')
+
+
+@pick_context_manager_writer
+def _security_group_ensure_default(context):
+ try:
+ default_group = _security_group_get_by_names(context, ['default'])[0]
+ except exception.NotFound:
+ values = {'name': 'default',
+ 'description': 'default',
+ 'user_id': context.user_id,
+ 'project_id': context.project_id}
+ default_group = security_group_create(context, values)
+
+ default_rules = _security_group_rule_get_default_query(context).all()
+ for default_rule in default_rules:
+ # This is suboptimal, it should be programmatic to know
+ # the values of the default_rule
+ rule_values = {'protocol': default_rule.protocol,
+ 'from_port': default_rule.from_port,
+ 'to_port': default_rule.to_port,
+ 'cidr': default_rule.cidr,
+ 'parent_group_id': default_group.id,
+ }
+ _security_group_rule_create(context, rule_values)
+ return default_group
+
+
+@require_context
+@pick_context_manager_writer
+def security_group_destroy(context, security_group_id):
+ model_query(context, models.SecurityGroup).\
+ filter_by(id=security_group_id).\
+ soft_delete()
+ model_query(context, models.SecurityGroupInstanceAssociation).\
+ filter_by(security_group_id=security_group_id).\
+ soft_delete()
+ model_query(context, models.SecurityGroupIngressRule).\
+ filter_by(group_id=security_group_id).\
+ soft_delete()
+ model_query(context, models.SecurityGroupIngressRule).\
+ filter_by(parent_group_id=security_group_id).\
+ soft_delete()
+
+
+def _security_group_count_by_project_and_user(context, project_id, user_id):
+ nova.context.authorize_project_context(context, project_id)
+ return model_query(context, models.SecurityGroup, read_deleted="no").\
+ filter_by(project_id=project_id).\
+ filter_by(user_id=user_id).\
+ count()
+
+
+###################
+
+
+def _security_group_rule_create(context, values):
+ security_group_rule_ref = models.SecurityGroupIngressRule()
+ security_group_rule_ref.update(values)
+ security_group_rule_ref.save(context.session)
+ return security_group_rule_ref
+
+
+def _security_group_rule_get_query(context):
+ return model_query(context, models.SecurityGroupIngressRule)
+
+
+@require_context
+@pick_context_manager_reader
+def security_group_rule_get(context, security_group_rule_id):
+ result = (_security_group_rule_get_query(context).
+ filter_by(id=security_group_rule_id).
+ first())
+
+ if not result:
+ raise exception.SecurityGroupNotFoundForRule(
+ rule_id=security_group_rule_id)
+
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def security_group_rule_get_by_security_group(context, security_group_id,
+ columns_to_join=None):
+ if columns_to_join is None:
+ columns_to_join = ['grantee_group.instances.system_metadata',
+ 'grantee_group.instances.info_cache']
+ query = (_security_group_rule_get_query(context).
+ filter_by(parent_group_id=security_group_id))
+ for column in columns_to_join:
+ query = query.options(joinedload_all(column))
+ return query.all()
+
+
+@require_context
+@pick_context_manager_reader
+def security_group_rule_get_by_instance(context, instance_uuid):
+ return (_security_group_rule_get_query(context).
+ join('parent_group', 'instances').
+ filter_by(uuid=instance_uuid).
+ options(joinedload('grantee_group')).
+ all())
+
+
+@require_context
+@pick_context_manager_writer
+def security_group_rule_create(context, values):
+ return _security_group_rule_create(context, values)
+
+
+@require_context
+@pick_context_manager_writer
+def security_group_rule_destroy(context, security_group_rule_id):
+ count = (_security_group_rule_get_query(context).
+ filter_by(id=security_group_rule_id).
+ soft_delete())
+ if count == 0:
+ raise exception.SecurityGroupNotFoundForRule(
+ rule_id=security_group_rule_id)
+
+
+@require_context
+@pick_context_manager_reader
+def security_group_rule_count_by_group(context, security_group_id):
+ return (model_query(context, models.SecurityGroupIngressRule,
+ read_deleted="no").
+ filter_by(parent_group_id=security_group_id).
+ count())
+
+
+###################
+
+
+def _security_group_rule_get_default_query(context):
+ return model_query(context, models.SecurityGroupIngressDefaultRule)
+
+
+@require_context
+@pick_context_manager_reader
+def security_group_default_rule_get(context, security_group_rule_default_id):
+ result = _security_group_rule_get_default_query(context).\
+ filter_by(id=security_group_rule_default_id).\
+ first()
+
+ if not result:
+ raise exception.SecurityGroupDefaultRuleNotFound(
+ rule_id=security_group_rule_default_id)
+
+ return result
+
+
+@pick_context_manager_writer
+def security_group_default_rule_destroy(context,
+ security_group_rule_default_id):
+ count = _security_group_rule_get_default_query(context).\
+ filter_by(id=security_group_rule_default_id).\
+ soft_delete()
+ if count == 0:
+ raise exception.SecurityGroupDefaultRuleNotFound(
+ rule_id=security_group_rule_default_id)
+
+
+@pick_context_manager_writer
+def security_group_default_rule_create(context, values):
+ security_group_default_rule_ref = models.SecurityGroupIngressDefaultRule()
+ security_group_default_rule_ref.update(values)
+ security_group_default_rule_ref.save(context.session)
+ return security_group_default_rule_ref
+
+
+@require_context
+@pick_context_manager_reader
+def security_group_default_rule_list(context):
+ return _security_group_rule_get_default_query(context).all()
+
+
+###################
+
+
+@pick_context_manager_writer
+def provider_fw_rule_create(context, rule):
+ fw_rule_ref = models.ProviderFirewallRule()
+ fw_rule_ref.update(rule)
+ fw_rule_ref.save(context.session)
+ return fw_rule_ref
+
+
+@pick_context_manager_reader
+def provider_fw_rule_get_all(context):
+ return model_query(context, models.ProviderFirewallRule).all()
+
+
+@pick_context_manager_writer
+def provider_fw_rule_destroy(context, rule_id):
+ context.session.query(models.ProviderFirewallRule).\
+ filter_by(id=rule_id).\
+ soft_delete()
+
+
+###################
+
+
+@require_context
+@pick_context_manager_writer
+def project_get_networks(context, project_id, associate=True):
+ # NOTE(tr3buchet): as before this function will associate
+ # a project with a network if it doesn't have one and
+ # associate is true
+ result = model_query(context, models.Network, read_deleted="no").\
+ filter_by(project_id=project_id).\
+ all()
+
+ if not result:
+ if not associate:
+ return []
+
+ return [network_associate(context, project_id)]
+
+ return result
+
+
+###################
+
+
+@pick_context_manager_writer
+def migration_create(context, values):
+ migration = models.Migration()
+ migration.update(values)
+ migration.save(context.session)
+ return migration
+
+
+@pick_context_manager_writer
+def migration_update(context, id, values):
+ migration = migration_get(context, id)
+ migration.update(values)
+
+ return migration
+
+
+@pick_context_manager_reader
+def migration_get(context, id):
+ result = model_query(context, models.Migration, read_deleted="yes").\
+ filter_by(id=id).\
+ first()
+
+ if not result:
+ raise exception.MigrationNotFound(migration_id=id)
+
+ return result
+
+
+@pick_context_manager_reader
+def migration_get_by_uuid(context, migration_uuid):
+ result = model_query(context, models.Migration, read_deleted="yes").\
+ filter_by(uuid=migration_uuid).\
+ first()
+
+ if not result:
+ raise exception.MigrationNotFound(migration_id=migration_uuid)
+
+ return result
+
+
+@pick_context_manager_reader
+def migration_get_by_id_and_instance(context, id, instance_uuid):
+ result = model_query(context, models.Migration).\
+ filter_by(id=id).\
+ filter_by(instance_uuid=instance_uuid).\
+ first()
+
+ if not result:
+ raise exception.MigrationNotFoundForInstance(migration_id=id,
+ instance_id=instance_uuid)
+
+ return result
+
+
+@pick_context_manager_reader
+def migration_get_by_instance_and_status(context, instance_uuid, status):
+ result = model_query(context, models.Migration, read_deleted="yes").\
+ filter_by(instance_uuid=instance_uuid).\
+ filter_by(status=status).\
+ first()
+
+ if not result:
+ raise exception.MigrationNotFoundByStatus(instance_id=instance_uuid,
+ status=status)
+
+ return result
+
+
+@pick_context_manager_reader_allow_async
+def migration_get_unconfirmed_by_dest_compute(context, confirm_window,
+ dest_compute):
+ confirm_window = (timeutils.utcnow() -
+ datetime.timedelta(seconds=confirm_window))
+
+ return model_query(context, models.Migration, read_deleted="yes").\
+ filter(models.Migration.updated_at <= confirm_window).\
+ filter_by(status="finished").\
+ filter_by(dest_compute=dest_compute).\
+ all()
+
+
+@pick_context_manager_reader
+def migration_get_in_progress_by_host_and_node(context, host, node):
+ # TODO(mriedem): Tracking what various code flows set for
+ # migration status is nutty, since it happens all over the place
+ # and several of the statuses are redundant (done and completed).
+ # We need to define these in an enum somewhere and just update
+ # that one central place that defines what "in progress" means.
+ # NOTE(mriedem): The 'finished' status is not in this list because
+ # 'finished' means a resize is finished on the destination host
+ # and the instance is in VERIFY_RESIZE state, so the end state
+ # for a resize is actually 'confirmed' or 'reverted'.
+ return model_query(context, models.Migration).\
+ filter(or_(and_(models.Migration.source_compute == host,
+ models.Migration.source_node == node),
+ and_(models.Migration.dest_compute == host,
+ models.Migration.dest_node == node))).\
+ filter(~models.Migration.status.in_(['accepted', 'confirmed',
+ 'reverted', 'error',
+ 'failed', 'completed',
+ 'cancelled', 'done'])).\
+ options(joinedload_all('instance.system_metadata')).\
+ all()
+
+
+@pick_context_manager_reader
+def migration_get_in_progress_by_instance(context, instance_uuid,
+ migration_type=None):
+ # TODO(Shaohe Feng) we should share the in-progress list.
+ # TODO(Shaohe Feng) will also summarize all status to a new
+ # MigrationStatus class.
+ query = model_query(context, models.Migration).\
+ filter_by(instance_uuid=instance_uuid).\
+ filter(models.Migration.status.in_(['queued', 'preparing',
+ 'running',
+ 'post-migrating']))
+ if migration_type:
+ query = query.filter(models.Migration.migration_type == migration_type)
+
+ return query.all()
+
+
+@pick_context_manager_reader
+def migration_get_all_by_filters(context, filters,
+ sort_keys=None, sort_dirs=None,
+ limit=None, marker=None):
+ if limit == 0:
+ return []
+
+ query = model_query(context, models.Migration)
+ if "uuid" in filters:
+ # The uuid filter is here for the MigrationLister and multi-cell
+ # paging support in the compute API.
+ uuid = filters["uuid"]
+ uuid = [uuid] if isinstance(uuid, six.string_types) else uuid
+ query = query.filter(models.Migration.uuid.in_(uuid))
+
+ model_object = models.Migration
+ query = _get_query_nova_resource_by_changes_time(query,
+ filters,
+ model_object)
+
+ if "status" in filters:
+ status = filters["status"]
+ status = [status] if isinstance(status, six.string_types) else status
+ query = query.filter(models.Migration.status.in_(status))
+ if "host" in filters:
+ host = filters["host"]
+ query = query.filter(or_(models.Migration.source_compute == host,
+ models.Migration.dest_compute == host))
+ elif "source_compute" in filters:
+ host = filters['source_compute']
+ query = query.filter(models.Migration.source_compute == host)
+ if "migration_type" in filters:
+ migtype = filters["migration_type"]
+ query = query.filter(models.Migration.migration_type == migtype)
+ if "hidden" in filters:
+ hidden = filters["hidden"]
+ query = query.filter(models.Migration.hidden == hidden)
+ if "instance_uuid" in filters:
+ instance_uuid = filters["instance_uuid"]
+ query = query.filter(models.Migration.instance_uuid == instance_uuid)
+ if marker:
+ try:
+ marker = migration_get_by_uuid(context, marker)
+ except exception.MigrationNotFound:
+ raise exception.MarkerNotFound(marker=marker)
+ if limit or marker or sort_keys or sort_dirs:
+ # Default sort by desc(['created_at', 'id'])
+ sort_keys, sort_dirs = process_sort_params(sort_keys, sort_dirs,
+ default_dir='desc')
+ return sqlalchemyutils.paginate_query(query,
+ models.Migration,
+ limit=limit,
+ sort_keys=sort_keys,
+ marker=marker,
+ sort_dirs=sort_dirs).all()
+ else:
+ return query.all()
+
+
+@require_context
+@pick_context_manager_reader_allow_async
+def migration_get_by_sort_filters(context, sort_keys, sort_dirs, values):
+ """Attempt to get a single migration based on a combination of sort
+ keys, directions and filter values. This is used to try to find a
+ marker migration when we don't have a marker uuid.
+
+ This returns just a uuid of the migration that matched.
+ """
+ model = models.Migration
+ return _model_get_uuid_by_sort_filters(context, model, sort_keys,
+ sort_dirs, values)
+
+
+@pick_context_manager_writer
+def migration_migrate_to_uuid(context, count):
+ # Avoid circular import
+ from nova import objects
+
+ db_migrations = model_query(context, models.Migration).filter_by(
+ uuid=None).limit(count).all()
+
+ done = 0
+ for db_migration in db_migrations:
+ mig = objects.Migration(context)
+ mig._from_db_object(context, mig, db_migration)
+ done += 1
+
+ # We don't have any situation where we can (detectably) not
+ # migrate a thing, so report anything that matched as "completed".
+ return done, done
+
+
+##################
+
+
+@pick_context_manager_writer
+def console_pool_create(context, values):
+ pool = models.ConsolePool()
+ pool.update(values)
+ try:
+ pool.save(context.session)
+ except db_exc.DBDuplicateEntry:
+ raise exception.ConsolePoolExists(
+ host=values["host"],
+ console_type=values["console_type"],
+ compute_host=values["compute_host"],
+ )
+ return pool
+
+
+@pick_context_manager_reader
+def console_pool_get_by_host_type(context, compute_host, host,
+ console_type):
+
+ result = model_query(context, models.ConsolePool, read_deleted="no").\
+ filter_by(host=host).\
+ filter_by(console_type=console_type).\
+ filter_by(compute_host=compute_host).\
+ options(joinedload('consoles')).\
+ first()
+
+ if not result:
+ raise exception.ConsolePoolNotFoundForHostType(
+ host=host, console_type=console_type,
+ compute_host=compute_host)
+
+ return result
+
+
+@pick_context_manager_reader
+def console_pool_get_all_by_host_type(context, host, console_type):
+ return model_query(context, models.ConsolePool, read_deleted="no").\
+ filter_by(host=host).\
+ filter_by(console_type=console_type).\
+ options(joinedload('consoles')).\
+ all()
+
+
+##################
+
+
+@pick_context_manager_writer
+def console_create(context, values):
+ console = models.Console()
+ console.update(values)
+ console.save(context.session)
+ return console
+
+
+@pick_context_manager_writer
+def console_delete(context, console_id):
+ # NOTE(mdragon): consoles are meant to be transient.
+ context.session.query(models.Console).\
+ filter_by(id=console_id).\
+ delete()
+
+
+@pick_context_manager_reader
+def console_get_by_pool_instance(context, pool_id, instance_uuid):
+ result = model_query(context, models.Console, read_deleted="yes").\
+ filter_by(pool_id=pool_id).\
+ filter_by(instance_uuid=instance_uuid).\
+ options(joinedload('pool')).\
+ first()
+
+ if not result:
+ raise exception.ConsoleNotFoundInPoolForInstance(
+ pool_id=pool_id, instance_uuid=instance_uuid)
+
+ return result
+
+
+@pick_context_manager_reader
+def console_get_all_by_instance(context, instance_uuid, columns_to_join=None):
+ query = model_query(context, models.Console, read_deleted="yes").\
+ filter_by(instance_uuid=instance_uuid)
+ if columns_to_join:
+ for column in columns_to_join:
+ query = query.options(joinedload(column))
+ return query.all()
+
+
+@pick_context_manager_reader
+def console_get(context, console_id, instance_uuid=None):
+ query = model_query(context, models.Console, read_deleted="yes").\
+ filter_by(id=console_id).\
+ options(joinedload('pool'))
+
+ if instance_uuid is not None:
+ query = query.filter_by(instance_uuid=instance_uuid)
+
+ result = query.first()
+
+ if not result:
+ if instance_uuid:
+ raise exception.ConsoleNotFoundForInstance(
+ instance_uuid=instance_uuid)
+ else:
+ raise exception.ConsoleNotFound(console_id=console_id)
+
+ return result
+
+
+##################
+
+
+@pick_context_manager_writer
+def cell_create(context, values):
+ cell = models.Cell()
+ cell.update(values)
+ try:
+ cell.save(context.session)
+ except db_exc.DBDuplicateEntry:
+ raise exception.CellExists(name=values['name'])
+ return cell
+
+
+def _cell_get_by_name_query(context, cell_name):
+ return model_query(context, models.Cell).filter_by(name=cell_name)
+
+
+@pick_context_manager_writer
+def cell_update(context, cell_name, values):
+ cell_query = _cell_get_by_name_query(context, cell_name)
+ if not cell_query.update(values):
+ raise exception.CellNotFound(cell_name=cell_name)
+ cell = cell_query.first()
+ return cell
+
+
+@pick_context_manager_writer
+def cell_delete(context, cell_name):
+ return _cell_get_by_name_query(context, cell_name).soft_delete()
+
+
+@pick_context_manager_reader
+def cell_get(context, cell_name):
+ result = _cell_get_by_name_query(context, cell_name).first()
+ if not result:
+ raise exception.CellNotFound(cell_name=cell_name)
+ return result
+
+
+@pick_context_manager_reader
+def cell_get_all(context):
+ return model_query(context, models.Cell, read_deleted="no").all()
+
+
+########################
+# User-provided metadata
+
+def _instance_metadata_get_multi(context, instance_uuids):
+ if not instance_uuids:
+ return []
+ return model_query(context, models.InstanceMetadata).filter(
+ models.InstanceMetadata.instance_uuid.in_(instance_uuids))
+
+
+def _instance_metadata_get_query(context, instance_uuid):
+ return model_query(context, models.InstanceMetadata, read_deleted="no").\
+ filter_by(instance_uuid=instance_uuid)
+
+
+@require_context
+@pick_context_manager_reader
+def instance_metadata_get(context, instance_uuid):
+ rows = _instance_metadata_get_query(context, instance_uuid).all()
+ return {row['key']: row['value'] for row in rows}
+
+
+@require_context
+@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
+@pick_context_manager_writer
+def instance_metadata_delete(context, instance_uuid, key):
+ _instance_metadata_get_query(context, instance_uuid).\
+ filter_by(key=key).\
+ soft_delete()
+
+
+@require_context
+@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
+@pick_context_manager_writer
+def instance_metadata_update(context, instance_uuid, metadata, delete):
+ all_keys = metadata.keys()
+ if delete:
+ _instance_metadata_get_query(context, instance_uuid).\
+ filter(~models.InstanceMetadata.key.in_(all_keys)).\
+ soft_delete(synchronize_session=False)
+
+ already_existing_keys = []
+ meta_refs = _instance_metadata_get_query(context, instance_uuid).\
+ filter(models.InstanceMetadata.key.in_(all_keys)).\
+ all()
+
+ for meta_ref in meta_refs:
+ already_existing_keys.append(meta_ref.key)
+ meta_ref.update({"value": metadata[meta_ref.key]})
+
+ new_keys = set(all_keys) - set(already_existing_keys)
+ for key in new_keys:
+ meta_ref = models.InstanceMetadata()
+ meta_ref.update({"key": key, "value": metadata[key],
+ "instance_uuid": instance_uuid})
+ context.session.add(meta_ref)
+
+ return metadata
+
+
+#######################
+# System-owned metadata
+
+
+def _instance_system_metadata_get_multi(context, instance_uuids):
+ if not instance_uuids:
+ return []
+ return model_query(context, models.InstanceSystemMetadata,
+ read_deleted='yes').filter(
+ models.InstanceSystemMetadata.instance_uuid.in_(instance_uuids))
+
+
+def _instance_system_metadata_get_query(context, instance_uuid):
+ return model_query(context, models.InstanceSystemMetadata).\
+ filter_by(instance_uuid=instance_uuid)
+
+
+@require_context
+@pick_context_manager_reader
+def instance_system_metadata_get(context, instance_uuid):
+ rows = _instance_system_metadata_get_query(context, instance_uuid).all()
+ return {row['key']: row['value'] for row in rows}
+
+
+@require_context
+@pick_context_manager_writer
+def instance_system_metadata_update(context, instance_uuid, metadata, delete):
+ all_keys = metadata.keys()
+ if delete:
+ _instance_system_metadata_get_query(context, instance_uuid).\
+ filter(~models.InstanceSystemMetadata.key.in_(all_keys)).\
+ soft_delete(synchronize_session=False)
+
+ already_existing_keys = []
+ meta_refs = _instance_system_metadata_get_query(context, instance_uuid).\
+ filter(models.InstanceSystemMetadata.key.in_(all_keys)).\
+ all()
+
+ for meta_ref in meta_refs:
+ already_existing_keys.append(meta_ref.key)
+ meta_ref.update({"value": metadata[meta_ref.key]})
+
+ new_keys = set(all_keys) - set(already_existing_keys)
+ for key in new_keys:
+ meta_ref = models.InstanceSystemMetadata()
+ meta_ref.update({"key": key, "value": metadata[key],
+ "instance_uuid": instance_uuid})
+ context.session.add(meta_ref)
+
+ return metadata
+
+
+####################
+
+
+@pick_context_manager_writer
+def agent_build_create(context, values):
+ agent_build_ref = models.AgentBuild()
+ agent_build_ref.update(values)
+ try:
+ agent_build_ref.save(context.session)
+ except db_exc.DBDuplicateEntry:
+ raise exception.AgentBuildExists(hypervisor=values['hypervisor'],
+ os=values['os'], architecture=values['architecture'])
+ return agent_build_ref
+
+
+@pick_context_manager_reader
+def agent_build_get_by_triple(context, hypervisor, os, architecture):
+ return model_query(context, models.AgentBuild, read_deleted="no").\
+ filter_by(hypervisor=hypervisor).\
+ filter_by(os=os).\
+ filter_by(architecture=architecture).\
+ first()
+
+
+@pick_context_manager_reader
+def agent_build_get_all(context, hypervisor=None):
+ if hypervisor:
+ return model_query(context, models.AgentBuild, read_deleted="no").\
+ filter_by(hypervisor=hypervisor).\
+ all()
+ else:
+ return model_query(context, models.AgentBuild, read_deleted="no").\
+ all()
+
+
+@pick_context_manager_writer
+def agent_build_destroy(context, agent_build_id):
+ rows_affected = model_query(context, models.AgentBuild).filter_by(
+ id=agent_build_id).soft_delete()
+ if rows_affected == 0:
+ raise exception.AgentBuildNotFound(id=agent_build_id)
+
+
+@pick_context_manager_writer
+def agent_build_update(context, agent_build_id, values):
+ rows_affected = model_query(context, models.AgentBuild).\
+ filter_by(id=agent_build_id).\
+ update(values)
+ if rows_affected == 0:
+ raise exception.AgentBuildNotFound(id=agent_build_id)
+
+
+####################
+
+@require_context
+@pick_context_manager_reader_allow_async
+def bw_usage_get(context, uuid, start_period, mac):
+ values = {'start_period': start_period}
+ values = convert_objects_related_datetimes(values, 'start_period')
+ return model_query(context, models.BandwidthUsage, read_deleted="yes").\
+ filter_by(start_period=values['start_period']).\
+ filter_by(uuid=uuid).\
+ filter_by(mac=mac).\
+ first()
+
+
+@require_context
+@pick_context_manager_reader_allow_async
+def bw_usage_get_by_uuids(context, uuids, start_period):
+ values = {'start_period': start_period}
+ values = convert_objects_related_datetimes(values, 'start_period')
+ return (
+ model_query(context, models.BandwidthUsage, read_deleted="yes").
+ filter(models.BandwidthUsage.uuid.in_(uuids)).
+ filter_by(start_period=values['start_period']).
+ all()
+ )
+
+
+@require_context
+@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
+@pick_context_manager_writer
+def bw_usage_update(context, uuid, mac, start_period, bw_in, bw_out,
+ last_ctr_in, last_ctr_out, last_refreshed=None):
+
+ if last_refreshed is None:
+ last_refreshed = timeutils.utcnow()
+
+ # NOTE(comstud): More often than not, we'll be updating records vs
+ # creating records. Optimize accordingly, trying to update existing
+ # records. Fall back to creation when no rows are updated.
+ ts_values = {'last_refreshed': last_refreshed,
+ 'start_period': start_period}
+ ts_keys = ('start_period', 'last_refreshed')
+ ts_values = convert_objects_related_datetimes(ts_values, *ts_keys)
+ values = {'last_refreshed': ts_values['last_refreshed'],
+ 'last_ctr_in': last_ctr_in,
+ 'last_ctr_out': last_ctr_out,
+ 'bw_in': bw_in,
+ 'bw_out': bw_out}
+ # NOTE(pkholkin): order_by() is needed here to ensure that the
+ # same record is updated every time. It can be removed after adding
+ # unique constraint to this model.
+ bw_usage = model_query(context, models.BandwidthUsage,
+ read_deleted='yes').\
+ filter_by(start_period=ts_values['start_period']).\
+ filter_by(uuid=uuid).\
+ filter_by(mac=mac).\
+ order_by(asc(models.BandwidthUsage.id)).first()
+
+ if bw_usage:
+ bw_usage.update(values)
+ return bw_usage
+
+ bwusage = models.BandwidthUsage()
+ bwusage.start_period = ts_values['start_period']
+ bwusage.uuid = uuid
+ bwusage.mac = mac
+ bwusage.last_refreshed = ts_values['last_refreshed']
+ bwusage.bw_in = bw_in
+ bwusage.bw_out = bw_out
+ bwusage.last_ctr_in = last_ctr_in
+ bwusage.last_ctr_out = last_ctr_out
+ bwusage.save(context.session)
+
+ return bwusage
+
+
+####################
+
+
+@require_context
+@pick_context_manager_reader
+def vol_get_usage_by_time(context, begin):
+ """Return volumes usage that have been updated after a specified time."""
+ return model_query(context, models.VolumeUsage, read_deleted="yes").\
+ filter(or_(models.VolumeUsage.tot_last_refreshed == null(),
+ models.VolumeUsage.tot_last_refreshed > begin,
+ models.VolumeUsage.curr_last_refreshed == null(),
+ models.VolumeUsage.curr_last_refreshed > begin,
+ )).all()
+
+
+@require_context
+@pick_context_manager_writer
+def vol_usage_update(context, id, rd_req, rd_bytes, wr_req, wr_bytes,
+ instance_id, project_id, user_id, availability_zone,
+ update_totals=False):
+
+ refreshed = timeutils.utcnow()
+
+ values = {}
+ # NOTE(dricco): We will be mostly updating current usage records vs
+ # updating total or creating records. Optimize accordingly.
+ if not update_totals:
+ values = {'curr_last_refreshed': refreshed,
+ 'curr_reads': rd_req,
+ 'curr_read_bytes': rd_bytes,
+ 'curr_writes': wr_req,
+ 'curr_write_bytes': wr_bytes,
+ 'instance_uuid': instance_id,
+ 'project_id': project_id,
+ 'user_id': user_id,
+ 'availability_zone': availability_zone}
+ else:
+ values = {'tot_last_refreshed': refreshed,
+ 'tot_reads': models.VolumeUsage.tot_reads + rd_req,
+ 'tot_read_bytes': models.VolumeUsage.tot_read_bytes +
+ rd_bytes,
+ 'tot_writes': models.VolumeUsage.tot_writes + wr_req,
+ 'tot_write_bytes': models.VolumeUsage.tot_write_bytes +
+ wr_bytes,
+ 'curr_reads': 0,
+ 'curr_read_bytes': 0,
+ 'curr_writes': 0,
+ 'curr_write_bytes': 0,
+ 'instance_uuid': instance_id,
+ 'project_id': project_id,
+ 'user_id': user_id,
+ 'availability_zone': availability_zone}
+
+ current_usage = model_query(context, models.VolumeUsage,
+ read_deleted="yes").\
+ filter_by(volume_id=id).\
+ first()
+ if current_usage:
+ if (rd_req < current_usage['curr_reads'] or
+ rd_bytes < current_usage['curr_read_bytes'] or
+ wr_req < current_usage['curr_writes'] or
+ wr_bytes < current_usage['curr_write_bytes']):
+ LOG.info("Volume(%s) has lower stats then what is in "
+ "the database. Instance must have been rebooted "
+ "or crashed. Updating totals.", id)
+ if not update_totals:
+ values['tot_reads'] = (models.VolumeUsage.tot_reads +
+ current_usage['curr_reads'])
+ values['tot_read_bytes'] = (
+ models.VolumeUsage.tot_read_bytes +
+ current_usage['curr_read_bytes'])
+ values['tot_writes'] = (models.VolumeUsage.tot_writes +
+ current_usage['curr_writes'])
+ values['tot_write_bytes'] = (
+ models.VolumeUsage.tot_write_bytes +
+ current_usage['curr_write_bytes'])
+ else:
+ values['tot_reads'] = (models.VolumeUsage.tot_reads +
+ current_usage['curr_reads'] +
+ rd_req)
+ values['tot_read_bytes'] = (
+ models.VolumeUsage.tot_read_bytes +
+ current_usage['curr_read_bytes'] + rd_bytes)
+ values['tot_writes'] = (models.VolumeUsage.tot_writes +
+ current_usage['curr_writes'] +
+ wr_req)
+ values['tot_write_bytes'] = (
+ models.VolumeUsage.tot_write_bytes +
+ current_usage['curr_write_bytes'] + wr_bytes)
+
+ current_usage.update(values)
+ current_usage.save(context.session)
+ context.session.refresh(current_usage)
+ return current_usage
+
+ vol_usage = models.VolumeUsage()
+ vol_usage.volume_id = id
+ vol_usage.instance_uuid = instance_id
+ vol_usage.project_id = project_id
+ vol_usage.user_id = user_id
+ vol_usage.availability_zone = availability_zone
+
+ if not update_totals:
+ vol_usage.curr_last_refreshed = refreshed
+ vol_usage.curr_reads = rd_req
+ vol_usage.curr_read_bytes = rd_bytes
+ vol_usage.curr_writes = wr_req
+ vol_usage.curr_write_bytes = wr_bytes
+ else:
+ vol_usage.tot_last_refreshed = refreshed
+ vol_usage.tot_reads = rd_req
+ vol_usage.tot_read_bytes = rd_bytes
+ vol_usage.tot_writes = wr_req
+ vol_usage.tot_write_bytes = wr_bytes
+
+ vol_usage.save(context.session)
+
+ return vol_usage
+
+
+####################
+
+
+@pick_context_manager_reader
+def s3_image_get(context, image_id):
+ """Find local s3 image represented by the provided id."""
+ result = model_query(context, models.S3Image, read_deleted="yes").\
+ filter_by(id=image_id).\
+ first()
+
+ if not result:
+ raise exception.ImageNotFound(image_id=image_id)
+
+ return result
+
+
+@pick_context_manager_reader
+def s3_image_get_by_uuid(context, image_uuid):
+ """Find local s3 image represented by the provided uuid."""
+ result = model_query(context, models.S3Image, read_deleted="yes").\
+ filter_by(uuid=image_uuid).\
+ first()
+
+ if not result:
+ raise exception.ImageNotFound(image_id=image_uuid)
+
+ return result
+
+
+@pick_context_manager_writer
+def s3_image_create(context, image_uuid):
+ """Create local s3 image represented by provided uuid."""
+ try:
+ s3_image_ref = models.S3Image()
+ s3_image_ref.update({'uuid': image_uuid})
+ s3_image_ref.save(context.session)
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return s3_image_ref
+
+
+####################
+
+
+@pick_context_manager_writer
+def instance_fault_create(context, values):
+ """Create a new InstanceFault."""
+ fault_ref = models.InstanceFault()
+ fault_ref.update(values)
+ fault_ref.save(context.session)
+ return dict(fault_ref)
+
+
+@pick_context_manager_reader
+def instance_fault_get_by_instance_uuids(context, instance_uuids,
+ latest=False):
+ """Get all instance faults for the provided instance_uuids.
+
+ :param instance_uuids: List of UUIDs of instances to grab faults for
+ :param latest: Optional boolean indicating we should only return the latest
+ fault for the instance
+ """
+ if not instance_uuids:
+ return {}
+
+ faults_tbl = models.InstanceFault.__table__
+ # NOTE(rpodolyaka): filtering by instance_uuids is performed in both
+ # code branches below for the sake of a better query plan. On change,
+ # make sure to update the other one as well.
+ query = model_query(context, models.InstanceFault,
+ [faults_tbl],
+ read_deleted='no')
+
+ if latest:
+ # NOTE(jaypipes): We join instance_faults to a derived table of the
+ # latest faults per instance UUID. The SQL produced below looks like
+ # this:
+ #
+ # SELECT instance_faults.*
+ # FROM instance_faults
+ # JOIN (
+ # SELECT instance_uuid, MAX(id) AS max_id
+ # FROM instance_faults
+ # WHERE instance_uuid IN ( ... )
+ # AND deleted = 0
+ # GROUP BY instance_uuid
+ # ) AS latest_faults
+ # ON instance_faults.id = latest_faults.max_id;
+ latest_faults = model_query(
+ context, models.InstanceFault,
+ [faults_tbl.c.instance_uuid,
+ sql.func.max(faults_tbl.c.id).label('max_id')],
+ read_deleted='no'
+ ).filter(
+ faults_tbl.c.instance_uuid.in_(instance_uuids)
+ ).group_by(
+ faults_tbl.c.instance_uuid
+ ).subquery(name="latest_faults")
+
+ query = query.join(latest_faults,
+ faults_tbl.c.id == latest_faults.c.max_id)
+ else:
+ query = query.filter(models.InstanceFault.instance_uuid.in_(
+ instance_uuids)).order_by(desc("id"))
+
+ output = {}
+ for instance_uuid in instance_uuids:
+ output[instance_uuid] = []
+
+ for row in query:
+ output[row.instance_uuid].append(row._asdict())
+
+ return output
+
+
+##################
+
+
+@pick_context_manager_writer
+def action_start(context, values):
+ convert_objects_related_datetimes(values, 'start_time', 'updated_at')
+ action_ref = models.InstanceAction()
+ action_ref.update(values)
+ action_ref.save(context.session)
+ return action_ref
+
+
+@pick_context_manager_writer
+def action_finish(context, values):
+ convert_objects_related_datetimes(values, 'start_time', 'finish_time',
+ 'updated_at')
+ query = model_query(context, models.InstanceAction).\
+ filter_by(instance_uuid=values['instance_uuid']).\
+ filter_by(request_id=values['request_id'])
+ if query.update(values) != 1:
+ raise exception.InstanceActionNotFound(
+ request_id=values['request_id'],
+ instance_uuid=values['instance_uuid'])
+ return query.one()
+
+
+@pick_context_manager_reader
+def actions_get(context, instance_uuid, limit=None, marker=None,
+ filters=None):
+ """Get all instance actions for the provided uuid and filters."""
+ if limit == 0:
+ return []
+
+ sort_keys = ['created_at', 'id']
+ sort_dirs = ['desc', 'desc']
+
+ query_prefix = model_query(context, models.InstanceAction).\
+ filter_by(instance_uuid=instance_uuid)
+
+ model_object = models.InstanceAction
+ query_prefix = _get_query_nova_resource_by_changes_time(query_prefix,
+ filters,
+ model_object)
+
+ if marker is not None:
+ marker = action_get_by_request_id(context, instance_uuid, marker)
+ if not marker:
+ raise exception.MarkerNotFound(marker=marker)
+ actions = sqlalchemyutils.paginate_query(query_prefix,
+ models.InstanceAction, limit,
+ sort_keys, marker=marker,
+ sort_dirs=sort_dirs).all()
+ return actions
+
+
+@pick_context_manager_reader
+def action_get_by_request_id(context, instance_uuid, request_id):
+ """Get the action by request_id and given instance."""
+ action = _action_get_by_request_id(context, instance_uuid, request_id)
+ return action
+
+
+def _action_get_by_request_id(context, instance_uuid, request_id):
+ result = model_query(context, models.InstanceAction).\
+ filter_by(instance_uuid=instance_uuid).\
+ filter_by(request_id=request_id).\
+ order_by(desc("created_at"), desc("id")).\
+ first()
+ return result
+
+
+def _action_get_last_created_by_instance_uuid(context, instance_uuid):
+ result = (model_query(context, models.InstanceAction).
+ filter_by(instance_uuid=instance_uuid).
+ order_by(desc("created_at"), desc("id")).
+ first())
+ return result
+
+
+@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
+@pick_context_manager_writer
+def action_event_start(context, values):
+ """Start an event on an instance action."""
+ convert_objects_related_datetimes(values, 'start_time')
+ action = _action_get_by_request_id(context, values['instance_uuid'],
+ values['request_id'])
+ # When nova-compute restarts, the context is generated again in
+ # init_host workflow, the request_id was different with the request_id
+ # recorded in InstanceAction, so we can't get the original record
+ # according to request_id. Try to get the last created action so that
+ # init_instance can continue to finish the recovery action, like:
+ # powering_off, unpausing, and so on.
+ update_action = True
+ if not action and not context.project_id:
+ action = _action_get_last_created_by_instance_uuid(
+ context, values['instance_uuid'])
+ # If we couldn't find an action by the request_id, we don't want to
+ # update this action since it likely represents an inactive action.
+ update_action = False
+
+ if not action:
+ raise exception.InstanceActionNotFound(
+ request_id=values['request_id'],
+ instance_uuid=values['instance_uuid'])
+
+ values['action_id'] = action['id']
+
+ event_ref = models.InstanceActionEvent()
+ event_ref.update(values)
+ context.session.add(event_ref)
+
+ # Update action updated_at.
+ if update_action:
+ action.update({'updated_at': values['start_time']})
+ action.save(context.session)
+
+ return event_ref
+
+
+# NOTE: We need the retry_on_deadlock decorator for cases like resize where
+# a lot of events are happening at once between multiple hosts trying to
+# update the same action record in a small time window.
+@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
+@pick_context_manager_writer
+def action_event_finish(context, values):
+ """Finish an event on an instance action."""
+ convert_objects_related_datetimes(values, 'start_time', 'finish_time')
+ action = _action_get_by_request_id(context, values['instance_uuid'],
+ values['request_id'])
+ # When nova-compute restarts, the context is generated again in
+ # init_host workflow, the request_id was different with the request_id
+ # recorded in InstanceAction, so we can't get the original record
+ # according to request_id. Try to get the last created action so that
+ # init_instance can continue to finish the recovery action, like:
+ # powering_off, unpausing, and so on.
+ update_action = True
+ if not action and not context.project_id:
+ action = _action_get_last_created_by_instance_uuid(
+ context, values['instance_uuid'])
+ # If we couldn't find an action by the request_id, we don't want to
+ # update this action since it likely represents an inactive action.
+ update_action = False
+
+ if not action:
+ raise exception.InstanceActionNotFound(
+ request_id=values['request_id'],
+ instance_uuid=values['instance_uuid'])
+
+ event_ref = model_query(context, models.InstanceActionEvent).\
+ filter_by(action_id=action['id']).\
+ filter_by(event=values['event']).\
+ first()
+
+ if not event_ref:
+ raise exception.InstanceActionEventNotFound(action_id=action['id'],
+ event=values['event'])
+ event_ref.update(values)
+
+ if values['result'].lower() == 'error':
+ action.update({'message': 'Error'})
+
+ # Update action updated_at.
+ if update_action:
+ action.update({'updated_at': values['finish_time']})
+ action.save(context.session)
+
+ return event_ref
+
+
+@pick_context_manager_reader
+def action_events_get(context, action_id):
+ events = model_query(context, models.InstanceActionEvent).\
+ filter_by(action_id=action_id).\
+ order_by(desc("created_at"), desc("id")).\
+ all()
+
+ return events
+
+
+@pick_context_manager_reader
+def action_event_get_by_id(context, action_id, event_id):
+ event = model_query(context, models.InstanceActionEvent).\
+ filter_by(action_id=action_id).\
+ filter_by(id=event_id).\
+ first()
+
+ return event
+
+
+##################
+
+
+@require_context
+@pick_context_manager_writer
+def ec2_instance_create(context, instance_uuid, id=None):
+ """Create ec2 compatible instance by provided uuid."""
+ ec2_instance_ref = models.InstanceIdMapping()
+ ec2_instance_ref.update({'uuid': instance_uuid})
+ if id is not None:
+ ec2_instance_ref.update({'id': id})
+
+ ec2_instance_ref.save(context.session)
+
+ return ec2_instance_ref
+
+
+@require_context
+@pick_context_manager_reader
+def ec2_instance_get_by_uuid(context, instance_uuid):
+ result = _ec2_instance_get_query(context).\
+ filter_by(uuid=instance_uuid).\
+ first()
+
+ if not result:
+ raise exception.InstanceNotFound(instance_id=instance_uuid)
+
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def ec2_instance_get_by_id(context, instance_id):
+ result = _ec2_instance_get_query(context).\
+ filter_by(id=instance_id).\
+ first()
+
+ if not result:
+ raise exception.InstanceNotFound(instance_id=instance_id)
+
+ return result
+
+
+@require_context
+@pick_context_manager_reader
+def get_instance_uuid_by_ec2_id(context, ec2_id):
+ result = ec2_instance_get_by_id(context, ec2_id)
+ return result['uuid']
+
+
+def _ec2_instance_get_query(context):
+ return model_query(context, models.InstanceIdMapping, read_deleted='yes')
+
+
+##################
+
+
+def _task_log_get_query(context, task_name, period_beginning,
+ period_ending, host=None, state=None):
+ values = {'period_beginning': period_beginning,
+ 'period_ending': period_ending}
+ values = convert_objects_related_datetimes(values, *values.keys())
+
+ query = model_query(context, models.TaskLog).\
+ filter_by(task_name=task_name).\
+ filter_by(period_beginning=values['period_beginning']).\
+ filter_by(period_ending=values['period_ending'])
+ if host is not None:
+ query = query.filter_by(host=host)
+ if state is not None:
+ query = query.filter_by(state=state)
+ return query
+
+
+@pick_context_manager_reader
+def task_log_get(context, task_name, period_beginning, period_ending, host,
+ state=None):
+ return _task_log_get_query(context, task_name, period_beginning,
+ period_ending, host, state).first()
+
+
+@pick_context_manager_reader
+def task_log_get_all(context, task_name, period_beginning, period_ending,
+ host=None, state=None):
+ return _task_log_get_query(context, task_name, period_beginning,
+ period_ending, host, state).all()
+
+
+@pick_context_manager_writer
+def task_log_begin_task(context, task_name, period_beginning, period_ending,
+ host, task_items=None, message=None):
+ values = {'period_beginning': period_beginning,
+ 'period_ending': period_ending}
+ values = convert_objects_related_datetimes(values, *values.keys())
+
+ task = models.TaskLog()
+ task.task_name = task_name
+ task.period_beginning = values['period_beginning']
+ task.period_ending = values['period_ending']
+ task.host = host
+ task.state = "RUNNING"
+ if message:
+ task.message = message
+ if task_items:
+ task.task_items = task_items
+ try:
+ task.save(context.session)
+ except db_exc.DBDuplicateEntry:
+ raise exception.TaskAlreadyRunning(task_name=task_name, host=host)
+
+
+@pick_context_manager_writer
+def task_log_end_task(context, task_name, period_beginning, period_ending,
+ host, errors, message=None):
+ values = dict(state="DONE", errors=errors)
+ if message:
+ values["message"] = message
+
+ rows = _task_log_get_query(context, task_name, period_beginning,
+ period_ending, host).update(values)
+ if rows == 0:
+ # It's not running!
+ raise exception.TaskNotRunning(task_name=task_name, host=host)
+
+
+##################
+
+
+def _archive_if_instance_deleted(table, shadow_table, instances, conn,
+ max_rows):
+ """Look for records that pertain to deleted instances, but may not be
+ deleted themselves. This catches cases where we delete an instance,
+ but leave some residue because of a failure in a cleanup path or
+ similar.
+
+ Logic is: if I have a column called instance_uuid, and that instance
+ is deleted, then I can be deleted.
+ """
+ query_insert = shadow_table.insert(inline=True).\
+ from_select(
+ [c.name for c in table.c],
+ sql.select(
+ [table],
+ and_(instances.c.deleted != instances.c.deleted.default.arg,
+ instances.c.uuid == table.c.instance_uuid)).
+ order_by(table.c.id).limit(max_rows))
+
+ query_delete = sql.select(
+ [table.c.id],
+ and_(instances.c.deleted != instances.c.deleted.default.arg,
+ instances.c.uuid == table.c.instance_uuid)).\
+ order_by(table.c.id).limit(max_rows)
+ delete_statement = DeleteFromSelect(table, query_delete,
+ table.c.id)
+
+ try:
+ with conn.begin():
+ conn.execute(query_insert)
+ result_delete = conn.execute(delete_statement)
+ return result_delete.rowcount
+ except db_exc.DBReferenceError as ex:
+ LOG.warning('Failed to archive %(table)s: %(error)s',
+ {'table': table.name,
+ 'error': six.text_type(ex)})
+ return 0
+
+
+def _archive_deleted_rows_for_table(tablename, max_rows):
+ """Move up to max_rows rows from one tables to the corresponding
+ shadow table.
+
+ :returns: number of rows archived
+ """
+ engine = get_engine()
+ conn = engine.connect()
+ metadata = MetaData()
+ metadata.bind = engine
+ # NOTE(tdurakov): table metadata should be received
+ # from models, not db tables. Default value specified by SoftDeleteMixin
+ # is known only by models, not DB layer.
+ # IMPORTANT: please do not change source of metadata information for table.
+ table = models.BASE.metadata.tables[tablename]
+
+ shadow_tablename = _SHADOW_TABLE_PREFIX + tablename
+ rows_archived = 0
+ deleted_instance_uuids = []
+ try:
+ shadow_table = Table(shadow_tablename, metadata, autoload=True)
+ except NoSuchTableError:
+ # No corresponding shadow table; skip it.
+ return rows_archived, deleted_instance_uuids
+
+ if tablename == "dns_domains":
+ # We have one table (dns_domains) where the key is called
+ # "domain" rather than "id"
+ column = table.c.domain
+ else:
+ column = table.c.id
+ # NOTE(guochbo): Use DeleteFromSelect to avoid
+ # database's limit of maximum parameter in one SQL statement.
+ deleted_column = table.c.deleted
+ columns = [c.name for c in table.c]
+
+ # NOTE(clecomte): Tables instance_actions and instances_actions_events
+ # have to be manage differently so we soft-delete them here to let
+ # the archive work the same for all tables
+ # NOTE(takashin): The record in table migrations should be
+ # soft deleted when the instance is deleted.
+ # This is just for upgrading.
+ if tablename in ("instance_actions", "migrations"):
+ instances = models.BASE.metadata.tables["instances"]
+ deleted_instances = sql.select([instances.c.uuid]).\
+ where(instances.c.deleted != instances.c.deleted.default.arg)
+ update_statement = table.update().values(deleted=table.c.id).\
+ where(table.c.instance_uuid.in_(deleted_instances))
+
+ conn.execute(update_statement)
+
+ elif tablename == "instance_actions_events":
+ # NOTE(clecomte): we have to grab all the relation from
+ # instances because instance_actions_events rely on
+ # action_id and not uuid
+ instances = models.BASE.metadata.tables["instances"]
+ instance_actions = models.BASE.metadata.tables["instance_actions"]
+ deleted_instances = sql.select([instances.c.uuid]).\
+ where(instances.c.deleted != instances.c.deleted.default.arg)
+ deleted_actions = sql.select([instance_actions.c.id]).\
+ where(instance_actions.c.instance_uuid.in_(deleted_instances))
+
+ update_statement = table.update().values(deleted=table.c.id).\
+ where(table.c.action_id.in_(deleted_actions))
+
+ conn.execute(update_statement)
+
+ select = sql.select([column],
+ deleted_column != deleted_column.default.arg).\
+ order_by(column).limit(max_rows)
+ rows = conn.execute(select).fetchall()
+ records = [r[0] for r in rows]
+
+ if records:
+ insert = shadow_table.insert(inline=True).\
+ from_select(columns, sql.select([table], column.in_(records)))
+ delete = table.delete().where(column.in_(records))
+ # NOTE(tssurya): In order to facilitate the deletion of records from
+ # instance_mappings and request_specs tables in the nova_api DB, the
+ # rows of deleted instances from the instances table are stored prior
+ # to their deletion. Basically the uuids of the archived instances
+ # are queried and returned.
+ if tablename == "instances":
+ query_select = sql.select([table.c.uuid], table.c.id.in_(records))
+ rows = conn.execute(query_select).fetchall()
+ deleted_instance_uuids = [r[0] for r in rows]
+
+ try:
+ # Group the insert and delete in a transaction.
+ with conn.begin():
+ conn.execute(insert)
+ result_delete = conn.execute(delete)
+ rows_archived = result_delete.rowcount
+ except db_exc.DBReferenceError as ex:
+ # A foreign key constraint keeps us from deleting some of
+ # these rows until we clean up a dependent table. Just
+ # skip this table for now; we'll come back to it later.
+ LOG.warning("IntegrityError detected when archiving table "
+ "%(tablename)s: %(error)s",
+ {'tablename': tablename, 'error': six.text_type(ex)})
+
+ if ((max_rows is None or rows_archived < max_rows)
+ and 'instance_uuid' in columns):
+ instances = models.BASE.metadata.tables['instances']
+ limit = max_rows - rows_archived if max_rows is not None else None
+ extra = _archive_if_instance_deleted(table, shadow_table, instances,
+ conn, limit)
+ rows_archived += extra
+
+ return rows_archived, deleted_instance_uuids
+
+
+def archive_deleted_rows(max_rows=None):
+ """Move up to max_rows rows from production tables to the corresponding
+ shadow tables.
+
+ :returns: dict that maps table name to number of rows archived from that
+ table, for example:
+
+ ::
+
+ {
+ 'instances': 5,
+ 'block_device_mapping': 5,
+ 'pci_devices': 2,
+ }
+
+ """
+ table_to_rows_archived = {}
+ deleted_instance_uuids = []
+ total_rows_archived = 0
+ meta = MetaData(get_engine(use_slave=True))
+ meta.reflect()
+ # Reverse sort the tables so we get the leaf nodes first for processing.
+ for table in reversed(meta.sorted_tables):
+ tablename = table.name
+ rows_archived = 0
+ # skip the special sqlalchemy-migrate migrate_version table and any
+ # shadow tables
+ if (tablename == 'migrate_version' or
+ tablename.startswith(_SHADOW_TABLE_PREFIX)):
+ continue
+ rows_archived,\
+ deleted_instance_uuid = _archive_deleted_rows_for_table(
+ tablename, max_rows=max_rows - total_rows_archived)
+ total_rows_archived += rows_archived
+ if tablename == 'instances':
+ deleted_instance_uuids = deleted_instance_uuid
+ # Only report results for tables that had updates.
+ if rows_archived:
+ table_to_rows_archived[tablename] = rows_archived
+ if total_rows_archived >= max_rows:
+ break
+ return table_to_rows_archived, deleted_instance_uuids
+
+
+def _purgeable_tables(metadata):
+ return [t for t in metadata.sorted_tables
+ if (t.name.startswith(_SHADOW_TABLE_PREFIX) and not
+ t.name.endswith('migrate_version'))]
+
+
+def purge_shadow_tables(context, before_date, status_fn=None):
+ engine = get_engine(context=context)
+ conn = engine.connect()
+ metadata = MetaData()
+ metadata.bind = engine
+ metadata.reflect()
+ total_deleted = 0
+
+ if status_fn is None:
+ status_fn = lambda m: None
+
+ # Some things never get formally deleted, and thus deleted_at
+ # is never set. So, prefer specific timestamp columns here
+ # for those special cases.
+ overrides = {
+ 'shadow_instance_actions': 'created_at',
+ 'shadow_instance_actions_events': 'created_at',
+ }
+
+ for table in _purgeable_tables(metadata):
+ if before_date is None:
+ col = None
+ elif table.name in overrides:
+ col = getattr(table.c, overrides[table.name])
+ elif hasattr(table.c, 'deleted_at'):
+ col = table.c.deleted_at
+ elif hasattr(table.c, 'updated_at'):
+ col = table.c.updated_at
+ elif hasattr(table.c, 'created_at'):
+ col = table.c.created_at
+ else:
+ status_fn(_('Unable to purge table %(table)s because it '
+ 'has no timestamp column') % {
+ 'table': table.name})
+ continue
+
+ if col is not None:
+ delete = table.delete().where(col < before_date)
+ else:
+ delete = table.delete()
+
+ deleted = conn.execute(delete)
+ if deleted.rowcount > 0:
+ status_fn(_('Deleted %(rows)i rows from %(table)s based on '
+ 'timestamp column %(col)s') % {
+ 'rows': deleted.rowcount,
+ 'table': table.name,
+ 'col': col is None and '(n/a)' or col.name})
+ total_deleted += deleted.rowcount
+
+ return total_deleted
+
+
+@pick_context_manager_writer
+def service_uuids_online_data_migration(context, max_count):
+ from nova.objects import service
+
+ count_all = 0
+ count_hit = 0
+
+ db_services = model_query(context, models.Service).filter_by(
+ uuid=None).limit(max_count)
+ for db_service in db_services:
+ count_all += 1
+ service_obj = service.Service._from_db_object(
+ context, service.Service(), db_service)
+ if 'uuid' in service_obj:
+ count_hit += 1
+ return count_all, count_hit
+
+
+####################
+
+
+@pick_context_manager_reader
+def pci_device_get_by_addr(context, node_id, dev_addr):
+ pci_dev_ref = model_query(context, models.PciDevice).\
+ filter_by(compute_node_id=node_id).\
+ filter_by(address=dev_addr).\
+ first()
+ if not pci_dev_ref:
+ raise exception.PciDeviceNotFound(node_id=node_id, address=dev_addr)
+ return pci_dev_ref
+
+
+@pick_context_manager_reader
+def pci_device_get_by_id(context, id):
+ pci_dev_ref = model_query(context, models.PciDevice).\
+ filter_by(id=id).\
+ first()
+ if not pci_dev_ref:
+ raise exception.PciDeviceNotFoundById(id=id)
+ return pci_dev_ref
+
+
+@pick_context_manager_reader
+def pci_device_get_all_by_node(context, node_id):
+ return model_query(context, models.PciDevice).\
+ filter_by(compute_node_id=node_id).\
+ all()
+
+
+@pick_context_manager_reader
+def pci_device_get_all_by_parent_addr(context, node_id, parent_addr):
+ return model_query(context, models.PciDevice).\
+ filter_by(compute_node_id=node_id).\
+ filter_by(parent_addr=parent_addr).\
+ all()
+
+
+@require_context
+@pick_context_manager_reader
+def pci_device_get_all_by_instance_uuid(context, instance_uuid):
+ return model_query(context, models.PciDevice).\
+ filter_by(status='allocated').\
+ filter_by(instance_uuid=instance_uuid).\
+ all()
+
+
+@pick_context_manager_reader
+def _instance_pcidevs_get_multi(context, instance_uuids):
+ if not instance_uuids:
+ return []
+ return model_query(context, models.PciDevice).\
+ filter_by(status='allocated').\
+ filter(models.PciDevice.instance_uuid.in_(instance_uuids))
+
+
+@pick_context_manager_writer
+def pci_device_destroy(context, node_id, address):
+ result = model_query(context, models.PciDevice).\
+ filter_by(compute_node_id=node_id).\
+ filter_by(address=address).\
+ soft_delete()
+ if not result:
+ raise exception.PciDeviceNotFound(node_id=node_id, address=address)
+
+
+@pick_context_manager_writer
+def pci_device_update(context, node_id, address, values):
+ query = model_query(context, models.PciDevice, read_deleted="no").\
+ filter_by(compute_node_id=node_id).\
+ filter_by(address=address)
+ if query.update(values) == 0:
+ device = models.PciDevice()
+ device.update(values)
+ context.session.add(device)
+ return query.one()
+
+
+####################
+
+
+@pick_context_manager_writer
+def instance_tag_add(context, instance_uuid, tag):
+ tag_ref = models.Tag()
+ tag_ref.resource_id = instance_uuid
+ tag_ref.tag = tag
+
+ try:
+ _check_instance_exists_in_project(context, instance_uuid)
+ with get_context_manager(context).writer.savepoint.using(context):
+ context.session.add(tag_ref)
+ except db_exc.DBDuplicateEntry:
+ # NOTE(snikitin): We should ignore tags duplicates
+ pass
+
+ return tag_ref
+
+
+@pick_context_manager_writer
+def instance_tag_set(context, instance_uuid, tags):
+ _check_instance_exists_in_project(context, instance_uuid)
+
+ existing = context.session.query(models.Tag.tag).filter_by(
+ resource_id=instance_uuid).all()
+
+ existing = set(row.tag for row in existing)
+ tags = set(tags)
+ to_delete = existing - tags
+ to_add = tags - existing
+
+ if to_delete:
+ context.session.query(models.Tag).filter_by(
+ resource_id=instance_uuid).filter(
+ models.Tag.tag.in_(to_delete)).delete(
+ synchronize_session=False)
+
+ if to_add:
+ data = [
+ {'resource_id': instance_uuid, 'tag': tag} for tag in to_add]
+ context.session.execute(models.Tag.__table__.insert(), data)
+
+ return context.session.query(models.Tag).filter_by(
+ resource_id=instance_uuid).all()
+
+
+@pick_context_manager_reader
+def instance_tag_get_by_instance_uuid(context, instance_uuid):
+ _check_instance_exists_in_project(context, instance_uuid)
+ return context.session.query(models.Tag).filter_by(
+ resource_id=instance_uuid).all()
+
+
+@pick_context_manager_writer
+def instance_tag_delete(context, instance_uuid, tag):
+ _check_instance_exists_in_project(context, instance_uuid)
+ result = context.session.query(models.Tag).filter_by(
+ resource_id=instance_uuid, tag=tag).delete()
+
+ if not result:
+ raise exception.InstanceTagNotFound(instance_id=instance_uuid,
+ tag=tag)
+
+
+@pick_context_manager_writer
+def instance_tag_delete_all(context, instance_uuid):
+ _check_instance_exists_in_project(context, instance_uuid)
+ context.session.query(models.Tag).filter_by(
+ resource_id=instance_uuid).delete()
+
+
+@pick_context_manager_reader
+def instance_tag_exists(context, instance_uuid, tag):
+ _check_instance_exists_in_project(context, instance_uuid)
+ q = context.session.query(models.Tag).filter_by(
+ resource_id=instance_uuid, tag=tag)
+ return context.session.query(q.exists()).scalar()
+
+
+####################
+
+
+@pick_context_manager_writer
+def console_auth_token_create(context, values):
+ instance_uuid = values.get('instance_uuid')
+ _check_instance_exists_in_project(context, instance_uuid)
+ token_ref = models.ConsoleAuthToken()
+ token_ref.update(values)
+ context.session.add(token_ref)
+ return token_ref
+
+
+@pick_context_manager_reader
+def console_auth_token_get_valid(context, token_hash, instance_uuid=None):
+ if instance_uuid is not None:
+ _check_instance_exists_in_project(context, instance_uuid)
+ query = context.session.query(models.ConsoleAuthToken).\
+ filter_by(token_hash=token_hash)
+ if instance_uuid is not None:
+ query = query.filter_by(instance_uuid=instance_uuid)
+ return query.filter(
+ models.ConsoleAuthToken.expires > timeutils.utcnow_ts()).first()
+
+
+@pick_context_manager_writer
+def console_auth_token_destroy_all_by_instance(context, instance_uuid):
+ context.session.query(models.ConsoleAuthToken).\
+ filter_by(instance_uuid=instance_uuid).delete()
+
+
+@pick_context_manager_writer
+def console_auth_token_destroy_expired_by_host(context, host):
+ context.session.query(models.ConsoleAuthToken).\
+ filter_by(host=host).\
+ filter(models.ConsoleAuthToken.expires <= timeutils.utcnow_ts()).\
+ delete()
diff --git a/gosbs/db/sqlalchemy/models.py b/gosbs/db/sqlalchemy/models.py
new file mode 100644
index 0000000..e4fc530
--- /dev/null
+++ b/gosbs/db/sqlalchemy/models.py
@@ -0,0 +1,430 @@
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Piston Cloud Computing, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/db/sqlalchemy/models.py
+# Only small part is left from the origin
+
+"""
+SQLAlchemy models for gosbs data.
+"""
+
+import uuid
+
+from oslo_config import cfg
+from oslo_db.sqlalchemy import models
+from oslo_utils import timeutils
+from sqlalchemy import (Column, Index, Integer, BigInteger, Enum, String,
+ schema, Unicode)
+from sqlalchemy.dialects.mysql import MEDIUMTEXT
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy import orm
+from sqlalchemy import ForeignKey, DateTime, Boolean, Text, Float, Time
+
+from gosbs.db.sqlalchemy import types
+
+CONF = cfg.CONF
+BASE = declarative_base()
+
+
+def MediumText():
+ return Text().with_variant(MEDIUMTEXT(), 'mysql')
+
+
+class NovaBase(models.ModelBase):
+ metadata = None
+
+ def __copy__(self):
+ """Implement a safe copy.copy().
+
+ SQLAlchemy-mapped objects travel with an object
+ called an InstanceState, which is pegged to that object
+ specifically and tracks everything about that object. It's
+ critical within all attribute operations, including gets
+ and deferred loading. This object definitely cannot be
+ shared among two instances, and must be handled.
+
+ The copy routine here makes use of session.merge() which
+ already essentially implements a "copy" style of operation,
+ which produces a new instance with a new InstanceState and copies
+ all the data along mapped attributes without using any SQL.
+
+ The mode we are using here has the caveat that the given object
+ must be "clean", e.g. that it has no database-loaded state
+ that has been updated and not flushed. This is a good thing,
+ as creating a copy of an object including non-flushed, pending
+ database state is probably not a good idea; neither represents
+ what the actual row looks like, and only one should be flushed.
+
+ """
+ session = orm.Session()
+
+ copy = session.merge(self, load=False)
+ session.expunge(copy)
+ return copy
+
+
+class Service(BASE, NovaBase, models.TimestampMixin, models.SoftDeleteMixin):
+ """Represents a running service on a host."""
+
+ __tablename__ = 'services'
+ __table_args__ = (
+ schema.UniqueConstraint("host", "topic", "deleted",
+ name="uniq_services0host0topic0deleted"),
+ schema.UniqueConstraint("host", "binary", "deleted",
+ name="uniq_services0host0binary0deleted"),
+ Index('services_uuid_idx', 'uuid', unique=True),
+ )
+
+ id = Column(Integer, primary_key=True)
+ uuid = Column(String(36), nullable=True)
+ host = Column(String(255))
+ binary = Column(String(255))
+ topic = Column(String(255))
+ report_count = Column(Integer, nullable=False, default=0)
+ disabled = Column(Boolean, default=False)
+ disabled_reason = Column(String(255))
+ last_seen_up = Column(DateTime, nullable=True)
+ forced_down = Column(Boolean(), default=False)
+ version = Column(Integer, default=0)
+
+
+class Tasks(BASE, NovaBase, models.TimestampMixin, models.SoftDeleteMixin):
+ """Represents an image in the datastore."""
+ __tablename__ = 'tasks'
+ __table_args__ = (
+ )
+ uuid = Column(String(36), primary_key=True,
+ default=lambda: str(uuid.uuid4()))
+ name = Column(String(255))
+ service_uuid = Column(String(36), nullable=True)
+ repet = Column(Boolean(), default=False)
+ run = Column(DateTime)
+ status = Column(Enum('failed', 'completed', 'in-progress', 'waiting'),
+ nullable=True)
+ last = Column(DateTime, default=timeutils.now)
+ priority = Column(Integer, default=5)
+
+class Projects(BASE, NovaBase, models.TimestampMixin, models.SoftDeleteMixin):
+ """Represents an image in the datastore."""
+ __tablename__ = 'projects'
+ __table_args__ = (
+ )
+ uuid = Column(String(36), primary_key=True,
+ default=lambda: str(uuid.uuid4()))
+ name = Column(String(255))
+ active = Column(Boolean(), default=False)
+ auto = Column(Boolean(), default=False)
+
+class ProjectsMetadata(BASE, NovaBase):
+ """Represents an image in the datastore."""
+ __tablename__ = 'projects_metadata'
+ __table_args__ = (
+ )
+ id = Column(Integer, primary_key=True)
+ project_uuid = Column(String(36), ForeignKey('projects.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ project_repo_uuid = Column(String(36), ForeignKey('repos.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ project_profile = Column(String(255))
+ project_profile_repo_uuid = Column(String(36), ForeignKey('repos.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ titel = Column(String(255))
+ description = Column(Text)
+
+class ProjectsRepos(BASE, NovaBase):
+ """Represents an image in the datastore."""
+ __tablename__ = 'projects_repos'
+ __table_args__ = (
+ )
+ id = Column(Integer, primary_key=True)
+ project_uuid = Column(String(36), ForeignKey('projects.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ repo_uuid = Column(String(36), ForeignKey('repos.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ build = Column(Boolean(), default=False)
+ test = Column(Boolean(), default=False)
+ repoman = Column(Boolean(), default=False)
+ qa = Column(Boolean(), default=False)
+ auto = Column(Boolean(), default=False)
+ depclean = Column(Boolean(), default=False)
+
+class ProjectsBuilds(BASE, NovaBase, models.TimestampMixin, models.SoftDeleteMixin):
+ """Represents an image in the datastore."""
+ __tablename__ = 'projects_builds'
+ __table_args__ = (
+ )
+ uuid = Column(String(36), primary_key=True,
+ default=lambda: str(uuid.uuid4()))
+ project_uuid = Column(String(36), ForeignKey('projects.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ ebuild_uuid = Column(String(36), ForeignKey('ebuilds.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ user_id = Column(Integer, ForeignKey('users.id'),)
+ status = Column(Enum('failed', 'completed', 'in-progress', 'waiting'),
+ nullable=True)
+ priority = Column(Integer, default=5)
+
+
+class BuildsIUses(BASE, NovaBase):
+ __tablename__ = 'builds_uses'
+ __table_args__ = (
+ )
+ id = Column(Integer, primary_key=True)
+ build_uuid = Column(String(36), ForeignKey('projects_builds.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ use_id = Column(Integer, ForeignKey('users.user_id'),)
+ status = Column(Boolean, default=False)
+
+
+class Users(BASE, NovaBase):
+ __tablename__ = 'users'
+ __table_args__ = (
+ )
+ id = Column(Integer, primary_key=True)
+ user_id = Column(Integer)
+ name = Column(String(255))
+
+
+class Repos(BASE, NovaBase, models.TimestampMixin, models.SoftDeleteMixin):
+ """Represents an image in the datastore."""
+ __tablename__ = 'repos'
+ __table_args__ = (
+ )
+
+ uuid = Column(String(36), primary_key=True,
+ default=lambda: str(uuid.uuid4()))
+ name = Column(String(255))
+ status = Column(Enum('failed', 'completed', 'in-progress', 'waiting'),
+ nullable=True)
+ description = Column(Text)
+ src_url = Column(String(255))
+ auto = Column(Boolean(), default=False)
+ repo_type = Column(Enum('project', 'ebuild'), nullable=True)
+
+
+class Categories(BASE, NovaBase, models.TimestampMixin, models.SoftDeleteMixin):
+ """Represents an image in the datastore."""
+ __tablename__ = 'categories'
+ __table_args__ = (
+ )
+
+ uuid = Column(String(36), primary_key=True,
+ default=lambda: str(uuid.uuid4()))
+ name = Column(String(255))
+ status = Column(Enum('failed', 'completed', 'in-progress', 'waiting'),
+ nullable=True)
+
+class CategoriesMetadata(BASE, NovaBase):
+ """Represents an image in the datastore."""
+ __tablename__ = 'categories_metadata'
+ __table_args__ = (
+ )
+ id = Column(Integer, primary_key=True)
+ category_uuid = Column(String(36), ForeignKey('categories.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ checksum = Column(String(255))
+ description = Column(Text)
+
+class Packages(BASE, NovaBase, models.TimestampMixin, models.SoftDeleteMixin):
+ """Represents an image in the datastore."""
+ __tablename__ = 'packages'
+ __table_args__ = (
+ )
+
+ uuid = Column(String(36), primary_key=True,
+ default=lambda: str(uuid.uuid4()))
+ name = Column(String(255))
+ status = Column(Enum('failed', 'completed', 'in-progress', 'waiting'),
+ nullable=True)
+ category_uuid = Column(String(36), ForeignKey('categories.uuid'), nullable=False,
+ default=lambda: str(uuid.uuid4()))
+ repo_uuid = Column(String(36), ForeignKey('repos.uuid'), nullable=False,
+ default=lambda: str(uuid.uuid4()))
+
+class PackagesMetadata(BASE, NovaBase):
+ """Represents an image in the datastore."""
+ __tablename__ = 'packages_metadata'
+ __table_args__ = (
+ )
+ id = Column(Integer, primary_key=True)
+ package_uuid = Column(String(36), ForeignKey('packages.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ description = Column(Text)
+ gitlog = Column(Text)
+ checksum = Column(String(200))
+
+class PackagesEmails(BASE, NovaBase):
+ """Represents an image in the datastore."""
+ __tablename__ = 'packages_emails'
+ __table_args__ = (
+ )
+ id = Column(Integer, primary_key=True)
+ package_uuid = Column(String(36), ForeignKey('packages.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ email_id = Column(Integer, ForeignKey('emails.id'))
+
+class Emails(BASE, NovaBase, models.TimestampMixin, models.SoftDeleteMixin):
+ """Represents an image in the datastore."""
+ __tablename__ = 'emails'
+ __table_args__ = (
+ )
+ id = Column(Integer, primary_key=True)
+ email = Column(String(255))
+
+class Uses(BASE, NovaBase, models.TimestampMixin, models.SoftDeleteMixin):
+ """Represents an image in the datastore."""
+ __tablename__ = 'uses'
+ __table_args__ = (
+ )
+
+ id = Column(Integer, primary_key=True)
+ flag = Column(String(255))
+ description = Column(Text)
+
+class Restrictions(BASE, NovaBase, models.TimestampMixin, models.SoftDeleteMixin):
+ """Represents an image in the datastore."""
+ __tablename__ = 'restrictions'
+ __table_args__ = (
+ )
+
+ id = Column(Integer, primary_key=True)
+ restriction = Column(String(255))
+
+class Keywords(BASE, NovaBase, models.TimestampMixin, models.SoftDeleteMixin):
+ """Represents an image in the datastore."""
+ __tablename__ = 'keywords'
+ __table_args__ = (
+ )
+
+ id = Column(Integer, primary_key=True)
+ keyword = Column(String(255))
+
+class Ebuilds(BASE, NovaBase, models.TimestampMixin, models.SoftDeleteMixin):
+ __tablename__ = 'ebuilds'
+ __table_args__ = (
+ )
+ uuid = Column(String(36), primary_key=True,
+ default=lambda: str(uuid.uuid4()))
+ package_uuid = Column(String(36), ForeignKey('packages.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ version = Column(String(100))
+ checksum = Column(String(200))
+
+class EbuildsMetadata(BASE, NovaBase):
+ __tablename__ = 'ebuilds_metadata'
+ __table_args__ = (
+ )
+ id = Column(Integer, primary_key=True)
+ ebuild_uuid = Column(String(36), ForeignKey('ebuilds.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ commit = Column(String(100))
+ commit_msg = Column(Text)
+ description = Column(Text)
+ slot = Column(String(10))
+ homepage = Column(String(200))
+ license = Column(String(200))
+
+class EbuildsRestrictions(BASE, NovaBase):
+ __tablename__ = 'ebuilds_restrictions'
+ __table_args__ = (
+ )
+ id = Column(Integer, primary_key=True)
+ ebuild_uuid = Column(String(36), ForeignKey('ebuilds.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ restriction_id = Column(Integer, ForeignKey('restrictions.id'),)
+
+class EbuildsIUses(BASE, NovaBase):
+ __tablename__ = 'ebuilds_uses'
+ __table_args__ = (
+ )
+ id = Column(Integer, primary_key=True)
+ ebuild_uuid = Column(String(36), ForeignKey('ebuilds.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ use_id = Column(Integer, ForeignKey('uses.id'),)
+ status = Column(Boolean, default=False)
+
+class EbuildsKeywords(BASE, NovaBase):
+ __tablename__ = 'ebuilds_keywords'
+ __table_args__ = (
+ )
+ id = Column(Integer, primary_key=True)
+ ebuild_uuid = Column(String(36), ForeignKey('ebuilds.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ keyword_id = Column(Integer, ForeignKey('keywords.id'),)
+ status = Column(Enum('stable','unstable','negative'))
+
+
+class Images(BASE, NovaBase):
+ """Represents an image in the datastore."""
+ __tablename__ = 'images'
+ __table_args__ = (
+ )
+
+ uuid = Column(String(36), primary_key=True,
+ default=lambda: str(uuid.uuid4()))
+ name = Column(String(255))
+ size = Column(BigInteger().with_variant(Integer, "sqlite"))
+ status = Column(Integer, nullable=False)
+ min_disk = Column(Integer, nullable=False, default=0)
+ min_ram = Column(Integer, nullable=False, default=0)
+
+class Flavors(BASE, NovaBase):
+ """Represents possible flavors for instances"""
+ __tablename__ = 'flavors'
+ __table_args__ = (
+ )
+
+ id = Column(Integer, primary_key=True)
+ name = Column(String(255), nullable=False)
+ ram = Column(Integer, nullable=False)
+ vcpus = Column(Integer, nullable=False)
+ disk = Column(Integer)
+ swap = Column(Integer, nullable=False, default=0)
+ description = Column(Text)
+
+class Instances(BASE, NovaBase, models.TimestampMixin, models.SoftDeleteMixin):
+ """Represents a guest VM."""
+ __tablename__ = 'instances'
+ __table_args__ = (
+ )
+
+ uuid = Column(String(36), primary_key=True,
+ default=lambda: str(uuid.uuid4()))
+ image_uuid = Column(String(36), ForeignKey('images.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ flavor_uuid = Column(String(36), ForeignKey('flavors.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ project_uuid = Column(String(36), ForeignKey('projects.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ status = Column(Enum('failed', 'completed', 'in-progress', 'waiting'),
+ nullable=True)
+
+class ServicesRepos(BASE, NovaBase, models.TimestampMixin, models.SoftDeleteMixin):
+ """Represents a guest VM."""
+ __tablename__ = 'services_repos'
+ __table_args__ = (
+ )
+
+ id = Column(Integer, primary_key=True)
+ service_uuid = Column(String(36), ForeignKey('services.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ repo_uuid = Column(String(36), ForeignKey('repos.uuid'),
+ default=lambda: str(uuid.uuid4()))
+ auto = Column(Boolean(), default=False)
+ status = Column(Enum('failed', 'completed', 'in-progress', 'waiting', 'update_db', 'rebuild_db'),
+ nullable=True)
diff --git a/gosbs/db/sqlalchemy/types.py b/gosbs/db/sqlalchemy/types.py
new file mode 100644
index 0000000..d1431c3
--- /dev/null
+++ b/gosbs/db/sqlalchemy/types.py
@@ -0,0 +1,74 @@
+# Copyright 2011 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/db/sqlalchemy/types.py
+
+"""Custom SQLAlchemy types."""
+
+import netaddr
+from oslo_utils import netutils
+from sqlalchemy.dialects import postgresql
+from sqlalchemy import types
+
+from gosbs import utils
+
+
+class IPAddress(types.TypeDecorator):
+ """An SQLAlchemy type representing an IP-address."""
+
+ impl = types.String
+
+ def load_dialect_impl(self, dialect):
+ if dialect.name == 'postgresql':
+ return dialect.type_descriptor(postgresql.INET())
+ else:
+ return dialect.type_descriptor(types.String(39))
+
+ def process_bind_param(self, value, dialect):
+ """Process/Formats the value before insert it into the db."""
+ if dialect.name == 'postgresql':
+ return value
+ # NOTE(maurosr): The purpose here is to convert ipv6 to the shortened
+ # form, not validate it.
+ elif netutils.is_valid_ipv6(value):
+ return utils.get_shortened_ipv6(value)
+ return value
+
+
+class CIDR(types.TypeDecorator):
+ """An SQLAlchemy type representing a CIDR definition."""
+
+ impl = types.String
+
+ def load_dialect_impl(self, dialect):
+ if dialect.name == 'postgresql':
+ return dialect.type_descriptor(postgresql.INET())
+ else:
+ return dialect.type_descriptor(types.String(43))
+
+ def process_bind_param(self, value, dialect):
+ """Process/Formats the value before insert it into the db."""
+ # NOTE(sdague): normalize all the inserts
+ if netutils.is_valid_ipv6_cidr(value):
+ return utils.get_shortened_ipv6_cidr(value)
+ return value
+
+ def process_result_value(self, value, dialect):
+ try:
+ return str(netaddr.IPNetwork(value, version=4).cidr)
+ except netaddr.AddrFormatError:
+ return str(netaddr.IPNetwork(value, version=6).cidr)
+ except TypeError:
+ return None
diff --git a/gosbs/db/sqlalchemy/utils.py b/gosbs/db/sqlalchemy/utils.py
new file mode 100644
index 0000000..e4236d8
--- /dev/null
+++ b/gosbs/db/sqlalchemy/utils.py
@@ -0,0 +1,118 @@
+# Copyright (c) 2013 Boris Pavlovic (boris@pavlovic.me).
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/db/sqlalchemy/utils.py
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as oslodbutils
+from oslo_log import log as logging
+from sqlalchemy.exc import OperationalError
+from sqlalchemy import MetaData
+from sqlalchemy import Table
+from sqlalchemy.types import NullType
+
+from gosbs.db.sqlalchemy import api as db
+from gosbs import exception
+from gosbs.i18n import _
+
+
+LOG = logging.getLogger(__name__)
+
+
+def check_shadow_table(migrate_engine, table_name):
+ """This method checks that table with ``table_name`` and
+ corresponding shadow table have same columns.
+ """
+ meta = MetaData()
+ meta.bind = migrate_engine
+
+ table = Table(table_name, meta, autoload=True)
+ shadow_table = Table(db._SHADOW_TABLE_PREFIX + table_name, meta,
+ autoload=True)
+
+ columns = {c.name: c for c in table.columns}
+ shadow_columns = {c.name: c for c in shadow_table.columns}
+
+ for name, column in columns.items():
+ if name not in shadow_columns:
+ raise exception.NovaException(
+ _("Missing column %(table)s.%(column)s in shadow table")
+ % {'column': name, 'table': shadow_table.name})
+ shadow_column = shadow_columns[name]
+
+ if not isinstance(shadow_column.type, type(column.type)):
+ raise exception.NovaException(
+ _("Different types in %(table)s.%(column)s and shadow table: "
+ "%(c_type)s %(shadow_c_type)s")
+ % {'column': name, 'table': table.name,
+ 'c_type': column.type,
+ 'shadow_c_type': shadow_column.type})
+
+ for name, column in shadow_columns.items():
+ if name not in columns:
+ raise exception.NovaException(
+ _("Extra column %(table)s.%(column)s in shadow table")
+ % {'column': name, 'table': shadow_table.name})
+ return True
+
+
+def create_shadow_table(migrate_engine, table_name=None, table=None,
+ **col_name_col_instance):
+ """This method create shadow table for table with name ``table_name``
+ or table instance ``table``.
+ :param table_name: Autoload table with this name and create shadow table
+ :param table: Autoloaded table, so just create corresponding shadow table.
+ :param col_name_col_instance: contains pair column_name=column_instance.
+ column_instance is instance of Column. These params are required only for
+ columns that have unsupported types by sqlite. For example BigInteger.
+ :returns: The created shadow_table object.
+ """
+ meta = MetaData(bind=migrate_engine)
+
+ if table_name is None and table is None:
+ raise exception.NovaException(_("Specify `table_name` or `table` "
+ "param"))
+ if not (table_name is None or table is None):
+ raise exception.NovaException(_("Specify only one param `table_name` "
+ "`table`"))
+
+ if table is None:
+ table = Table(table_name, meta, autoload=True)
+
+ columns = []
+ for column in table.columns:
+ if isinstance(column.type, NullType):
+ new_column = oslodbutils._get_not_supported_column(
+ col_name_col_instance, column.name)
+ columns.append(new_column)
+ else:
+ columns.append(column.copy())
+
+ shadow_table_name = db._SHADOW_TABLE_PREFIX + table.name
+ shadow_table = Table(shadow_table_name, meta, *columns,
+ mysql_engine='InnoDB')
+ try:
+ shadow_table.create()
+ return shadow_table
+ except (db_exc.DBError, OperationalError):
+ # NOTE(ekudryashova): At the moment there is a case in oslo.db code,
+ # which raises unwrapped OperationalError, so we should catch it until
+ # oslo.db would wraps all such exceptions
+ LOG.info(repr(shadow_table))
+ LOG.exception('Exception while creating table.')
+ raise exception.ShadowTableExists(name=shadow_table_name)
+ except Exception:
+ LOG.info(repr(shadow_table))
+ LOG.exception('Exception while creating table.')
diff --git a/gosbs/debugger.py b/gosbs/debugger.py
new file mode 100644
index 0000000..0cda17c
--- /dev/null
+++ b/gosbs/debugger.py
@@ -0,0 +1,62 @@
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Justin Santa Barbara
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/debugger.py
+
+# NOTE(markmc): this is imported before monkey patching in nova.cmd
+# so we avoid extra imports here
+
+import sys
+
+
+def enabled():
+ return ('--remote_debug-host' in sys.argv and
+ '--remote_debug-port' in sys.argv)
+
+
+def init():
+ import gosbs.conf
+ CONF = gosbs.conf.CONF
+
+ # NOTE(markmc): gracefully handle the CLI options not being registered
+ if 'remote_debug' not in CONF:
+ return
+
+ if not (CONF.remote_debug.host and CONF.remote_debug.port):
+ return
+
+ from gobs.i18n import _LW
+ from oslo_log import log as logging
+ LOG = logging.getLogger(__name__)
+
+ LOG.debug('Listening on %(host)s:%(port)s for debug connection',
+ {'host': CONF.remote_debug.host,
+ 'port': CONF.remote_debug.port})
+
+ try:
+ from pydev import pydevd
+ except ImportError:
+ import pydevd
+ pydevd.settrace(host=CONF.remote_debug.host,
+ port=CONF.remote_debug.port,
+ stdoutToServer=False,
+ stderrToServer=False)
+
+ LOG.warning(_LW('WARNING: Using the remote debug option changes how '
+ 'Nova uses the eventlet library to support async IO. This '
+ 'could result in failures that do not occur under normal '
+ 'operation. Use at your own risk.'))
diff --git a/gosbs/exception.py b/gosbs/exception.py
new file mode 100644
index 0000000..4759034
--- /dev/null
+++ b/gosbs/exception.py
@@ -0,0 +1,2394 @@
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/exception.py
+
+"""Nova base exception handling.
+
+Includes decorator for re-raising Nova-type exceptions.
+
+SHOULD include dedicated exception logging.
+
+"""
+
+from oslo_log import log as logging
+
+from gosbs.i18n import _, _LE
+
+LOG = logging.getLogger(__name__)
+
+class NovaException(Exception):
+ """Base Nova Exception
+
+ To correctly use this class, inherit from it and define
+ a 'msg_fmt' property. That msg_fmt will get printf'd
+ with the keyword arguments provided to the constructor.
+
+ """
+ msg_fmt = _("An unknown exception occurred.")
+ code = 500
+ headers = {}
+ safe = False
+
+ def __init__(self, message=None, **kwargs):
+ self.kwargs = kwargs
+
+ if 'code' not in self.kwargs:
+ try:
+ self.kwargs['code'] = self.code
+ except AttributeError:
+ pass
+
+ if not message:
+ try:
+ message = self.msg_fmt % kwargs
+
+ except Exception:
+ # NOTE(melwitt): This is done in a separate method so it can be
+ # monkey-patched during testing to make it a hard failure.
+ self._log_exception()
+ message = self.msg_fmt
+
+ self.message = message
+ super(NovaException, self).__init__(message)
+
+ def _log_exception(self):
+ # kwargs doesn't match a variable in the message
+ # log the issue and the kwargs
+ LOG.exception(_LE('Exception in string format operation'))
+ for name, value in self.kwargs.items():
+ LOG.error("%s: %s" % (name, value)) # noqa
+
+ def format_message(self):
+ # NOTE(mrodden): use the first argument to the python Exception object
+ # which should be our full NovaException message, (see __init__)
+ return self.args[0]
+
+ def __repr__(self):
+ dict_repr = self.__dict__
+ dict_repr['class'] = self.__class__.__name__
+ return str(dict_repr)
+
+
+class EncryptionFailure(NovaException):
+ msg_fmt = _("Failed to encrypt text: %(reason)s")
+
+
+class DecryptionFailure(NovaException):
+ msg_fmt = _("Failed to decrypt text: %(reason)s")
+
+
+class RevokeCertFailure(NovaException):
+ msg_fmt = _("Failed to revoke certificate for %(project_id)s")
+
+
+class VirtualInterfaceCreateException(NovaException):
+ msg_fmt = _("Virtual Interface creation failed")
+
+
+class VirtualInterfaceMacAddressException(NovaException):
+ msg_fmt = _("Creation of virtual interface with "
+ "unique mac address failed")
+
+
+class VirtualInterfacePlugException(NovaException):
+ msg_fmt = _("Virtual interface plugin failed")
+
+
+class VirtualInterfaceUnplugException(NovaException):
+ msg_fmt = _("Failed to unplug virtual interface: %(reason)s")
+
+
+class GlanceConnectionFailed(NovaException):
+ msg_fmt = _("Connection to glance host %(server)s failed: "
+ "%(reason)s")
+
+
+class CinderConnectionFailed(NovaException):
+ msg_fmt = _("Connection to cinder host failed: %(reason)s")
+
+
+class UnsupportedCinderAPIVersion(NovaException):
+ msg_fmt = _('Nova does not support Cinder API version %(version)s')
+
+
+class CinderAPIVersionNotAvailable(NovaException):
+ """Used to indicate that a requested Cinder API version, generally a
+ microversion, is not available.
+ """
+ msg_fmt = _('Cinder API version %(version)s is not available.')
+
+
+class Forbidden(NovaException):
+ msg_fmt = _("Forbidden")
+ code = 403
+
+
+class AdminRequired(Forbidden):
+ msg_fmt = _("User does not have admin privileges")
+
+
+class PolicyNotAuthorized(Forbidden):
+ msg_fmt = _("Policy doesn't allow %(action)s to be performed.")
+
+
+class ImageNotActive(NovaException):
+ # NOTE(jruzicka): IncorrectState is used for volumes only in EC2,
+ # but it still seems like the most appropriate option.
+ msg_fmt = _("Image %(image_id)s is not active.")
+
+
+class ImageNotAuthorized(NovaException):
+ msg_fmt = _("Not authorized for image %(image_id)s.")
+
+
+class Invalid(NovaException):
+ msg_fmt = _("Bad Request - Invalid Parameters")
+ code = 400
+
+
+class InvalidBDM(Invalid):
+ msg_fmt = _("Block Device Mapping is Invalid.")
+
+
+class InvalidBDMSnapshot(InvalidBDM):
+ msg_fmt = _("Block Device Mapping is Invalid: "
+ "failed to get snapshot %(id)s.")
+
+
+class InvalidBDMVolume(InvalidBDM):
+ msg_fmt = _("Block Device Mapping is Invalid: "
+ "failed to get volume %(id)s.")
+
+
+class InvalidBDMImage(InvalidBDM):
+ msg_fmt = _("Block Device Mapping is Invalid: "
+ "failed to get image %(id)s.")
+
+
+class InvalidBDMBootSequence(InvalidBDM):
+ msg_fmt = _("Block Device Mapping is Invalid: "
+ "Boot sequence for the instance "
+ "and image/block device mapping "
+ "combination is not valid.")
+
+
+class InvalidBDMLocalsLimit(InvalidBDM):
+ msg_fmt = _("Block Device Mapping is Invalid: "
+ "You specified more local devices than the "
+ "limit allows")
+
+
+class InvalidBDMEphemeralSize(InvalidBDM):
+ msg_fmt = _("Ephemeral disks requested are larger than "
+ "the instance type allows. If no size is given "
+ "in one block device mapping, flavor ephemeral "
+ "size will be used.")
+
+
+class InvalidBDMSwapSize(InvalidBDM):
+ msg_fmt = _("Swap drive requested is larger than instance type allows.")
+
+
+class InvalidBDMFormat(InvalidBDM):
+ msg_fmt = _("Block Device Mapping is Invalid: "
+ "%(details)s")
+
+
+class InvalidBDMForLegacy(InvalidBDM):
+ msg_fmt = _("Block Device Mapping cannot "
+ "be converted to legacy format. ")
+
+
+class InvalidBDMVolumeNotBootable(InvalidBDM):
+ msg_fmt = _("Block Device %(id)s is not bootable.")
+
+
+class InvalidAttribute(Invalid):
+ msg_fmt = _("Attribute not supported: %(attr)s")
+
+
+class ValidationError(Invalid):
+ msg_fmt = "%(detail)s"
+
+
+class VolumeAttachFailed(Invalid):
+ msg_fmt = _("Volume %(volume_id)s could not be attached. "
+ "Reason: %(reason)s")
+
+
+class VolumeDetachFailed(Invalid):
+ msg_fmt = _("Volume %(volume_id)s could not be detached. "
+ "Reason: %(reason)s")
+
+
+class MultiattachNotSupportedByVirtDriver(NovaException):
+ # This exception indicates the compute hosting the instance does not
+ # support multiattach volumes. This should generally be considered a
+ # 409 HTTPConflict error in the API since we expect all virt drivers to
+ # eventually support multiattach volumes.
+ msg_fmt = _("Volume %(volume_id)s has 'multiattach' set, "
+ "which is not supported for this instance.")
+ code = 409
+
+
+class MultiattachSupportNotYetAvailable(NovaException):
+ # This exception indicates the deployment is not yet new enough to support
+ # multiattach volumes, so a 409 HTTPConflict response is generally used
+ # for handling this in the API.
+ msg_fmt = _("Multiattach volume support is not yet available.")
+ code = 409
+
+
+class MultiattachNotSupportedOldMicroversion(Invalid):
+ msg_fmt = _('Multiattach volumes are only supported starting with '
+ 'compute API version 2.60.')
+
+
+class VolumeTypeSupportNotYetAvailable(NovaException):
+ # This exception indicates the deployment is not yet new enough to support
+ # volume type, so a 409 HTTPConflict response is generally used
+ # for handling this in the API.
+ msg_fmt = _("Volume type support is not yet available.")
+ code = 409
+
+
+class MultiattachToShelvedNotSupported(Invalid):
+ msg_fmt = _("Attaching multiattach volumes is not supported for "
+ "shelved-offloaded instances.")
+
+
+class VolumeNotCreated(NovaException):
+ msg_fmt = _("Volume %(volume_id)s did not finish being created"
+ " even after we waited %(seconds)s seconds or %(attempts)s"
+ " attempts. And its status is %(volume_status)s.")
+
+
+class ExtendVolumeNotSupported(Invalid):
+ msg_fmt = _("Volume size extension is not supported by the hypervisor.")
+
+
+class VolumeEncryptionNotSupported(Invalid):
+ msg_fmt = _("Volume encryption is not supported for %(volume_type)s "
+ "volume %(volume_id)s")
+
+
+class VolumeTaggedAttachNotSupported(Invalid):
+ msg_fmt = _("Tagged volume attachment is not supported for this server "
+ "instance.")
+
+
+class VolumeTaggedAttachToShelvedNotSupported(VolumeTaggedAttachNotSupported):
+ msg_fmt = _("Tagged volume attachment is not supported for "
+ "shelved-offloaded instances.")
+
+
+class NetworkInterfaceTaggedAttachNotSupported(Invalid):
+ msg_fmt = _("Tagged network interface attachment is not supported for "
+ "this server instance.")
+
+
+class InvalidKeypair(Invalid):
+ msg_fmt = _("Keypair data is invalid: %(reason)s")
+
+
+class InvalidRequest(Invalid):
+ msg_fmt = _("The request is invalid.")
+
+
+class InvalidInput(Invalid):
+ msg_fmt = _("Invalid input received: %(reason)s")
+
+
+class InvalidVolume(Invalid):
+ msg_fmt = _("Invalid volume: %(reason)s")
+
+
+class InvalidVolumeAccessMode(Invalid):
+ msg_fmt = _("Invalid volume access mode: %(access_mode)s")
+
+
+class InvalidMetadata(Invalid):
+ msg_fmt = _("Invalid metadata: %(reason)s")
+
+
+class InvalidMetadataSize(Invalid):
+ msg_fmt = _("Invalid metadata size: %(reason)s")
+
+
+class InvalidPortRange(Invalid):
+ msg_fmt = _("Invalid port range %(from_port)s:%(to_port)s. %(msg)s")
+
+
+class InvalidIpProtocol(Invalid):
+ msg_fmt = _("Invalid IP protocol %(protocol)s.")
+
+
+class InvalidContentType(Invalid):
+ msg_fmt = _("Invalid content type %(content_type)s.")
+
+
+class InvalidAPIVersionString(Invalid):
+ msg_fmt = _("API Version String %(version)s is of invalid format. Must "
+ "be of format MajorNum.MinorNum.")
+
+
+class VersionNotFoundForAPIMethod(Invalid):
+ msg_fmt = _("API version %(version)s is not supported on this method.")
+
+
+class InvalidGlobalAPIVersion(Invalid):
+ msg_fmt = _("Version %(req_ver)s is not supported by the API. Minimum "
+ "is %(min_ver)s and maximum is %(max_ver)s.")
+
+
+class ApiVersionsIntersect(Invalid):
+ msg_fmt = _("Version of %(name)s %(min_ver)s %(max_ver)s intersects "
+ "with another versions.")
+
+
+# Cannot be templated as the error syntax varies.
+# msg needs to be constructed when raised.
+class InvalidParameterValue(Invalid):
+ msg_fmt = "%(err)s"
+
+
+class InvalidAggregateAction(Invalid):
+ msg_fmt = _("Unacceptable parameters.")
+ code = 400
+
+
+class InvalidAggregateActionAdd(InvalidAggregateAction):
+ msg_fmt = _("Cannot add host to aggregate "
+ "%(aggregate_id)s. Reason: %(reason)s.")
+
+
+class InvalidAggregateActionDelete(InvalidAggregateAction):
+ msg_fmt = _("Cannot remove host from aggregate "
+ "%(aggregate_id)s. Reason: %(reason)s.")
+
+
+class InvalidAggregateActionUpdate(InvalidAggregateAction):
+ msg_fmt = _("Cannot update aggregate "
+ "%(aggregate_id)s. Reason: %(reason)s.")
+
+
+class InvalidAggregateActionUpdateMeta(InvalidAggregateAction):
+ msg_fmt = _("Cannot update metadata of aggregate "
+ "%(aggregate_id)s. Reason: %(reason)s.")
+
+
+class InvalidSortKey(Invalid):
+ msg_fmt = _("Sort key supplied was not valid.")
+
+
+class InvalidStrTime(Invalid):
+ msg_fmt = _("Invalid datetime string: %(reason)s")
+
+
+class InvalidNUMANodesNumber(Invalid):
+ msg_fmt = _("The property 'numa_nodes' cannot be '%(nodes)s'. "
+ "It must be a number greater than 0")
+
+
+class InvalidName(Invalid):
+ msg_fmt = _("An invalid 'name' value was provided. "
+ "The name must be: %(reason)s")
+
+
+class InstanceInvalidState(Invalid):
+ msg_fmt = _("Instance %(instance_uuid)s in %(attr)s %(state)s. Cannot "
+ "%(method)s while the instance is in this state.")
+
+
+class InstanceNotRunning(Invalid):
+ msg_fmt = _("Instance %(instance_id)s is not running.")
+
+
+class InstanceNotInRescueMode(Invalid):
+ msg_fmt = _("Instance %(instance_id)s is not in rescue mode")
+
+
+class InstanceNotRescuable(Invalid):
+ msg_fmt = _("Instance %(instance_id)s cannot be rescued: %(reason)s")
+
+
+class InstanceNotReady(Invalid):
+ msg_fmt = _("Instance %(instance_id)s is not ready")
+
+
+class InstanceSuspendFailure(Invalid):
+ msg_fmt = _("Failed to suspend instance: %(reason)s")
+
+
+class InstanceResumeFailure(Invalid):
+ msg_fmt = _("Failed to resume instance: %(reason)s")
+
+
+class InstancePowerOnFailure(Invalid):
+ msg_fmt = _("Failed to power on instance: %(reason)s")
+
+
+class InstancePowerOffFailure(Invalid):
+ msg_fmt = _("Failed to power off instance: %(reason)s")
+
+
+class InstanceRebootFailure(Invalid):
+ msg_fmt = _("Failed to reboot instance: %(reason)s")
+
+
+class InstanceTerminationFailure(Invalid):
+ msg_fmt = _("Failed to terminate instance: %(reason)s")
+
+
+class InstanceDeployFailure(Invalid):
+ msg_fmt = _("Failed to deploy instance: %(reason)s")
+
+
+class MultiplePortsNotApplicable(Invalid):
+ msg_fmt = _("Failed to launch instances: %(reason)s")
+
+
+class InvalidFixedIpAndMaxCountRequest(Invalid):
+ msg_fmt = _("Failed to launch instances: %(reason)s")
+
+
+class ServiceUnavailable(Invalid):
+ msg_fmt = _("Service is unavailable at this time.")
+
+
+class ServiceNotUnique(Invalid):
+ msg_fmt = _("More than one possible service found.")
+
+
+class ComputeResourcesUnavailable(ServiceUnavailable):
+ msg_fmt = _("Insufficient compute resources: %(reason)s.")
+
+
+class HypervisorUnavailable(NovaException):
+ msg_fmt = _("Connection to the hypervisor is broken on host: %(host)s")
+
+
+class ComputeServiceUnavailable(ServiceUnavailable):
+ msg_fmt = _("Compute service of %(host)s is unavailable at this time.")
+
+
+class ComputeServiceInUse(NovaException):
+ msg_fmt = _("Compute service of %(host)s is still in use.")
+
+
+class UnableToMigrateToSelf(Invalid):
+ msg_fmt = _("Unable to migrate instance (%(instance_id)s) "
+ "to current host (%(host)s).")
+
+
+class InvalidHypervisorType(Invalid):
+ msg_fmt = _("The supplied hypervisor type of is invalid.")
+
+
+class HypervisorTooOld(Invalid):
+ msg_fmt = _("This compute node's hypervisor is older than the minimum "
+ "supported version: %(version)s.")
+
+
+class DestinationHypervisorTooOld(Invalid):
+ msg_fmt = _("The instance requires a newer hypervisor version than "
+ "has been provided.")
+
+
+class ServiceTooOld(Invalid):
+ msg_fmt = _("This service is older (v%(thisver)i) than the minimum "
+ "(v%(minver)i) version of the rest of the deployment. "
+ "Unable to continue.")
+
+
+class DestinationDiskExists(Invalid):
+ msg_fmt = _("The supplied disk path (%(path)s) already exists, "
+ "it is expected not to exist.")
+
+
+class InvalidDevicePath(Invalid):
+ msg_fmt = _("The supplied device path (%(path)s) is invalid.")
+
+
+class DevicePathInUse(Invalid):
+ msg_fmt = _("The supplied device path (%(path)s) is in use.")
+ code = 409
+
+
+class InvalidCPUInfo(Invalid):
+ msg_fmt = _("Unacceptable CPU info: %(reason)s")
+
+
+class InvalidIpAddressError(Invalid):
+ msg_fmt = _("%(address)s is not a valid IP v4/6 address.")
+
+
+class InvalidVLANTag(Invalid):
+ msg_fmt = _("VLAN tag is not appropriate for the port group "
+ "%(bridge)s. Expected VLAN tag is %(tag)s, "
+ "but the one associated with the port group is %(pgroup)s.")
+
+
+class InvalidVLANPortGroup(Invalid):
+ msg_fmt = _("vSwitch which contains the port group %(bridge)s is "
+ "not associated with the desired physical adapter. "
+ "Expected vSwitch is %(expected)s, but the one associated "
+ "is %(actual)s.")
+
+
+class InvalidDiskFormat(Invalid):
+ msg_fmt = _("Disk format %(disk_format)s is not acceptable")
+
+
+class InvalidDiskInfo(Invalid):
+ msg_fmt = _("Disk info file is invalid: %(reason)s")
+
+
+class DiskInfoReadWriteFail(Invalid):
+ msg_fmt = _("Failed to read or write disk info file: %(reason)s")
+
+
+class ImageUnacceptable(Invalid):
+ msg_fmt = _("Image %(image_id)s is unacceptable: %(reason)s")
+
+
+class ImageBadRequest(Invalid):
+ msg_fmt = _("Request of image %(image_id)s got BadRequest response: "
+ "%(response)s")
+
+
+class InstanceUnacceptable(Invalid):
+ msg_fmt = _("Instance %(instance_id)s is unacceptable: %(reason)s")
+
+
+class InvalidEc2Id(Invalid):
+ msg_fmt = _("Ec2 id %(ec2_id)s is unacceptable.")
+
+
+class InvalidUUID(Invalid):
+ msg_fmt = _("Expected a uuid but received %(uuid)s.")
+
+
+class InvalidID(Invalid):
+ msg_fmt = _("Invalid ID received %(id)s.")
+
+
+class ConstraintNotMet(NovaException):
+ msg_fmt = _("Constraint not met.")
+ code = 412
+
+
+class NotFound(NovaException):
+ msg_fmt = _("Resource could not be found.")
+ code = 404
+
+
+class AgentBuildNotFound(NotFound):
+ msg_fmt = _("No agent-build associated with id %(id)s.")
+
+
+class AgentBuildExists(NovaException):
+ msg_fmt = _("Agent-build with hypervisor %(hypervisor)s os %(os)s "
+ "architecture %(architecture)s exists.")
+
+
+class VolumeAttachmentNotFound(NotFound):
+ msg_fmt = _("Volume attachment %(attachment_id)s could not be found.")
+
+
+class VolumeNotFound(NotFound):
+ msg_fmt = _("Volume %(volume_id)s could not be found.")
+
+
+class VolumeTypeNotFound(NotFound):
+ msg_fmt = _("Volume type %(id_or_name)s could not be found.")
+
+
+class UndefinedRootBDM(NovaException):
+ msg_fmt = _("Undefined Block Device Mapping root: BlockDeviceMappingList "
+ "contains Block Device Mappings from multiple instances.")
+
+
+class BDMNotFound(NotFound):
+ msg_fmt = _("No Block Device Mapping with id %(id)s.")
+
+
+class VolumeBDMNotFound(NotFound):
+ msg_fmt = _("No volume Block Device Mapping with id %(volume_id)s.")
+
+
+class VolumeBDMIsMultiAttach(Invalid):
+ msg_fmt = _("Block Device Mapping %(volume_id)s is a multi-attach volume"
+ " and is not valid for this operation.")
+
+
+class VolumeBDMPathNotFound(VolumeBDMNotFound):
+ msg_fmt = _("No volume Block Device Mapping at path: %(path)s")
+
+
+class DeviceDetachFailed(NovaException):
+ msg_fmt = _("Device detach failed for %(device)s: %(reason)s")
+
+
+class DeviceNotFound(NotFound):
+ msg_fmt = _("Device '%(device)s' not found.")
+
+
+class SnapshotNotFound(NotFound):
+ msg_fmt = _("Snapshot %(snapshot_id)s could not be found.")
+
+
+class DiskNotFound(NotFound):
+ msg_fmt = _("No disk at %(location)s")
+
+
+class VolumeDriverNotFound(NotFound):
+ msg_fmt = _("Could not find a handler for %(driver_type)s volume.")
+
+
+class InvalidImageRef(Invalid):
+ msg_fmt = _("Invalid image href %(image_href)s.")
+
+
+class AutoDiskConfigDisabledByImage(Invalid):
+ msg_fmt = _("Requested image %(image)s "
+ "has automatic disk resize disabled.")
+
+
+class ImageNotFound(NotFound):
+ msg_fmt = _("Image %(image_id)s could not be found.")
+
+
+class ImageDeleteConflict(NovaException):
+ msg_fmt = _("Conflict deleting image. Reason: %(reason)s.")
+
+
+class ImageHandlerUnsupported(NovaException):
+ msg_fmt = _("Error: unsupported image handler %(image_handler)s.")
+
+
+class PreserveEphemeralNotSupported(Invalid):
+ msg_fmt = _("The current driver does not support "
+ "preserving ephemeral partitions.")
+
+
+class ProjectNotFound(NotFound):
+ msg_fmt = _("Project %(project_id)s could not be found.")
+
+
+class StorageRepositoryNotFound(NotFound):
+ msg_fmt = _("Cannot find SR to read/write VDI.")
+
+
+class InstanceMappingNotFound(NotFound):
+ msg_fmt = _("Instance %(uuid)s has no mapping to a cell.")
+
+
+class NetworkDhcpReleaseFailed(NovaException):
+ msg_fmt = _("Failed to release IP %(address)s with MAC %(mac_address)s")
+
+
+class NetworkInUse(NovaException):
+ msg_fmt = _("Network %(network_id)s is still in use.")
+
+
+class NetworkSetHostFailed(NovaException):
+ msg_fmt = _("Network set host failed for network %(network_id)s.")
+
+
+class NetworkNotCreated(Invalid):
+ msg_fmt = _("%(req)s is required to create a network.")
+
+
+class LabelTooLong(Invalid):
+ msg_fmt = _("Maximum allowed length for 'label' is 255.")
+
+
+class InvalidIntValue(Invalid):
+ msg_fmt = _("%(key)s must be an integer.")
+
+
+class InvalidCidr(Invalid):
+ msg_fmt = _("%(cidr)s is not a valid IP network.")
+
+
+class InvalidAddress(Invalid):
+ msg_fmt = _("%(address)s is not a valid IP address.")
+
+
+class AddressOutOfRange(Invalid):
+ msg_fmt = _("%(address)s is not within %(cidr)s.")
+
+
+class DuplicateVlan(NovaException):
+ msg_fmt = _("Detected existing vlan with id %(vlan)d")
+ code = 409
+
+
+class CidrConflict(NovaException):
+ msg_fmt = _('Requested cidr (%(cidr)s) conflicts '
+ 'with existing cidr (%(other)s)')
+ code = 409
+
+
+class NetworkHasProject(NetworkInUse):
+ msg_fmt = _('Network must be disassociated from project '
+ '%(project_id)s before it can be deleted.')
+
+
+class NetworkNotFound(NotFound):
+ msg_fmt = _("Network %(network_id)s could not be found.")
+
+
+class PortNotFound(NotFound):
+ msg_fmt = _("Port id %(port_id)s could not be found.")
+
+
+class NetworkNotFoundForBridge(NetworkNotFound):
+ msg_fmt = _("Network could not be found for bridge %(bridge)s")
+
+
+class NetworkNotFoundForUUID(NetworkNotFound):
+ msg_fmt = _("Network could not be found for uuid %(uuid)s")
+
+
+class NetworkNotFoundForCidr(NetworkNotFound):
+ msg_fmt = _("Network could not be found with cidr %(cidr)s.")
+
+
+class NetworkNotFoundForInstance(NetworkNotFound):
+ msg_fmt = _("Network could not be found for instance %(instance_id)s.")
+
+
+class NoNetworksFound(NotFound):
+ msg_fmt = _("No networks defined.")
+
+
+class NoMoreNetworks(NovaException):
+ msg_fmt = _("No more available networks.")
+
+
+class NetworkNotFoundForProject(NetworkNotFound):
+ msg_fmt = _("Either network uuid %(network_uuid)s is not present or "
+ "is not assigned to the project %(project_id)s.")
+
+
+class NetworkAmbiguous(Invalid):
+ msg_fmt = _("More than one possible network found. Specify "
+ "network ID(s) to select which one(s) to connect to.")
+
+
+class UnableToAutoAllocateNetwork(Invalid):
+ msg_fmt = _('Unable to automatically allocate a network for project '
+ '%(project_id)s')
+
+
+class NetworkRequiresSubnet(Invalid):
+ msg_fmt = _("Network %(network_uuid)s requires a subnet in order to boot"
+ " instances on.")
+
+
+class ExternalNetworkAttachForbidden(Forbidden):
+ msg_fmt = _("It is not allowed to create an interface on "
+ "external network %(network_uuid)s")
+
+
+class NetworkMissingPhysicalNetwork(NovaException):
+ msg_fmt = _("Physical network is missing for network %(network_uuid)s")
+
+
+class VifDetailsMissingVhostuserSockPath(Invalid):
+ msg_fmt = _("vhostuser_sock_path not present in vif_details"
+ " for vif %(vif_id)s")
+
+
+class VifDetailsMissingMacvtapParameters(Invalid):
+ msg_fmt = _("Parameters %(missing_params)s not present in"
+ " vif_details for vif %(vif_id)s. Check your Neutron"
+ " configuration to validate that the macvtap parameters are"
+ " correct.")
+
+
+class OvsConfigurationFailure(NovaException):
+ msg_fmt = _("OVS configuration failed with: %(inner_exception)s.")
+
+
+class DatastoreNotFound(NotFound):
+ msg_fmt = _("Could not find the datastore reference(s) which the VM uses.")
+
+
+class PortInUse(Invalid):
+ msg_fmt = _("Port %(port_id)s is still in use.")
+
+
+class PortRequiresFixedIP(Invalid):
+ msg_fmt = _("Port %(port_id)s requires a FixedIP in order to be used.")
+
+
+class PortNotUsable(Invalid):
+ msg_fmt = _("Port %(port_id)s not usable for instance %(instance)s.")
+
+
+class PortNotUsableDNS(Invalid):
+ msg_fmt = _("Port %(port_id)s not usable for instance %(instance)s. "
+ "Value %(value)s assigned to dns_name attribute does not "
+ "match instance's hostname %(hostname)s")
+
+
+class PortNotFree(Invalid):
+ msg_fmt = _("No free port available for instance %(instance)s.")
+
+
+class PortBindingFailed(Invalid):
+ msg_fmt = _("Binding failed for port %(port_id)s, please check neutron "
+ "logs for more information.")
+
+
+class PortBindingDeletionFailed(NovaException):
+ msg_fmt = _("Failed to delete binding for port %(port_id)s and host "
+ "%(host)s.")
+
+
+class PortBindingActivationFailed(NovaException):
+ msg_fmt = _("Failed to activate binding for port %(port_id)s and host "
+ "%(host)s.")
+
+
+class PortUpdateFailed(Invalid):
+ msg_fmt = _("Port update failed for port %(port_id)s: %(reason)s")
+
+
+class AttachSRIOVPortNotSupported(Invalid):
+ msg_fmt = _('Attaching SR-IOV port %(port_id)s to server '
+ '%(instance_uuid)s is not supported. SR-IOV ports must be '
+ 'specified during server creation.')
+
+
+class FixedIpExists(NovaException):
+ msg_fmt = _("Fixed IP %(address)s already exists.")
+
+
+class FixedIpNotFound(NotFound):
+ msg_fmt = _("No fixed IP associated with id %(id)s.")
+
+
+class FixedIpNotFoundForAddress(FixedIpNotFound):
+ msg_fmt = _("Fixed IP not found for address %(address)s.")
+
+
+class FixedIpNotFoundForInstance(FixedIpNotFound):
+ msg_fmt = _("Instance %(instance_uuid)s has zero fixed IPs.")
+
+
+class FixedIpNotFoundForNetworkHost(FixedIpNotFound):
+ msg_fmt = _("Network host %(host)s has zero fixed IPs "
+ "in network %(network_id)s.")
+
+
+class FixedIpNotFoundForSpecificInstance(FixedIpNotFound):
+ msg_fmt = _("Instance %(instance_uuid)s doesn't have fixed IP '%(ip)s'.")
+
+
+class FixedIpNotFoundForNetwork(FixedIpNotFound):
+ msg_fmt = _("Fixed IP address (%(address)s) does not exist in "
+ "network (%(network_uuid)s).")
+
+
+class FixedIpAssociateFailed(NovaException):
+ msg_fmt = _("Fixed IP associate failed for network: %(net)s.")
+
+
+class FixedIpAlreadyInUse(NovaException):
+ msg_fmt = _("Fixed IP address %(address)s is already in use on instance "
+ "%(instance_uuid)s.")
+
+
+class FixedIpAssociatedWithMultipleInstances(NovaException):
+ msg_fmt = _("More than one instance is associated with fixed IP address "
+ "'%(address)s'.")
+
+
+class FixedIpInvalid(Invalid):
+ msg_fmt = _("Fixed IP address %(address)s is invalid.")
+
+
+class FixedIpInvalidOnHost(Invalid):
+ msg_fmt = _("The fixed IP associated with port %(port_id)s is not "
+ "compatible with the host.")
+
+
+class NoMoreFixedIps(NovaException):
+ msg_fmt = _("No fixed IP addresses available for network: %(net)s")
+
+
+class NoFixedIpsDefined(NotFound):
+ msg_fmt = _("Zero fixed IPs could be found.")
+
+
+class FloatingIpExists(NovaException):
+ msg_fmt = _("Floating IP %(address)s already exists.")
+
+
+class FloatingIpNotFound(NotFound):
+ msg_fmt = _("Floating IP not found for ID %(id)s.")
+
+
+class FloatingIpDNSExists(Invalid):
+ msg_fmt = _("The DNS entry %(name)s already exists in domain %(domain)s.")
+
+
+class FloatingIpNotFoundForAddress(FloatingIpNotFound):
+ msg_fmt = _("Floating IP not found for address %(address)s.")
+
+
+class FloatingIpNotFoundForHost(FloatingIpNotFound):
+ msg_fmt = _("Floating IP not found for host %(host)s.")
+
+
+class FloatingIpMultipleFoundForAddress(NovaException):
+ msg_fmt = _("Multiple floating IPs are found for address %(address)s.")
+
+
+class FloatingIpPoolNotFound(NotFound):
+ msg_fmt = _("Floating IP pool not found.")
+ safe = True
+
+
+class NoMoreFloatingIps(FloatingIpNotFound):
+ msg_fmt = _("Zero floating IPs available.")
+ safe = True
+
+
+class FloatingIpAssociated(NovaException):
+ msg_fmt = _("Floating IP %(address)s is associated.")
+
+
+class FloatingIpNotAssociated(NovaException):
+ msg_fmt = _("Floating IP %(address)s is not associated.")
+
+
+class NoFloatingIpsDefined(NotFound):
+ msg_fmt = _("Zero floating IPs exist.")
+
+
+class NoFloatingIpInterface(NotFound):
+ msg_fmt = _("Interface %(interface)s not found.")
+
+
+class FloatingIpAllocateFailed(NovaException):
+ msg_fmt = _("Floating IP allocate failed.")
+
+
+class FloatingIpAssociateFailed(NovaException):
+ msg_fmt = _("Floating IP %(address)s association has failed.")
+
+
+class FloatingIpBadRequest(Invalid):
+ msg_fmt = _("The floating IP request failed with a BadRequest")
+
+
+class CannotDisassociateAutoAssignedFloatingIP(NovaException):
+ msg_fmt = _("Cannot disassociate auto assigned floating IP")
+
+
+class KeypairNotFound(NotFound):
+ msg_fmt = _("Keypair %(name)s not found for user %(user_id)s")
+
+
+class ServiceNotFound(NotFound):
+ msg_fmt = _("Service %(service_id)s could not be found.")
+
+
+class ConfGroupForServiceTypeNotFound(ServiceNotFound):
+ msg_fmt = _("No conf group name could be found for service type "
+ "%(stype)s.")
+
+
+class ServiceBinaryExists(NovaException):
+ msg_fmt = _("Service with host %(host)s binary %(binary)s exists.")
+
+
+class ServiceTopicExists(NovaException):
+ msg_fmt = _("Service with host %(host)s topic %(topic)s exists.")
+
+
+class HostNotFound(NotFound):
+ msg_fmt = _("Host %(host)s could not be found.")
+
+
+class ComputeHostNotFound(HostNotFound):
+ msg_fmt = _("Compute host %(host)s could not be found.")
+
+
+class HostBinaryNotFound(NotFound):
+ msg_fmt = _("Could not find binary %(binary)s on host %(host)s.")
+
+
+class InvalidReservationExpiration(Invalid):
+ msg_fmt = _("Invalid reservation expiration %(expire)s.")
+
+
+class InvalidQuotaValue(Invalid):
+ msg_fmt = _("Change would make usage less than 0 for the following "
+ "resources: %(unders)s")
+
+
+class InvalidQuotaMethodUsage(Invalid):
+ msg_fmt = _("Wrong quota method %(method)s used on resource %(res)s")
+
+
+class QuotaNotFound(NotFound):
+ msg_fmt = _("Quota could not be found")
+
+
+class QuotaExists(NovaException):
+ msg_fmt = _("Quota exists for project %(project_id)s, "
+ "resource %(resource)s")
+
+
+class QuotaResourceUnknown(QuotaNotFound):
+ msg_fmt = _("Unknown quota resources %(unknown)s.")
+
+
+class ProjectUserQuotaNotFound(QuotaNotFound):
+ msg_fmt = _("Quota for user %(user_id)s in project %(project_id)s "
+ "could not be found.")
+
+
+class ProjectQuotaNotFound(QuotaNotFound):
+ msg_fmt = _("Quota for project %(project_id)s could not be found.")
+
+
+class QuotaClassNotFound(QuotaNotFound):
+ msg_fmt = _("Quota class %(class_name)s could not be found.")
+
+
+class QuotaClassExists(NovaException):
+ msg_fmt = _("Quota class %(class_name)s exists for resource %(resource)s")
+
+
+class QuotaUsageNotFound(QuotaNotFound):
+ msg_fmt = _("Quota usage for project %(project_id)s could not be found.")
+
+
+class QuotaUsageRefreshNotAllowed(Invalid):
+ msg_fmt = _("Quota usage refresh of resource %(resource)s for project "
+ "%(project_id)s, user %(user_id)s, is not allowed. "
+ "The allowed resources are %(syncable)s.")
+
+
+class ReservationNotFound(QuotaNotFound):
+ msg_fmt = _("Quota reservation %(uuid)s could not be found.")
+
+
+class OverQuota(NovaException):
+ msg_fmt = _("Quota exceeded for resources: %(overs)s")
+
+
+class SecurityGroupNotFound(NotFound):
+ msg_fmt = _("Security group %(security_group_id)s not found.")
+
+
+class SecurityGroupNotFoundForProject(SecurityGroupNotFound):
+ msg_fmt = _("Security group %(security_group_id)s not found "
+ "for project %(project_id)s.")
+
+
+class SecurityGroupNotFoundForRule(SecurityGroupNotFound):
+ msg_fmt = _("Security group with rule %(rule_id)s not found.")
+
+
+class SecurityGroupExists(Invalid):
+ msg_fmt = _("Security group %(security_group_name)s already exists "
+ "for project %(project_id)s.")
+
+
+class SecurityGroupExistsForInstance(Invalid):
+ msg_fmt = _("Security group %(security_group_id)s is already associated"
+ " with the instance %(instance_id)s")
+
+
+class SecurityGroupNotExistsForInstance(Invalid):
+ msg_fmt = _("Security group %(security_group_id)s is not associated with"
+ " the instance %(instance_id)s")
+
+
+class SecurityGroupDefaultRuleNotFound(Invalid):
+ msg_fmt = _("Security group default rule (%rule_id)s not found.")
+
+
+class SecurityGroupCannotBeApplied(Invalid):
+ msg_fmt = _("Network requires port_security_enabled and subnet associated"
+ " in order to apply security groups.")
+
+
+class NoUniqueMatch(NovaException):
+ msg_fmt = _("No Unique Match Found.")
+ code = 409
+
+
+class NoActiveMigrationForInstance(NotFound):
+ msg_fmt = _("Active live migration for instance %(instance_id)s not found")
+
+
+class MigrationNotFound(NotFound):
+ msg_fmt = _("Migration %(migration_id)s could not be found.")
+
+
+class MigrationNotFoundByStatus(MigrationNotFound):
+ msg_fmt = _("Migration not found for instance %(instance_id)s "
+ "with status %(status)s.")
+
+
+class MigrationNotFoundForInstance(MigrationNotFound):
+ msg_fmt = _("Migration %(migration_id)s not found for instance "
+ "%(instance_id)s")
+
+
+class InvalidMigrationState(Invalid):
+ msg_fmt = _("Migration %(migration_id)s state of instance "
+ "%(instance_uuid)s is %(state)s. Cannot %(method)s while the "
+ "migration is in this state.")
+
+
+class AbortQueuedLiveMigrationNotYetSupported(NovaException):
+ msg_fmt = _("Aborting live migration %(migration_id)s with status "
+ "%(status)s is not yet supported for this instance.")
+ code = 409
+
+
+class ConsoleLogOutputException(NovaException):
+ msg_fmt = _("Console log output could not be retrieved for instance "
+ "%(instance_id)s. Reason: %(reason)s")
+
+
+class ConsolePoolExists(NovaException):
+ msg_fmt = _("Console pool with host %(host)s, console_type "
+ "%(console_type)s and compute_host %(compute_host)s "
+ "already exists.")
+
+
+class ConsolePoolNotFoundForHostType(NotFound):
+ msg_fmt = _("Console pool of type %(console_type)s "
+ "for compute host %(compute_host)s "
+ "on proxy host %(host)s not found.")
+
+
+class ConsoleNotFound(NotFound):
+ msg_fmt = _("Console %(console_id)s could not be found.")
+
+
+class ConsoleNotFoundForInstance(ConsoleNotFound):
+ msg_fmt = _("Console for instance %(instance_uuid)s could not be found.")
+
+
+class ConsoleNotAvailable(NotFound):
+ msg_fmt = _("Guest does not have a console available.")
+
+
+class ConsoleNotFoundInPoolForInstance(ConsoleNotFound):
+ msg_fmt = _("Console for instance %(instance_uuid)s "
+ "in pool %(pool_id)s could not be found.")
+
+
+class ConsoleTypeInvalid(Invalid):
+ msg_fmt = _("Invalid console type %(console_type)s")
+
+
+class ConsoleTypeUnavailable(Invalid):
+ msg_fmt = _("Unavailable console type %(console_type)s.")
+
+
+class ConsolePortRangeExhausted(NovaException):
+ msg_fmt = _("The console port range %(min_port)d-%(max_port)d is "
+ "exhausted.")
+
+
+class FlavorNotFound(NotFound):
+ msg_fmt = _("Flavor %(flavor_id)s could not be found.")
+
+
+class FlavorNotFoundByName(FlavorNotFound):
+ msg_fmt = _("Flavor with name %(flavor_name)s could not be found.")
+
+
+class FlavorAccessNotFound(NotFound):
+ msg_fmt = _("Flavor access not found for %(flavor_id)s / "
+ "%(project_id)s combination.")
+
+
+class FlavorExtraSpecUpdateCreateFailed(NovaException):
+ msg_fmt = _("Flavor %(id)s extra spec cannot be updated or created "
+ "after %(retries)d retries.")
+
+
+class CellNotFound(NotFound):
+ msg_fmt = _("Cell %(cell_name)s doesn't exist.")
+
+
+class CellExists(NovaException):
+ msg_fmt = _("Cell with name %(name)s already exists.")
+
+
+class CellRoutingInconsistency(NovaException):
+ msg_fmt = _("Inconsistency in cell routing: %(reason)s")
+
+
+class CellServiceAPIMethodNotFound(NotFound):
+ msg_fmt = _("Service API method not found: %(detail)s")
+
+
+class CellTimeout(NotFound):
+ msg_fmt = _("Timeout waiting for response from cell")
+
+
+class CellMaxHopCountReached(NovaException):
+ msg_fmt = _("Cell message has reached maximum hop count: %(hop_count)s")
+
+
+class NoCellsAvailable(NovaException):
+ msg_fmt = _("No cells available matching scheduling criteria.")
+
+
+class CellsUpdateUnsupported(NovaException):
+ msg_fmt = _("Cannot update cells configuration file.")
+
+
+class InstanceUnknownCell(NotFound):
+ msg_fmt = _("Cell is not known for instance %(instance_uuid)s")
+
+
+class SchedulerHostFilterNotFound(NotFound):
+ msg_fmt = _("Scheduler Host Filter %(filter_name)s could not be found.")
+
+
+class FlavorExtraSpecsNotFound(NotFound):
+ msg_fmt = _("Flavor %(flavor_id)s has no extra specs with "
+ "key %(extra_specs_key)s.")
+
+
+class ComputeHostMetricNotFound(NotFound):
+ msg_fmt = _("Metric %(name)s could not be found on the compute "
+ "host node %(host)s.%(node)s.")
+
+
+class FileNotFound(NotFound):
+ msg_fmt = _("File %(file_path)s could not be found.")
+
+
+class SwitchNotFoundForNetworkAdapter(NotFound):
+ msg_fmt = _("Virtual switch associated with the "
+ "network adapter %(adapter)s not found.")
+
+
+class NetworkAdapterNotFound(NotFound):
+ msg_fmt = _("Network adapter %(adapter)s could not be found.")
+
+
+class ClassNotFound(NotFound):
+ msg_fmt = _("Class %(class_name)s could not be found: %(exception)s")
+
+
+class InstanceTagNotFound(NotFound):
+ msg_fmt = _("Instance %(instance_id)s has no tag '%(tag)s'")
+
+
+class KeyPairExists(NovaException):
+ msg_fmt = _("Key pair '%(key_name)s' already exists.")
+
+
+class InstanceExists(NovaException):
+ msg_fmt = _("Instance %(name)s already exists.")
+
+
+class FlavorExists(NovaException):
+ msg_fmt = _("Flavor with name %(name)s already exists.")
+
+
+class FlavorIdExists(NovaException):
+ msg_fmt = _("Flavor with ID %(flavor_id)s already exists.")
+
+
+class FlavorAccessExists(NovaException):
+ msg_fmt = _("Flavor access already exists for flavor %(flavor_id)s "
+ "and project %(project_id)s combination.")
+
+
+class InvalidSharedStorage(NovaException):
+ msg_fmt = _("%(path)s is not on shared storage: %(reason)s")
+
+
+class InvalidLocalStorage(NovaException):
+ msg_fmt = _("%(path)s is not on local storage: %(reason)s")
+
+
+class StorageError(NovaException):
+ msg_fmt = _("Storage error: %(reason)s")
+
+
+class MigrationError(NovaException):
+ msg_fmt = _("Migration error: %(reason)s")
+
+
+class MigrationPreCheckError(MigrationError):
+ msg_fmt = _("Migration pre-check error: %(reason)s")
+
+
+class MigrationSchedulerRPCError(MigrationError):
+ msg_fmt = _("Migration select destinations error: %(reason)s")
+
+
+class RPCPinnedToOldVersion(NovaException):
+ msg_fmt = _("RPC is pinned to old version")
+
+
+class MalformedRequestBody(NovaException):
+ msg_fmt = _("Malformed message body: %(reason)s")
+
+
+# NOTE(johannes): NotFound should only be used when a 404 error is
+# appropriate to be returned
+class ConfigNotFound(NovaException):
+ msg_fmt = _("Could not find config at %(path)s")
+
+
+class PasteAppNotFound(NovaException):
+ msg_fmt = _("Could not load paste app '%(name)s' from %(path)s")
+
+
+class CannotResizeToSameFlavor(NovaException):
+ msg_fmt = _("When resizing, instances must change flavor!")
+
+
+class ResizeError(NovaException):
+ msg_fmt = _("Resize error: %(reason)s")
+
+
+class CannotResizeDisk(NovaException):
+ msg_fmt = _("Server disk was unable to be resized because: %(reason)s")
+
+
+class FlavorMemoryTooSmall(NovaException):
+ msg_fmt = _("Flavor's memory is too small for requested image.")
+
+
+class FlavorDiskTooSmall(NovaException):
+ msg_fmt = _("The created instance's disk would be too small.")
+
+
+class FlavorDiskSmallerThanImage(FlavorDiskTooSmall):
+ msg_fmt = _("Flavor's disk is too small for requested image. Flavor disk "
+ "is %(flavor_size)i bytes, image is %(image_size)i bytes.")
+
+
+class FlavorDiskSmallerThanMinDisk(FlavorDiskTooSmall):
+ msg_fmt = _("Flavor's disk is smaller than the minimum size specified in "
+ "image metadata. Flavor disk is %(flavor_size)i bytes, "
+ "minimum size is %(image_min_disk)i bytes.")
+
+
+class VolumeSmallerThanMinDisk(FlavorDiskTooSmall):
+ msg_fmt = _("Volume is smaller than the minimum size specified in image "
+ "metadata. Volume size is %(volume_size)i bytes, minimum "
+ "size is %(image_min_disk)i bytes.")
+
+
+class BootFromVolumeRequiredForZeroDiskFlavor(Forbidden):
+ msg_fmt = _("Only volume-backed servers are allowed for flavors with "
+ "zero disk.")
+
+
+class InsufficientFreeMemory(NovaException):
+ msg_fmt = _("Insufficient free memory on compute node to start %(uuid)s.")
+
+
+class NoValidHost(NovaException):
+ msg_fmt = _("No valid host was found. %(reason)s")
+
+
+class RequestFilterFailed(NovaException):
+ msg_fmt = _("Scheduling failed: %(reason)s")
+
+
+class MaxRetriesExceeded(NoValidHost):
+ msg_fmt = _("Exceeded maximum number of retries. %(reason)s")
+
+
+class QuotaError(NovaException):
+ msg_fmt = _("Quota exceeded: code=%(code)s")
+ # NOTE(cyeoh): 413 should only be used for the ec2 API
+ # The error status code for out of quota for the nova api should be
+ # 403 Forbidden.
+ code = 413
+ safe = True
+
+
+class TooManyInstances(QuotaError):
+ msg_fmt = _("Quota exceeded for %(overs)s: Requested %(req)s,"
+ " but already used %(used)s of %(allowed)s %(overs)s")
+
+
+class FloatingIpLimitExceeded(QuotaError):
+ msg_fmt = _("Maximum number of floating IPs exceeded")
+
+
+class FixedIpLimitExceeded(QuotaError):
+ msg_fmt = _("Maximum number of fixed IPs exceeded")
+
+
+class MetadataLimitExceeded(QuotaError):
+ msg_fmt = _("Maximum number of metadata items exceeds %(allowed)d")
+
+
+class OnsetFileLimitExceeded(QuotaError):
+ msg_fmt = _("Personality file limit exceeded")
+
+
+class OnsetFilePathLimitExceeded(OnsetFileLimitExceeded):
+ msg_fmt = _("Personality file path exceeds maximum %(allowed)s")
+
+
+class OnsetFileContentLimitExceeded(OnsetFileLimitExceeded):
+ msg_fmt = _("Personality file content exceeds maximum %(allowed)s")
+
+
+class KeypairLimitExceeded(QuotaError):
+ msg_fmt = _("Maximum number of key pairs exceeded")
+
+
+class SecurityGroupLimitExceeded(QuotaError):
+ msg_fmt = _("Maximum number of security groups or rules exceeded")
+
+
+class PortLimitExceeded(QuotaError):
+ msg_fmt = _("Maximum number of ports exceeded")
+
+
+class AggregateError(NovaException):
+ msg_fmt = _("Aggregate %(aggregate_id)s: action '%(action)s' "
+ "caused an error: %(reason)s.")
+
+
+class AggregateNotFound(NotFound):
+ msg_fmt = _("Aggregate %(aggregate_id)s could not be found.")
+
+
+class AggregateNameExists(NovaException):
+ msg_fmt = _("Aggregate %(aggregate_name)s already exists.")
+
+
+class AggregateHostNotFound(NotFound):
+ msg_fmt = _("Aggregate %(aggregate_id)s has no host %(host)s.")
+
+
+class AggregateMetadataNotFound(NotFound):
+ msg_fmt = _("Aggregate %(aggregate_id)s has no metadata with "
+ "key %(metadata_key)s.")
+
+
+class AggregateHostExists(NovaException):
+ msg_fmt = _("Aggregate %(aggregate_id)s already has host %(host)s.")
+
+
+class InstancePasswordSetFailed(NovaException):
+ msg_fmt = _("Failed to set admin password on %(instance)s "
+ "because %(reason)s")
+ safe = True
+
+
+class InstanceNotFound(NotFound):
+ msg_fmt = _("Instance %(instance_id)s could not be found.")
+
+
+class InstanceInfoCacheNotFound(NotFound):
+ msg_fmt = _("Info cache for instance %(instance_uuid)s could not be "
+ "found.")
+
+
+class MarkerNotFound(NotFound):
+ msg_fmt = _("Marker %(marker)s could not be found.")
+
+
+class CouldNotFetchImage(NovaException):
+ msg_fmt = _("Could not fetch image %(image_id)s")
+
+
+class CouldNotUploadImage(NovaException):
+ msg_fmt = _("Could not upload image %(image_id)s")
+
+
+class TaskAlreadyRunning(NovaException):
+ msg_fmt = _("Task %(task_name)s is already running on host %(host)s")
+
+
+class TaskNotRunning(NovaException):
+ msg_fmt = _("Task %(task_name)s is not running on host %(host)s")
+
+
+class InstanceIsLocked(InstanceInvalidState):
+ msg_fmt = _("Instance %(instance_uuid)s is locked")
+
+
+class ConfigDriveInvalidValue(Invalid):
+ msg_fmt = _("Invalid value for Config Drive option: %(option)s")
+
+
+class ConfigDriveUnsupportedFormat(Invalid):
+ msg_fmt = _("Config drive format '%(format)s' is not supported.")
+
+
+class ConfigDriveMountFailed(NovaException):
+ msg_fmt = _("Could not mount vfat config drive. %(operation)s failed. "
+ "Error: %(error)s")
+
+
+class ConfigDriveUnknownFormat(NovaException):
+ msg_fmt = _("Unknown config drive format %(format)s. Select one of "
+ "iso9660 or vfat.")
+
+
+class ConfigDriveNotFound(NotFound):
+ msg_fmt = _("Instance %(instance_uuid)s requires config drive, but it "
+ "does not exist.")
+
+
+class InterfaceAttachFailed(Invalid):
+ msg_fmt = _("Failed to attach network adapter device to "
+ "%(instance_uuid)s")
+
+
+class InterfaceAttachFailedNoNetwork(InterfaceAttachFailed):
+ msg_fmt = _("No specific network was requested and none are available "
+ "for project '%(project_id)s'.")
+
+
+class InterfaceDetachFailed(Invalid):
+ msg_fmt = _("Failed to detach network adapter device from "
+ "%(instance_uuid)s")
+
+
+class InstanceUserDataMalformed(NovaException):
+ msg_fmt = _("User data needs to be valid base 64.")
+
+
+class InstanceUpdateConflict(NovaException):
+ msg_fmt = _("Conflict updating instance %(instance_uuid)s. "
+ "Expected: %(expected)s. Actual: %(actual)s")
+
+
+class UnknownInstanceUpdateConflict(InstanceUpdateConflict):
+ msg_fmt = _("Conflict updating instance %(instance_uuid)s, but we were "
+ "unable to determine the cause")
+
+
+class UnexpectedTaskStateError(InstanceUpdateConflict):
+ pass
+
+
+class UnexpectedDeletingTaskStateError(UnexpectedTaskStateError):
+ pass
+
+
+class InstanceActionNotFound(NovaException):
+ msg_fmt = _("Action for request_id %(request_id)s on instance"
+ " %(instance_uuid)s not found")
+
+
+class InstanceActionEventNotFound(NovaException):
+ msg_fmt = _("Event %(event)s not found for action id %(action_id)s")
+
+
+class CryptoCAFileNotFound(FileNotFound):
+ msg_fmt = _("The CA file for %(project)s could not be found")
+
+
+class CryptoCRLFileNotFound(FileNotFound):
+ msg_fmt = _("The CRL file for %(project)s could not be found")
+
+
+class InstanceEvacuateNotSupported(Invalid):
+ msg_fmt = _('Instance evacuate is not supported.')
+
+
+class DBNotAllowed(NovaException):
+ msg_fmt = _('%(binary)s attempted direct database access which is '
+ 'not allowed by policy')
+
+
+class UnsupportedVirtType(Invalid):
+ msg_fmt = _("Virtualization type '%(virt)s' is not supported by "
+ "this compute driver")
+
+
+class UnsupportedHardware(Invalid):
+ msg_fmt = _("Requested hardware '%(model)s' is not supported by "
+ "the '%(virt)s' virt driver")
+
+
+class Base64Exception(NovaException):
+ msg_fmt = _("Invalid Base 64 data for file %(path)s")
+
+
+class BuildAbortException(NovaException):
+ msg_fmt = _("Build of instance %(instance_uuid)s aborted: %(reason)s")
+
+
+class RescheduledException(NovaException):
+ msg_fmt = _("Build of instance %(instance_uuid)s was re-scheduled: "
+ "%(reason)s")
+
+
+class ShadowTableExists(NovaException):
+ msg_fmt = _("Shadow table with name %(name)s already exists.")
+
+
+class InstanceFaultRollback(NovaException):
+ def __init__(self, inner_exception=None):
+ message = _("Instance rollback performed due to: %s")
+ self.inner_exception = inner_exception
+ super(InstanceFaultRollback, self).__init__(message % inner_exception)
+
+
+class OrphanedObjectError(NovaException):
+ msg_fmt = _('Cannot call %(method)s on orphaned %(objtype)s object')
+
+
+class ObjectActionError(NovaException):
+ msg_fmt = _('Object action %(action)s failed because: %(reason)s')
+
+
+class AgentError(NovaException):
+ msg_fmt = _('Error during following call to agent: %(method)s')
+
+
+class AgentTimeout(AgentError):
+ msg_fmt = _('Unable to contact guest agent. '
+ 'The following call timed out: %(method)s')
+
+
+class AgentNotImplemented(AgentError):
+ msg_fmt = _('Agent does not support the call: %(method)s')
+
+
+class InstanceGroupNotFound(NotFound):
+ msg_fmt = _("Instance group %(group_uuid)s could not be found.")
+
+
+class InstanceGroupIdExists(NovaException):
+ msg_fmt = _("Instance group %(group_uuid)s already exists.")
+
+
+class InstanceGroupMemberNotFound(NotFound):
+ msg_fmt = _("Instance group %(group_uuid)s has no member with "
+ "id %(instance_id)s.")
+
+
+class InstanceGroupSaveException(NovaException):
+ msg_fmt = _("%(field)s should not be part of the updates.")
+
+
+class ResourceMonitorError(NovaException):
+ msg_fmt = _("Error when creating resource monitor: %(monitor)s")
+
+
+class PciDeviceWrongAddressFormat(NovaException):
+ msg_fmt = _("The PCI address %(address)s has an incorrect format.")
+
+
+class PciDeviceInvalidDeviceName(NovaException):
+ msg_fmt = _("Invalid PCI Whitelist: "
+ "The PCI whitelist can specify devname or address,"
+ " but not both")
+
+
+class PciDeviceNotFoundById(NotFound):
+ msg_fmt = _("PCI device %(id)s not found")
+
+
+class PciDeviceNotFound(NotFound):
+ msg_fmt = _("PCI Device %(node_id)s:%(address)s not found.")
+
+
+class PciDeviceInvalidStatus(Invalid):
+ msg_fmt = _(
+ "PCI device %(compute_node_id)s:%(address)s is %(status)s "
+ "instead of %(hopestatus)s")
+
+
+class PciDeviceVFInvalidStatus(Invalid):
+ msg_fmt = _(
+ "Not all Virtual Functions of PF %(compute_node_id)s:%(address)s "
+ "are free.")
+
+
+class PciDevicePFInvalidStatus(Invalid):
+ msg_fmt = _(
+ "Physical Function %(compute_node_id)s:%(address)s, related to VF"
+ " %(compute_node_id)s:%(vf_address)s is %(status)s "
+ "instead of %(hopestatus)s")
+
+
+class PciDeviceInvalidOwner(Invalid):
+ msg_fmt = _(
+ "PCI device %(compute_node_id)s:%(address)s is owned by %(owner)s "
+ "instead of %(hopeowner)s")
+
+
+class PciDeviceRequestFailed(NovaException):
+ msg_fmt = _(
+ "PCI device request %(requests)s failed")
+
+
+class PciDevicePoolEmpty(NovaException):
+ msg_fmt = _(
+ "Attempt to consume PCI device %(compute_node_id)s:%(address)s "
+ "from empty pool")
+
+
+class PciInvalidAlias(Invalid):
+ msg_fmt = _("Invalid PCI alias definition: %(reason)s")
+
+
+class PciRequestAliasNotDefined(NovaException):
+ msg_fmt = _("PCI alias %(alias)s is not defined")
+
+
+class PciConfigInvalidWhitelist(Invalid):
+ msg_fmt = _("Invalid PCI devices Whitelist config: %(reason)s")
+
+
+# Cannot be templated, msg needs to be constructed when raised.
+class InternalError(NovaException):
+ """Generic hypervisor errors.
+
+ Consider subclassing this to provide more specific exceptions.
+ """
+ msg_fmt = "%(err)s"
+
+
+class PciDevicePrepareFailed(NovaException):
+ msg_fmt = _("Failed to prepare PCI device %(id)s for instance "
+ "%(instance_uuid)s: %(reason)s")
+
+
+class PciDeviceDetachFailed(NovaException):
+ msg_fmt = _("Failed to detach PCI device %(dev)s: %(reason)s")
+
+
+class PciDeviceUnsupportedHypervisor(NovaException):
+ msg_fmt = _("%(type)s hypervisor does not support PCI devices")
+
+
+class KeyManagerError(NovaException):
+ msg_fmt = _("Key manager error: %(reason)s")
+
+
+class VolumesNotRemoved(Invalid):
+ msg_fmt = _("Failed to remove volume(s): (%(reason)s)")
+
+
+class VolumeRebaseFailed(NovaException):
+ msg_fmt = _("Volume rebase failed: %(reason)s")
+
+
+class InvalidVideoMode(Invalid):
+ msg_fmt = _("Provided video model (%(model)s) is not supported.")
+
+
+class RngDeviceNotExist(Invalid):
+ msg_fmt = _("The provided RNG device path: (%(path)s) is not "
+ "present on the host.")
+
+
+class RequestedVRamTooHigh(NovaException):
+ msg_fmt = _("The requested amount of video memory %(req_vram)d is higher "
+ "than the maximum allowed by flavor %(max_vram)d.")
+
+
+class SecurityProxyNegotiationFailed(NovaException):
+ msg_fmt = _("Failed to negotiate security type with server: %(reason)s")
+
+
+class RFBAuthHandshakeFailed(NovaException):
+ msg_fmt = _("Failed to complete auth handshake: %(reason)s")
+
+
+class RFBAuthNoAvailableScheme(NovaException):
+ msg_fmt = _("No matching auth scheme: allowed types: '%(allowed_types)s', "
+ "desired types: '%(desired_types)s'")
+
+
+class InvalidWatchdogAction(Invalid):
+ msg_fmt = _("Provided watchdog action (%(action)s) is not supported.")
+
+
+class LiveMigrationNotSubmitted(NovaException):
+ msg_fmt = _("Failed to submit live migration %(migration_uuid)s for "
+ "instance %(instance_uuid)s for processing.")
+
+
+class SelectionObjectsWithOldRPCVersionNotSupported(NovaException):
+ msg_fmt = _("Requests for Selection objects with alternates are not "
+ "supported in select_destinations() before RPC version 4.5; "
+ "version %(version)s requested.")
+
+
+class LiveMigrationURINotAvailable(NovaException):
+ msg_fmt = _('No live migration URI configured and no default available '
+ 'for "%(virt_type)s" hypervisor virtualization type.')
+
+
+class UnshelveException(NovaException):
+ msg_fmt = _("Error during unshelve instance %(instance_id)s: %(reason)s")
+
+
+class ImageVCPULimitsRangeExceeded(Invalid):
+ msg_fmt = _('Image vCPU topology limits (sockets=%(image_sockets)d, '
+ 'cores=%(image_cores)d, threads=%(image_threads)d) exceeds '
+ 'the limits of the flavor (sockets=%(flavor_sockets)d, '
+ 'cores=%(flavor_cores)d, threads=%(flavor_threads)d)')
+
+
+class ImageVCPUTopologyRangeExceeded(Invalid):
+ msg_fmt = _('Image vCPU topology (sockets=%(image_sockets)d, '
+ 'cores=%(image_cores)d, threads=%(image_threads)d) exceeds '
+ 'the limits of the flavor or image (sockets=%(max_sockets)d, '
+ 'cores=%(max_cores)d, threads=%(max_threads)d)')
+
+
+class ImageVCPULimitsRangeImpossible(Invalid):
+ msg_fmt = _("Requested vCPU limits %(sockets)d:%(cores)d:%(threads)d "
+ "are impossible to satisfy for vcpus count %(vcpus)d")
+
+
+class InvalidArchitectureName(Invalid):
+ msg_fmt = _("Architecture name '%(arch)s' is not recognised")
+
+
+class ImageNUMATopologyIncomplete(Invalid):
+ msg_fmt = _("CPU and memory allocation must be provided for all "
+ "NUMA nodes")
+
+
+class ImageNUMATopologyForbidden(Forbidden):
+ msg_fmt = _("Image property '%(name)s' is not permitted to override "
+ "NUMA configuration set against the flavor")
+
+
+class ImageNUMATopologyAsymmetric(Invalid):
+ msg_fmt = _("Instance CPUs and/or memory cannot be evenly distributed "
+ "across instance NUMA nodes. Explicit assignment of CPUs "
+ "and memory to nodes is required")
+
+
+class ImageNUMATopologyCPUOutOfRange(Invalid):
+ msg_fmt = _("CPU number %(cpunum)d is larger than max %(cpumax)d")
+
+
+class ImageNUMATopologyCPUDuplicates(Invalid):
+ msg_fmt = _("CPU number %(cpunum)d is assigned to two nodes")
+
+
+class ImageNUMATopologyCPUsUnassigned(Invalid):
+ msg_fmt = _("CPU number %(cpuset)s is not assigned to any node")
+
+
+class ImageNUMATopologyMemoryOutOfRange(Invalid):
+ msg_fmt = _("%(memsize)d MB of memory assigned, but expected "
+ "%(memtotal)d MB")
+
+
+class InvalidHostname(Invalid):
+ msg_fmt = _("Invalid characters in hostname '%(hostname)s'")
+
+
+class NumaTopologyNotFound(NotFound):
+ msg_fmt = _("Instance %(instance_uuid)s does not specify a NUMA topology")
+
+
+class MigrationContextNotFound(NotFound):
+ msg_fmt = _("Instance %(instance_uuid)s does not specify a migration "
+ "context.")
+
+
+class SocketPortRangeExhaustedException(NovaException):
+ msg_fmt = _("Not able to acquire a free port for %(host)s")
+
+
+class SocketPortInUseException(NovaException):
+ msg_fmt = _("Not able to bind %(host)s:%(port)d, %(error)s")
+
+
+class ImageSerialPortNumberInvalid(Invalid):
+ msg_fmt = _("Number of serial ports specified in flavor is invalid: "
+ "expected an integer, got '%(num_ports)s'")
+
+
+class ImageSerialPortNumberExceedFlavorValue(Invalid):
+ msg_fmt = _("Forbidden to exceed flavor value of number of serial "
+ "ports passed in image meta.")
+
+
+class SerialPortNumberLimitExceeded(Invalid):
+ msg_fmt = _("Maximum number of serial port exceeds %(allowed)d "
+ "for %(virt_type)s")
+
+
+class InvalidImageConfigDrive(Invalid):
+ msg_fmt = _("Image's config drive option '%(config_drive)s' is invalid")
+
+
+class InvalidHypervisorVirtType(Invalid):
+ msg_fmt = _("Hypervisor virtualization type '%(hv_type)s' is not "
+ "recognised")
+
+
+class InvalidVirtualMachineMode(Invalid):
+ msg_fmt = _("Virtual machine mode '%(vmmode)s' is not recognised")
+
+
+class InvalidToken(Invalid):
+ msg_fmt = _("The token '%(token)s' is invalid or has expired")
+
+
+class TokenInUse(Invalid):
+ msg_fmt = _("The generated token is invalid")
+
+
+class InvalidConnectionInfo(Invalid):
+ msg_fmt = _("Invalid Connection Info")
+
+
+class InstanceQuiesceNotSupported(Invalid):
+ msg_fmt = _('Quiescing is not supported in instance %(instance_id)s')
+
+
+class InstanceAgentNotEnabled(Invalid):
+ msg_fmt = _('Guest agent is not enabled for the instance')
+ safe = True
+
+
+class QemuGuestAgentNotEnabled(InstanceAgentNotEnabled):
+ msg_fmt = _('QEMU guest agent is not enabled')
+
+
+class SetAdminPasswdNotSupported(Invalid):
+ msg_fmt = _('Set admin password is not supported')
+ safe = True
+
+
+class MemoryPageSizeInvalid(Invalid):
+ msg_fmt = _("Invalid memory page size '%(pagesize)s'")
+
+
+class MemoryPageSizeForbidden(Invalid):
+ msg_fmt = _("Page size %(pagesize)s forbidden against '%(against)s'")
+
+
+class MemoryPageSizeNotSupported(Invalid):
+ msg_fmt = _("Page size %(pagesize)s is not supported by the host.")
+
+
+class CPUPinningNotSupported(Invalid):
+ msg_fmt = _("CPU pinning is not supported by the host: "
+ "%(reason)s")
+
+
+class CPUPinningInvalid(Invalid):
+ msg_fmt = _("CPU set to pin %(requested)s must be a subset of "
+ "free CPU set %(free)s")
+
+
+class CPUUnpinningInvalid(Invalid):
+ msg_fmt = _("CPU set to unpin %(requested)s must be a subset of "
+ "pinned CPU set %(pinned)s")
+
+
+class CPUPinningUnknown(Invalid):
+ msg_fmt = _("CPU set to pin %(requested)s must be a subset of "
+ "known CPU set %(cpuset)s")
+
+
+class CPUUnpinningUnknown(Invalid):
+ msg_fmt = _("CPU set to unpin %(requested)s must be a subset of "
+ "known CPU set %(cpuset)s")
+
+
+class ImageCPUPinningForbidden(Forbidden):
+ msg_fmt = _("Image property 'hw_cpu_policy' is not permitted to override "
+ "CPU pinning policy set against the flavor")
+
+
+class ImageCPUThreadPolicyForbidden(Forbidden):
+ msg_fmt = _("Image property 'hw_cpu_thread_policy' is not permitted to "
+ "override CPU thread pinning policy set against the flavor")
+
+
+class UnsupportedPolicyException(Invalid):
+ msg_fmt = _("ServerGroup policy is not supported: %(reason)s")
+
+
+class CellMappingNotFound(NotFound):
+ msg_fmt = _("Cell %(uuid)s has no mapping.")
+
+
+class NUMATopologyUnsupported(Invalid):
+ msg_fmt = _("Host does not support guests with NUMA topology set")
+
+
+class MemoryPagesUnsupported(Invalid):
+ msg_fmt = _("Host does not support guests with custom memory page sizes")
+
+
+class InvalidImageFormat(Invalid):
+ msg_fmt = _("Invalid image format '%(format)s'")
+
+
+class UnsupportedImageModel(Invalid):
+ msg_fmt = _("Image model '%(image)s' is not supported")
+
+
+class HostMappingNotFound(Invalid):
+ msg_fmt = _("Host '%(name)s' is not mapped to any cell")
+
+
+class RealtimeConfigurationInvalid(Invalid):
+ msg_fmt = _("Cannot set realtime policy in a non dedicated "
+ "cpu pinning policy")
+
+
+class CPUThreadPolicyConfigurationInvalid(Invalid):
+ msg_fmt = _("Cannot set cpu thread pinning policy in a non dedicated "
+ "cpu pinning policy")
+
+
+class RequestSpecNotFound(NotFound):
+ msg_fmt = _("RequestSpec not found for instance %(instance_uuid)s")
+
+
+class UEFINotSupported(Invalid):
+ msg_fmt = _("UEFI is not supported")
+
+
+class TriggerCrashDumpNotSupported(Invalid):
+ msg_fmt = _("Triggering crash dump is not supported")
+
+
+class UnsupportedHostCPUControlPolicy(Invalid):
+ msg_fmt = _("Requested CPU control policy not supported by host")
+
+
+class LibguestfsCannotReadKernel(Invalid):
+ msg_fmt = _("Libguestfs does not have permission to read host kernel.")
+
+
+class RealtimeMaskNotFoundOrInvalid(Invalid):
+ msg_fmt = _("Realtime policy needs vCPU(s) mask configured with at least "
+ "1 RT vCPU and 1 ordinary vCPU. See hw:cpu_realtime_mask "
+ "or hw_cpu_realtime_mask")
+
+
+class OsInfoNotFound(NotFound):
+ msg_fmt = _("No configuration information found for operating system "
+ "%(os_name)s")
+
+
+class BuildRequestNotFound(NotFound):
+ msg_fmt = _("BuildRequest not found for instance %(uuid)s")
+
+
+class AttachInterfaceNotSupported(Invalid):
+ msg_fmt = _("Attaching interfaces is not supported for "
+ "instance %(instance_uuid)s.")
+
+
+class InvalidReservedMemoryPagesOption(Invalid):
+ msg_fmt = _("The format of the option 'reserved_huge_pages' is invalid. "
+ "(found '%(conf)s') Please refer to the nova "
+ "config-reference.")
+
+
+# An exception with this name is used on both sides of the placement/
+# nova interaction.
+class ResourceProviderInUse(NovaException):
+ msg_fmt = _("Resource provider has allocations.")
+
+
+class ResourceProviderRetrievalFailed(NovaException):
+ msg_fmt = _("Failed to get resource provider with UUID %(uuid)s")
+
+
+class ResourceProviderAggregateRetrievalFailed(NovaException):
+ msg_fmt = _("Failed to get aggregates for resource provider with UUID"
+ " %(uuid)s")
+
+
+class ResourceProviderTraitRetrievalFailed(NovaException):
+ msg_fmt = _("Failed to get traits for resource provider with UUID"
+ " %(uuid)s")
+
+
+class ResourceProviderCreationFailed(NovaException):
+ msg_fmt = _("Failed to create resource provider %(name)s")
+
+
+class ResourceProviderDeletionFailed(NovaException):
+ msg_fmt = _("Failed to delete resource provider %(uuid)s")
+
+
+class ResourceProviderUpdateFailed(NovaException):
+ msg_fmt = _("Failed to update resource provider via URL %(url)s: "
+ "%(error)s")
+
+
+class ResourceProviderNotFound(NotFound):
+ msg_fmt = _("No such resource provider %(name_or_uuid)s.")
+
+
+class ResourceProviderSyncFailed(NovaException):
+ msg_fmt = _("Failed to synchronize the placement service with resource "
+ "provider information supplied by the compute host.")
+
+
+class PlacementAPIConnectFailure(NovaException):
+ msg_fmt = _("Unable to communicate with the Placement API.")
+
+
+class PlacementAPIConflict(NovaException):
+ """Any 409 error from placement APIs should use (a subclass of) this
+ exception.
+ """
+ msg_fmt = _("A conflict was encountered attempting to invoke the "
+ "placement API at URL %(url)s: %(error)s")
+
+
+class ResourceProviderUpdateConflict(PlacementAPIConflict):
+ """A 409 caused by generation mismatch from attempting to update an
+ existing provider record or its associated data (aggregates, traits, etc.).
+ """
+ msg_fmt = _("A conflict was encountered attempting to update resource "
+ "provider %(uuid)s (generation %(generation)d): %(error)s")
+
+
+class InvalidResourceClass(Invalid):
+ msg_fmt = _("Resource class '%(resource_class)s' invalid.")
+
+
+class InvalidResourceAmount(Invalid):
+ msg_fmt = _("Resource amounts must be integers. Received '%(amount)s'.")
+
+
+class InvalidInventory(Invalid):
+ msg_fmt = _("Inventory for '%(resource_class)s' on "
+ "resource provider '%(resource_provider)s' invalid.")
+
+
+# An exception with this name is used on both sides of the placement/
+# nova interaction.
+class InventoryInUse(InvalidInventory):
+ # NOTE(mriedem): This message cannot change without impacting the
+ # nova.scheduler.client.report._RE_INV_IN_USE regex.
+ msg_fmt = _("Inventory for '%(resource_classes)s' on "
+ "resource provider '%(resource_provider)s' in use.")
+
+
+class UnsupportedPointerModelRequested(Invalid):
+ msg_fmt = _("Pointer model '%(model)s' requested is not supported by "
+ "host.")
+
+
+class NotSupportedWithOption(Invalid):
+ msg_fmt = _("%(operation)s is not supported in conjunction with the "
+ "current %(option)s setting. Please refer to the nova "
+ "config-reference.")
+
+
+class Unauthorized(NovaException):
+ msg_fmt = _("Not authorized.")
+ code = 401
+
+
+class NeutronAdminCredentialConfigurationInvalid(Invalid):
+ msg_fmt = _("Networking client is experiencing an unauthorized exception.")
+
+
+class InvalidEmulatorThreadsPolicy(Invalid):
+ msg_fmt = _("CPU emulator threads option requested is invalid, "
+ "given: '%(requested)s', available: '%(available)s'.")
+
+
+class BadRequirementEmulatorThreadsPolicy(Invalid):
+ msg_fmt = _("An isolated CPU emulator threads option requires a dedicated "
+ "CPU policy option.")
+
+
+class InvalidNetworkNUMAAffinity(Invalid):
+ msg_fmt = _("Invalid NUMA network affinity configured: %(reason)s")
+
+
+class PowerVMAPIFailed(NovaException):
+ msg_fmt = _("PowerVM API failed to complete for instance=%(inst_name)s. "
+ "%(reason)s")
+
+
+class TraitRetrievalFailed(NovaException):
+ msg_fmt = _("Failed to retrieve traits from the placement API: %(error)s")
+
+
+class TraitCreationFailed(NovaException):
+ msg_fmt = _("Failed to create trait %(name)s: %(error)s")
+
+
+class CannotMigrateWithTargetHost(NovaException):
+ msg_fmt = _("Cannot migrate with target host. Retry without a host "
+ "specified.")
+
+
+class CannotMigrateToSameHost(NovaException):
+ msg_fmt = _("Cannot migrate to the host where the server exists.")
+
+
+class VirtDriverNotReady(NovaException):
+ msg_fmt = _("Virt driver is not ready.")
+
+
+class InstanceDiskMappingFailed(NovaException):
+ msg_fmt = _("Failed to map boot disk of instance %(instance_name)s to "
+ "the management partition from any Virtual I/O Server.")
+
+
+class NewMgmtMappingNotFoundException(NovaException):
+ msg_fmt = _("Failed to find newly-created mapping of storage element "
+ "%(stg_name)s from Virtual I/O Server %(vios_name)s to the "
+ "management partition.")
+
+
+class NoDiskDiscoveryException(NovaException):
+ msg_fmt = _("Having scanned SCSI bus %(bus)x on the management partition, "
+ "disk with UDID %(udid)s failed to appear after %(polls)d "
+ "polls over %(timeout)d seconds.")
+
+
+class UniqueDiskDiscoveryException(NovaException):
+ msg_fmt = _("Expected to find exactly one disk on the management "
+ "partition at %(path_pattern)s; found %(count)d.")
+
+
+class DeviceDeletionException(NovaException):
+ msg_fmt = _("Device %(devpath)s is still present on the management "
+ "partition after attempting to delete it. Polled %(polls)d "
+ "times over %(timeout)d seconds.")
+
+
+class OptRequiredIfOtherOptValue(NovaException):
+ msg_fmt = _("The %(then_opt)s option is required if %(if_opt)s is "
+ "specified as '%(if_value)s'.")
+
+
+class AllocationCreateFailed(NovaException):
+ msg_fmt = _('Failed to create allocations for instance %(instance)s '
+ 'against resource provider %(provider)s.')
+
+
+class AllocationUpdateFailed(NovaException):
+ msg_fmt = _('Failed to update allocations for consumer %(consumer_uuid)s. '
+ 'Error: %(error)s')
+
+
+class AllocationMoveFailed(NovaException):
+ msg_fmt = _('Failed to move allocations from consumer %(source_consumer)s '
+ 'to consumer %(target_consumer)s. '
+ 'Error: %(error)s')
+
+
+class AllocationDeleteFailed(NovaException):
+ msg_fmt = _('Failed to delete allocations for consumer %(consumer_uuid)s. '
+ 'Error: %(error)s')
+
+
+class TooManyComputesForHost(NovaException):
+ msg_fmt = _('Unexpected number of compute node records '
+ '(%(num_computes)d) found for host %(host)s. There should '
+ 'only be a one-to-one mapping.')
+
+
+class CertificateValidationFailed(NovaException):
+ msg_fmt = _("Image signature certificate validation failed for "
+ "certificate: %(cert_uuid)s. %(reason)s")
+
+
+class CertificateValidationNotYetAvailable(NovaException):
+ msg_fmt = _("Image signature certificate validation support is "
+ "not yet available.")
+ code = 409
+
+
+class InstanceRescueFailure(NovaException):
+ msg_fmt = _("Failed to move instance to rescue mode: %(reason)s")
+
+
+class InstanceUnRescueFailure(NovaException):
+ msg_fmt = _("Failed to unrescue instance: %(reason)s")
+
+
+class IronicAPIVersionNotAvailable(NovaException):
+ msg_fmt = _('Ironic API version %(version)s is not available.')
+
+
+class ZVMDriverException(NovaException):
+ msg_fmt = _("ZVM Driver has error: %(error)s")
+
+
+class ZVMConnectorError(ZVMDriverException):
+ msg_fmt = _("zVM Cloud Connector request failed: %(results)s")
+
+ def __init__(self, message=None, **kwargs):
+ """Exception for zVM ConnectorClient calls.
+
+ :param results: The object returned from ZVMConnector.send_request.
+ """
+ super(ZVMConnectorError, self).__init__(message=message, **kwargs)
+
+ results = kwargs.get('results', {})
+ self.overallRC = results.get('overallRC')
+ self.rc = results.get('rc')
+ self.rs = results.get('rs')
+ self.errmsg = results.get('errmsg')
+
+
+class NoResourceClass(NovaException):
+ msg_fmt = _("Resource class not found for Ironic node %(node)s.")
+
+
+class ResourceProviderAllocationRetrievalFailed(NovaException):
+ msg_fmt = _("Failed to retrieve allocations for resource provider "
+ "%(rp_uuid)s: %(error)s")
+
+
+class ConsumerAllocationRetrievalFailed(NovaException):
+ msg_fmt = _("Failed to retrieve allocations for consumer "
+ "%(consumer_uuid)s: %(error)s")
+
+
+class ReshapeFailed(NovaException):
+ msg_fmt = _("Resource provider inventory and allocation data migration "
+ "failed: %(error)s")
+
+
+class ReshapeNeeded(NovaException):
+ msg_fmt = _("Virt driver indicates that provider inventories need to be "
+ "moved.")
diff --git a/gosbs/i18n.py b/gosbs/i18n.py
new file mode 100644
index 0000000..f0a769d
--- /dev/null
+++ b/gosbs/i18n.py
@@ -0,0 +1,48 @@
+# Copyright 2014 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/i18n.py
+
+"""oslo.i18n integration module.
+
+See https://docs.openstack.org/oslo.i18n/latest/user/index.html .
+
+"""
+
+import oslo_i18n
+
+DOMAIN = 'gosbs'
+
+_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)
+
+# The primary translation function using the well-known name "_"
+_ = _translators.primary
+
+# Translators for log levels.
+#
+# The abbreviated names are meant to reflect the usual use of a short
+# name like '_'. The "L" is for "log" and the other letter comes from
+# the level.
+_LI = _translators.log_info
+_LW = _translators.log_warning
+_LE = _translators.log_error
+_LC = _translators.log_critical
+
+
+def translate(value, user_locale):
+ return oslo_i18n.translate(value, user_locale)
+
+
+def get_available_languages():
+ return oslo_i18n.get_available_languages(DOMAIN)
diff --git a/gosbs/manager.py b/gosbs/manager.py
new file mode 100644
index 0000000..b8e1dda
--- /dev/null
+++ b/gosbs/manager.py
@@ -0,0 +1,149 @@
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/manager.py
+
+"""Base Manager class.
+
+Managers are responsible for a certain aspect of the system. It is a logical
+grouping of code relating to a portion of the system. In general other
+components should be using the manager to make changes to the components that
+it is responsible for.
+
+For example, other components that need to deal with volumes in some way,
+should do so by calling methods on the VolumeManager instead of directly
+changing fields in the database. This allows us to keep all of the code
+relating to volumes in the same place.
+
+We have adopted a basic strategy of Smart managers and dumb data, which means
+rather than attaching methods to data objects, components should call manager
+methods that act on the data.
+
+Methods on managers that can be executed locally should be called directly. If
+a particular method must execute on a remote host, this should be done via rpc
+to the service that wraps the manager
+
+Managers should be responsible for most of the db access, and
+non-implementation specific data. Anything implementation specific that can't
+be generalized should be done by the Driver.
+
+In general, we prefer to have one manager with multiple drivers for different
+implementations, but sometimes it makes sense to have multiple managers. You
+can think of it this way: Abstract different overall strategies at the manager
+level(FlatNetwork vs VlanNetwork), and different implementations at the driver
+level(LinuxNetDriver vs CiscoNetDriver).
+
+Managers will often provide methods for initial setup of a host or periodic
+tasks to a wrapping service.
+
+This module provides Manager, a base class for managers.
+
+"""
+
+from oslo_service import periodic_task
+import six
+
+import gosbs.conf
+from gosbs.db import base
+#from gosbs import profiler
+from gosbs import rpc
+
+
+CONF = gosbs.conf.CONF
+
+
+class PeriodicTasks(periodic_task.PeriodicTasks):
+ def __init__(self):
+ super(PeriodicTasks, self).__init__(CONF)
+
+
+class ManagerMeta(profiler.get_traced_meta(), type(PeriodicTasks)):
+ """Metaclass to trace all children of a specific class.
+
+ This metaclass wraps every public method (not starting with _ or __)
+ of the class using it. All children classes of the class using ManagerMeta
+ will be profiled as well.
+
+ Adding this metaclass requires that the __trace_args__ attribute be added
+ to the class we want to modify. That attribute is a dictionary
+ with one mandatory key: "name". "name" defines the name
+ of the action to be traced (for example, wsgi, rpc, db).
+
+ The OSprofiler-based tracing, although, will only happen if profiler
+ instance was initiated somewhere before in the thread, that can only happen
+ if profiling is enabled in nova.conf and the API call to Nova API contained
+ specific headers.
+ """
+
+
+@six.add_metaclass(ManagerMeta)
+class Manager(base.Base, PeriodicTasks):
+ __trace_args__ = {"name": "rpc"}
+
+ def __init__(self, host=None, service_name='undefined'):
+ if not host:
+ host = CONF.host
+ self.host = host
+ self.backdoor_port = None
+ self.service_name = service_name
+ self.notifier = rpc.get_notifier(self.service_name, self.host)
+ self.additional_endpoints = []
+ super(Manager, self).__init__()
+
+ def periodic_tasks(self, context, raise_on_error=False):
+ """Tasks to be run at a periodic interval."""
+ return self.run_periodic_tasks(context, raise_on_error=raise_on_error)
+
+ def init_host(self):
+ """Hook to do additional manager initialization when one requests
+ the service be started. This is called before any service record
+ is created.
+
+ Child classes should override this method.
+ """
+ pass
+
+ def cleanup_host(self):
+ """Hook to do cleanup work when the service shuts down.
+
+ Child classes should override this method.
+ """
+ pass
+
+ def pre_start_hook(self):
+ """Hook to provide the manager the ability to do additional
+ start-up work before any RPC queues/consumers are created. This is
+ called after other initialization has succeeded and a service
+ record is created.
+
+ Child classes should override this method.
+ """
+ pass
+
+ def post_start_hook(self):
+ """Hook to provide the manager the ability to do additional
+ start-up work immediately after a service creates RPC consumers
+ and starts 'running'.
+
+ Child classes should override this method.
+ """
+ pass
+
+ def reset(self):
+ """Hook called on SIGHUP to signal the manager to re-read any
+ dynamic configuration or do any reconfiguration tasks.
+ """
+ pass
diff --git a/gosbs/middleware.py b/gosbs/middleware.py
new file mode 100644
index 0000000..a796014
--- /dev/null
+++ b/gosbs/middleware.py
@@ -0,0 +1,39 @@
+# Copyright 2016 Hewlett Packard Enterprise Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/middleware.py
+
+from oslo_middleware import cors
+
+
+def set_defaults():
+ """Update default configuration options for oslo.middleware."""
+ cors.set_defaults(
+ allow_headers=['X-Auth-Token',
+ 'X-Gosbs-Request-Id',
+ 'X-Identity-Status',
+ 'X-Roles',
+ 'X-Service-Catalog',
+ 'X-User-Id',
+ 'X-Tenant-Id'],
+ expose_headers=['X-Auth-Token',
+ 'X-Gosbs-Request-Id',
+ 'X-Subject-Token',
+ 'X-Service-Token'],
+ allow_methods=['GET',
+ 'PUT',
+ 'POST',
+ 'DELETE',
+ 'PATCH']
+ )
diff --git a/gosbs/objects/__init__.py b/gosbs/objects/__init__.py
new file mode 100644
index 0000000..e67fba4
--- /dev/null
+++ b/gosbs/objects/__init__.py
@@ -0,0 +1,52 @@
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/__init__.py
+
+# NOTE(comstud): You may scratch your head as you see code that imports
+# this module and then accesses attributes for objects such as Instance,
+# etc, yet you do not see these attributes in here. Never fear, there is
+# a little bit of magic. When objects are registered, an attribute is set
+# on this module automatically, pointing to the newest/latest version of
+# the object.
+
+
+def register_all():
+ # NOTE(danms): You must make sure your object gets imported in this
+ # function in order for it to be registered by services that may
+ # need to receive it via RPC.
+ __import__('gosbs.objects.build_iuse')
+ __import__('gosbs.objects.category')
+ __import__('gosbs.objects.category_metadata')
+ __import__('gosbs.objects.ebuild')
+ __import__('gosbs.objects.ebuild_metadata')
+ __import__('gosbs.objects.ebuild_iuse')
+ __import__('gosbs.objects.ebuild_keyword')
+ __import__('gosbs.objects.ebuild_restriction')
+ __import__('gosbs.objects.email')
+ __import__('gosbs.objects.keyword')
+ __import__('gosbs.objects.package')
+ __import__('gosbs.objects.package_metadata')
+ __import__('gosbs.objects.package_email')
+ __import__('gosbs.objects.project')
+ __import__('gosbs.objects.project_metadata')
+ __import__('gosbs.objects.project_build')
+ __import__('gosbs.objects.project_repo')
+ __import__('gosbs.objects.repo')
+ __import__('gosbs.objects.restriction')
+ __import__('gosbs.objects.task')
+ __import__('gosbs.objects.service')
+ __import__('gosbs.objects.service_repo')
+ __import__('gosbs.objects.use')
+ __import__('gosbs.objects.user')
diff --git a/gosbs/objects/base.py b/gosbs/objects/base.py
new file mode 100644
index 0000000..363269c
--- /dev/null
+++ b/gosbs/objects/base.py
@@ -0,0 +1,361 @@
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/base.py
+
+"""Nova common internal object model"""
+
+import contextlib
+import datetime
+import functools
+import traceback
+
+import netaddr
+import oslo_messaging as messaging
+from oslo_utils import versionutils
+from oslo_versionedobjects import base as ovoo_base
+from oslo_versionedobjects import exception as ovoo_exc
+import six
+
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import fields as obj_fields
+from gosbs import utils
+
+
+def get_attrname(name):
+ """Return the mangled name of the attribute's underlying storage."""
+ # FIXME(danms): This is just until we use o.vo's class properties
+ # and object base.
+ return '_obj_' + name
+
+
+class NovaObjectRegistry(ovoo_base.VersionedObjectRegistry):
+ notification_classes = []
+
+ def registration_hook(self, cls, index):
+ # NOTE(danms): This is called when an object is registered,
+ # and is responsible for maintaining nova.objects.$OBJECT
+ # as the highest-versioned implementation of a given object.
+ version = versionutils.convert_version_to_tuple(cls.VERSION)
+ if not hasattr(objects, cls.obj_name()):
+ setattr(objects, cls.obj_name(), cls)
+ else:
+ cur_version = versionutils.convert_version_to_tuple(
+ getattr(objects, cls.obj_name()).VERSION)
+ if version >= cur_version:
+ setattr(objects, cls.obj_name(), cls)
+
+ @classmethod
+ def register_notification(cls, notification_cls):
+ """Register a class as notification.
+ Use only to register concrete notification or payload classes,
+ do not register base classes intended for inheritance only.
+ """
+ cls.register_if(False)(notification_cls)
+ cls.notification_classes.append(notification_cls)
+ return notification_cls
+
+ @classmethod
+ def register_notification_objects(cls):
+ """Register previously decorated notification as normal ovos.
+ This is not intended for production use but only for testing and
+ document generation purposes.
+ """
+ for notification_cls in cls.notification_classes:
+ cls.register(notification_cls)
+
+
+remotable_classmethod = ovoo_base.remotable_classmethod
+remotable = ovoo_base.remotable
+obj_make_list = ovoo_base.obj_make_list
+NovaObjectDictCompat = ovoo_base.VersionedObjectDictCompat
+NovaTimestampObject = ovoo_base.TimestampedObject
+
+
+class NovaObject(ovoo_base.VersionedObject):
+ """Base class and object factory.
+
+ This forms the base of all objects that can be remoted or instantiated
+ via RPC. Simply defining a class that inherits from this base class
+ will make it remotely instantiatable. Objects should implement the
+ necessary "get" classmethod routines as well as "save" object methods
+ as appropriate.
+ """
+
+ OBJ_SERIAL_NAMESPACE = 'gosbs_object'
+ OBJ_PROJECT_NAMESPACE = 'gosbs'
+
+ # NOTE(ndipanov): This is nova-specific
+ @staticmethod
+ def should_migrate_data():
+ """A check that can be used to inhibit online migration behavior
+
+ This is usually used to check if all services that will be accessing
+ the db directly are ready for the new format.
+ """
+ raise NotImplementedError()
+
+ # NOTE(danms): This is nova-specific
+ @contextlib.contextmanager
+ def obj_alternate_context(self, context):
+ original_context = self._context
+ self._context = context
+ try:
+ yield
+ finally:
+ self._context = original_context
+
+ # NOTE(danms): This is nova-specific
+ @contextlib.contextmanager
+ def obj_as_admin(self):
+ """Context manager to make an object call as an admin.
+
+ This temporarily modifies the context embedded in an object to
+ be elevated() and restores it after the call completes. Example
+ usage:
+
+ with obj.obj_as_admin():
+ obj.save()
+
+ """
+ if self._context is None:
+ raise exception.OrphanedObjectError(method='obj_as_admin',
+ objtype=self.obj_name())
+
+ original_context = self._context
+ self._context = self._context.elevated()
+ try:
+ yield
+ finally:
+ self._context = original_context
+
+
+class NovaPersistentObject(object):
+ """Mixin class for Persistent objects.
+
+ This adds the fields that we use in common for most persistent objects.
+ """
+ fields = {
+ 'created_at': obj_fields.DateTimeField(nullable=True),
+ 'updated_at': obj_fields.DateTimeField(nullable=True),
+ 'deleted_at': obj_fields.DateTimeField(nullable=True),
+ 'deleted': obj_fields.BooleanField(default=False),
+ }
+
+class NovaPersistentObject2(object):
+ """Mixin class for Persistent objects.
+
+ This adds the fields that we use in common for most persistent objects.
+ """
+ fields = {
+ 'created_at': obj_fields.DateTimeField(nullable=True),
+ 'updated_at': obj_fields.DateTimeField(nullable=True),
+ }
+
+
+class ObjectListBase(ovoo_base.ObjectListBase):
+ # NOTE(danms): These are for transition to using the oslo
+ # base object and can be removed when we move to it.
+ @classmethod
+ def _obj_primitive_key(cls, field):
+ return 'gosbs_object.%s' % field
+
+ @classmethod
+ def _obj_primitive_field(cls, primitive, field,
+ default=obj_fields.UnspecifiedDefault):
+ key = cls._obj_primitive_key(field)
+ if default == obj_fields.UnspecifiedDefault:
+ return primitive[key]
+ else:
+ return primitive.get(key, default)
+
+
+class NovaObjectSerializer(messaging.NoOpSerializer):
+ """A NovaObject-aware Serializer.
+
+ This implements the Oslo Serializer interface and provides the
+ ability to serialize and deserialize NovaObject entities. Any service
+ that needs to accept or return NovaObjects as arguments or result values
+ should pass this to its RPCClient and RPCServer objects.
+ """
+
+ @property
+ def conductor(self):
+ if not hasattr(self, '_conductor'):
+ from gobs import conductor
+ self._conductor = conductor.API()
+ return self._conductor
+
+ def _process_object(self, context, objprim):
+ try:
+ objinst = NovaObject.obj_from_primitive(objprim, context=context)
+ except ovoo_exc.IncompatibleObjectVersion:
+ objver = objprim['gobs_object.version']
+ if objver.count('.') == 2:
+ # NOTE(danms): For our purposes, the .z part of the version
+ # should be safe to accept without requiring a backport
+ objprim['gobs_object.version'] = \
+ '.'.join(objver.split('.')[:2])
+ return self._process_object(context, objprim)
+ objname = objprim['gobs_object.name']
+ version_manifest = ovoo_base.obj_tree_get_versions(objname)
+ if objname in version_manifest:
+ objinst = self.conductor.object_backport_versions(
+ context, objprim, version_manifest)
+ else:
+ raise
+ return objinst
+
+ def _process_iterable(self, context, action_fn, values):
+ """Process an iterable, taking an action on each value.
+ :param:context: Request context
+ :param:action_fn: Action to take on each item in values
+ :param:values: Iterable container of things to take action on
+ :returns: A new container of the same type (except set) with
+ items from values having had action applied.
+ """
+ iterable = values.__class__
+ if issubclass(iterable, dict):
+ return iterable(**{k: action_fn(context, v)
+ for k, v in values.items()})
+ else:
+ # NOTE(danms, gibi) A set can't have an unhashable value inside,
+ # such as a dict. Convert the set to list, which is fine, since we
+ # can't send them over RPC anyway. We convert it to list as this
+ # way there will be no semantic change between the fake rpc driver
+ # used in functional test and a normal rpc driver.
+ if iterable == set:
+ iterable = list
+ return iterable([action_fn(context, value) for value in values])
+
+ def serialize_entity(self, context, entity):
+ if isinstance(entity, (tuple, list, set, dict)):
+ entity = self._process_iterable(context, self.serialize_entity,
+ entity)
+ elif (hasattr(entity, 'obj_to_primitive') and
+ callable(entity.obj_to_primitive)):
+ entity = entity.obj_to_primitive()
+ return entity
+
+ def deserialize_entity(self, context, entity):
+ if isinstance(entity, dict) and 'gobs_object.name' in entity:
+ entity = self._process_object(context, entity)
+ elif isinstance(entity, (tuple, list, set, dict)):
+ entity = self._process_iterable(context, self.deserialize_entity,
+ entity)
+ return entity
+
+
+def obj_to_primitive(obj):
+ """Recursively turn an object into a python primitive.
+
+ A NovaObject becomes a dict, and anything that implements ObjectListBase
+ becomes a list.
+ """
+ if isinstance(obj, ObjectListBase):
+ return [obj_to_primitive(x) for x in obj]
+ elif isinstance(obj, NovaObject):
+ result = {}
+ for key in obj.obj_fields:
+ if obj.obj_attr_is_set(key) or key in obj.obj_extra_fields:
+ result[key] = obj_to_primitive(getattr(obj, key))
+ return result
+ elif isinstance(obj, netaddr.IPAddress):
+ return str(obj)
+ elif isinstance(obj, netaddr.IPNetwork):
+ return str(obj)
+ else:
+ return obj
+
+
+def obj_make_dict_of_lists(context, list_cls, obj_list, item_key):
+ """Construct a dictionary of object lists, keyed by item_key.
+
+ :param:context: Request context
+ :param:list_cls: The ObjectListBase class
+ :param:obj_list: The list of objects to place in the dictionary
+ :param:item_key: The object attribute name to use as a dictionary key
+ """
+
+ obj_lists = {}
+ for obj in obj_list:
+ key = getattr(obj, item_key)
+ if key not in obj_lists:
+ obj_lists[key] = list_cls()
+ obj_lists[key].objects = []
+ obj_lists[key].objects.append(obj)
+ for key in obj_lists:
+ obj_lists[key]._context = context
+ obj_lists[key].obj_reset_changes()
+ return obj_lists
+
+
+def serialize_args(fn):
+ """Decorator that will do the arguments serialization before remoting."""
+ def wrapper(obj, *args, **kwargs):
+ args = [utils.strtime(arg) if isinstance(arg, datetime.datetime)
+ else arg for arg in args]
+ for k, v in kwargs.items():
+ if k == 'exc_val' and v:
+ kwargs[k] = six.text_type(v)
+ elif k == 'exc_tb' and v and not isinstance(v, six.string_types):
+ kwargs[k] = ''.join(traceback.format_tb(v))
+ elif isinstance(v, datetime.datetime):
+ kwargs[k] = utils.strtime(v)
+ if hasattr(fn, '__call__'):
+ return fn(obj, *args, **kwargs)
+ # NOTE(danms): We wrap a descriptor, so use that protocol
+ return fn.__get__(None, obj)(*args, **kwargs)
+
+ # NOTE(danms): Make this discoverable
+ wrapper.remotable = getattr(fn, 'remotable', False)
+ wrapper.original_fn = fn
+ return (functools.wraps(fn)(wrapper) if hasattr(fn, '__call__')
+ else classmethod(wrapper))
+
+
+def obj_equal_prims(obj_1, obj_2, ignore=None):
+ """Compare two primitives for equivalence ignoring some keys.
+
+ This operation tests the primitives of two objects for equivalence.
+ Object primitives may contain a list identifying fields that have been
+ changed - this is ignored in the comparison. The ignore parameter lists
+ any other keys to be ignored.
+
+ :param:obj1: The first object in the comparison
+ :param:obj2: The second object in the comparison
+ :param:ignore: A list of fields to ignore
+ :returns: True if the primitives are equal ignoring changes
+ and specified fields, otherwise False.
+ """
+
+ def _strip(prim, keys):
+ if isinstance(prim, dict):
+ for k in keys:
+ prim.pop(k, None)
+ for v in prim.values():
+ _strip(v, keys)
+ if isinstance(prim, list):
+ for v in prim:
+ _strip(v, keys)
+ return prim
+
+ if ignore is not None:
+ keys = ['gosbs_object.changes'] + ignore
+ else:
+ keys = ['gosbs_object.changes']
+ prim_1 = _strip(obj_1.obj_to_primitive(), keys)
+ prim_2 = _strip(obj_2.obj_to_primitive(), keys)
+ return prim_1 == prim_2
diff --git a/gosbs/objects/build_iuse.py b/gosbs/objects/build_iuse.py
new file mode 100644
index 0000000..4aa5342
--- /dev/null
+++ b/gosbs/objects/build_iuse.py
@@ -0,0 +1,280 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it fit what we need.
+# I need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+PROJECT_STATUS = ['failed', 'completed', 'in-progress', 'waiting']
+
+def _dict_with_extra_specs(build_iuse_model):
+ extra_specs = {}
+ return dict(build_iuse_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _build_iuse_create(context, values):
+ db_build_iuse = models.BuildsIUses()
+ db_build_iuse.update(values)
+
+ try:
+ db_build_iuse.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'build_iuseid' in e.columns:
+ raise exception.ImagesIdExists(build_iuse_id=values['build_iuseid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_build_iuse)
+
+
+@db_api.main_context_manager.writer
+def _build_iuse_destroy(context, build_iuse_id=None, build_iuseid=None):
+ query = context.session.query(models.EbuildsIUses)
+
+ if build_iuse_id is not None:
+ query.filter(models.BuildsIUses.id == build_iuse_id).delete()
+ else:
+ query.filter(models.BuildsIUses.id == build_iuseid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class BuildIUse(base.NovaObject, base.NovaObjectDictCompat):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'id': fields.IntegerField(),
+ 'build_uuid': fields.UUIDField(),
+ 'use_id': fields.IntegerField(),
+ 'status' : fields.BooleanField(),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(BuildIUse, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_build_iuses = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(BuildIUse, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, build_iuse, db_build_iuse, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ build_iuse._context = context
+ for name, field in build_iuse.fields.items():
+ value = db_build_iuse[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ build_iuse[name] = value
+
+ build_iuse.obj_reset_changes()
+ return build_iuse
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _build_iuse_get_query_from_db(context):
+ query = context.session.query(models.BuildsIUses)
+ return query
+
+ @staticmethod
+ @require_context
+ def _build_iuse_get_from_db(context, id):
+ """Returns a dict describing specific build_iuses."""
+ result = BuildIUse._build_iuse_get_query_from_db(context).\
+ filter_by(id=id).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(build_iuse_id=id)
+ return result
+
+ @staticmethod
+ @require_context
+ def _build_iuse_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = BuildIUse._build_iuse_get_query_from_db(context).\
+ filter_by(name=name).\
+ first()
+ if not result:
+ raise exception.FlavorNotFoundByName(build_iuses_name=name)
+ return _dict_with_extra_specs(result)
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(BuildIUse, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(BuildIUse, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_id(cls, context, id):
+ db_build_iuse = cls._build_iuse_get_from_db(context, id)
+ return cls._from_db_object(context, cls(context), db_build_iuse,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name):
+ db_build_iuse = cls._build_iuse_get_by_name_from_db(context, name)
+ return cls._from_db_object(context, cls(context), db_build_iuse,
+ expected_attrs=[])
+
+ @staticmethod
+ def _build_iuse_create(context, updates):
+ return _build_iuse_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_build_iuse = self._build_iuse_create(context, updates)
+ self._from_db_object(context, self, db_build_iuse)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a build_iuses.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_build_iuse = context.session.query(models.BuildsIUses).\
+ filter_by(id=self.id).first()
+ if not db_build_iuse:
+ raise exception.ImagesNotFound(build_iuse_id=self.id)
+ db_build_iuse.update(values)
+ db_build_iuse.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_build_iuse)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _build_iuse_destroy(context, build_iuse_id=None, build_iuseid=None):
+ _build_iuse_destroy(context, build_iuse_id=build_iuse_id, build_iuseid=build_iuseid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a build_iuses
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a build_iuses object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._build_iuse_destroy(context, build_iuse_id=self.id)
+ else:
+ self._build_iuse_destroy(context, build_iuseid=self.build_iuseid)
+ #self._from_db_object(context, self, db_build_iuse)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+
+ db_build_iuse = BuildIUses._build_iuse_get_query_from_db(context)
+
+ if 'status' in filters:
+ db_build_iuse = db_build_iuse.filter(
+ models.BuildsIUses.status == filters['status']).first()
+ return cls._from_db_object(context, cls(context), db_build_iuse,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _build_iuse_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all build_iusess.
+ """
+ filters = filters or {}
+
+ query = BuildIUse._build_iuse_get_query_from_db(context)
+
+ if 'status' in filters:
+ query = query.filter(
+ models.BuildsIUses.status == filters['status'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = BuildIUse._build_iuse_get_query_from_db(context).\
+ filter_by(id=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.BuildsIUses,
+ limit,
+ [sort_key, 'id'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class BuildIUseList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('BuildIUse'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='id', sort_dir='asc', limit=None, marker=None):
+ db_build_iuses = _build_iuse_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.build_iuse.BuildIUse,
+ db_build_iuses,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.BuildsIUses).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_build_iuse = context.session.query(models.BuildsIUses).filter_by(auto=True)
+ db_build_iuse.update(values)
diff --git a/gosbs/objects/category.py b/gosbs/objects/category.py
new file mode 100644
index 0000000..c7659c5
--- /dev/null
+++ b/gosbs/objects/category.py
@@ -0,0 +1,278 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it fit what we need.
+# I need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+CATEGORY_STATUS = ['failed', 'completed', 'in-progress', 'waiting']
+
+def _dict_with_extra_specs(category_model):
+ extra_specs = {}
+ return dict(category_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _category_create(context, values):
+ db_category = models.Categories()
+ db_category.update(values)
+
+ try:
+ db_category.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'categoryid' in e.columns:
+ raise exception.ImagesIdExists(category_id=values['categoryid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_category)
+
+
+@db_api.main_context_manager.writer
+def _category_destroy(context, category_id=None, categoryid=None):
+ query = context.session.query(models.Categories)
+
+ if category_id is not None:
+ query.filter(models.Categories.uuid == category_id).delete()
+ else:
+ query.filter(models.Categories.uuid == categoryid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class Category(base.NovaObject, base.NovaObjectDictCompat, base.NovaPersistentObject):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'uuid': fields.UUIDField(),
+ 'name': fields.StringField(),
+ 'status' : fields.EnumField(valid_values=CATEGORY_STATUS),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(Category, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_categorys = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(Project, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, category, db_category, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ category._context = context
+ for name, field in category.fields.items():
+ value = db_category[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ category[name] = value
+
+ category.obj_reset_changes()
+ return category
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _category_get_query_from_db(context):
+ query = context.session.query(models.Categories)
+ return query
+
+ @staticmethod
+ @require_context
+ def _category_get_from_db(context, uuid):
+ """Returns a dict describing specific categorys."""
+ result = Category._category_get_query_from_db(context).\
+ filter_by(uuid=uuid).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(category_uuid=uuid)
+ return result
+
+ @staticmethod
+ @require_context
+ def _category_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = Category._category_get_query_from_db(context).\
+ filter_by(name=name).\
+ first()
+ return result
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(Category, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(Category, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_uuid(cls, context, uuid):
+ db_category = cls._category_get_from_db(context, uuid)
+ return cls._from_db_object(context, cls(context), db_category,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name):
+ db_category = cls._category_get_by_name_from_db(context, name)
+ if not db_category:
+ return None
+ return cls._from_db_object(context, cls(context), db_category,
+ expected_attrs=[])
+
+ @staticmethod
+ def _category_create(context, updates):
+ return _category_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_category = self._category_create(context, updates)
+ self._from_db_object(context, self, db_category)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a categorys.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_category = context.session.query(models.Categories).\
+ filter_by(uuid=self.uuid).first()
+ if not db_category:
+ raise exception.ImagesNotFound(category_id=self.uuid)
+ db_category.update(values)
+ db_category.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_category)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _category_destroy(context, category_id=None, categoryid=None):
+ _category_destroy(context, category_id=category_id, categoryid=categoryid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a categorys
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a categorys object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._category_destroy(context, category_id=self.uuid)
+ else:
+ self._category_destroy(context, categoryid=self.categoryid)
+ #self._from_db_object(context, self, db_category)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+ db_category = Category._category_get_query_from_db(context)
+
+ if 'status' in filters:
+ db_category = db_category.filter(
+ models.Categories.status == filters['status']).first()
+ return cls._from_db_object(context, cls(context), db_category,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _category_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all categoryss.
+ """
+ filters = filters or {}
+
+ query = Category._category_get_query_from_db(context)
+
+ if 'status' in filters:
+ query = query.filter(
+ models.Categories.status == filters['status'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = Category._category_get_query_from_db(context).\
+ filter_by(uuid=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.Categories,
+ limit,
+ [sort_key, 'uuid'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class CategoryList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('Category'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='uuid', sort_dir='asc', limit=None, marker=None):
+ db_categorys = _category_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.category.Category,
+ db_categorys,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.Categories).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_category = context.session.query(models.Categories).filter_by(auto=True)
+ db_category.update(values)
diff --git a/gosbs/objects/category_metadata.py b/gosbs/objects/category_metadata.py
new file mode 100644
index 0000000..76eff64
--- /dev/null
+++ b/gosbs/objects/category_metadata.py
@@ -0,0 +1,278 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it fit what we need.
+# I need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+
+def _dict_with_extra_specs(category_metadata_model):
+ extra_specs = {}
+ return dict(category_metadata_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _category_metadata_create(context, values):
+ db_category_metadata = models.CategoriesMetadata()
+ db_category_metadata.update(values)
+
+ try:
+ db_category_metadata.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'category_metadataid' in e.columns:
+ raise exception.ImagesIdExists(category_metadata_id=values['category_metadataid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_category_metadata)
+
+
+@db_api.main_context_manager.writer
+def _category_metadata_destroy(context, category_metadata_id=None, category_metadataid=None):
+ query = context.session.query(models.CategoriesMetadata)
+
+ if category_metadata_id is not None:
+ query.filter(models.CategoriesMetadata.id == category_metadata_id).delete()
+ else:
+ query.filter(models.CategoriesMetadata.id == category_metadataid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class CategoryMetadata(base.NovaObject, base.NovaObjectDictCompat):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'id': fields.IntegerField(),
+ 'category_uuid': fields.UUIDField(),
+ 'description' : fields.StringField(),
+ 'checksum': fields.StringField(),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(CategoryMetadata, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_category_metadatas = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(CategoryMetadata, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, category_metadata, db_category_metadata, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ category_metadata._context = context
+ for name, field in category_metadata.fields.items():
+ value = db_category_metadata[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ category_metadata[name] = value
+
+ category_metadata.obj_reset_changes()
+ return category_metadata
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _category_metadata_get_query_from_db(context):
+ query = context.session.query(models.CategoriesMetadata)
+ return query
+
+ @staticmethod
+ @require_context
+ def _category_metadata_get_from_db(context, uuid):
+ """Returns a dict describing specific category_metadatas."""
+ result = CategoryMetadata._category_metadata_get_query_from_db(context).\
+ filter_by(category_uuid=uuid).\
+ first()
+ return result
+
+ @staticmethod
+ @require_context
+ def _category_metadata_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = CategoryMetadata._category_metadata_get_query_from_db(context).\
+ filter_by(name=name).\
+ first()
+ if not result:
+ raise exception.FlavorNotFoundByName(category_metadatas_name=name)
+ return _dict_with_extra_specs(result)
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(CategoryMetadata, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(CategoryMetadata, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_uuid(cls, context, uuid):
+ db_category_metadata = cls._category_metadata_get_from_db(context, uuid)
+ if not db_category_metadata:
+ return None
+ return cls._from_db_object(context, cls(context), db_category_metadata,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name):
+ db_category_metadata = cls._category_metadata_get_by_name_from_db(context, name)
+ return cls._from_db_object(context, cls(context), db_category_metadata,
+ expected_attrs=[])
+
+ @staticmethod
+ def _category_metadata_create(context, updates):
+ return _category_metadata_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_category_metadata = self._category_metadata_create(context, updates)
+ self._from_db_object(context, self, db_category_metadata)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a category_metadatas.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_category_metadata = context.session.query(models.CategoriesMetadata).\
+ filter_by(id=self.id).first()
+ if not db_category_metadata:
+ raise exception.ImagesNotFound(category_metadata_id=self.id)
+ db_category_metadata.update(values)
+ db_category_metadata.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_category_metadata)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _category_metadata_destroy(context, category_metadata_id=None, category_metadataid=None):
+ _category_metadata_destroy(context, category_metadata_id=category_metadata_id, category_metadataid=category_metadataid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a category_metadatas
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a category_metadatas object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._category_metadata_destroy(context, category_metadata_id=self.id)
+ else:
+ self._category_metadata_destroy(context, category_metadataid=self.category_metadataid)
+ #self._from_db_object(context, self, db_category_metadata)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+ db_category_metadata = CategoryMetadata._category_metadata_get_query_from_db(context)
+
+ if 'status' in filters:
+ db_category_metadata = db_category_metadata.filter(
+ models.CategoriesMetadata.status == filters['status']).first()
+ return cls._from_db_object(context, cls(context), db_category_metadata,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _category_metadata_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all category_metadatass.
+ """
+ filters = filters or {}
+
+ query = CategoryMetadata._category_metadata_get_query_from_db(context)
+
+ if 'status' in filters:
+ query = query.filter(
+ models.CategoriesMetadata.status == filters['status'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = CategoryMetadata._category_metadata_get_query_from_db(context).\
+ filter_by(id=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.CategoriesMetadata,
+ limit,
+ [sort_key, 'id'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class CategoryMetadataList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('CategoryMetadata'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='id', sort_dir='asc', limit=None, marker=None):
+ db_category_metadatas = _category_metadata_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.category_metadata.CategoryMetadata,
+ db_category_metadatas,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.CategoriesMetadata).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_category_metadata = context.session.query(models.CategoriesMetadata).filter_by(auto=True)
+ db_category_metadata.update(values)
diff --git a/gosbs/objects/ebuild.py b/gosbs/objects/ebuild.py
new file mode 100644
index 0000000..4be3c64
--- /dev/null
+++ b/gosbs/objects/ebuild.py
@@ -0,0 +1,288 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it fit what we need.
+# I need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+PROJECT_STATUS = ['failed', 'completed', 'in-progress', 'waiting']
+
+def _dict_with_extra_specs(ebuild_model):
+ extra_specs = {}
+ return dict(ebuild_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _ebuild_create(context, values):
+ db_ebuild = models.Ebuilds()
+ db_ebuild.update(values)
+
+ try:
+ db_ebuild.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'ebuildid' in e.columns:
+ raise exception.ImagesIdExists(ebuild_id=values['ebuildid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_ebuild)
+
+
+@db_api.main_context_manager.writer
+def _ebuild_destroy(context, ebuild_id=None, ebuildid=None):
+ query = context.session.query(models.Ebuilds)
+
+ if ebuild_id is not None:
+ query.filter(models.Ebuilds.uuid == ebuild_id).delete()
+ else:
+ query.filter(models.Ebuilds.uuid == ebuildid).delete()
+
+
+@base.NovaObjectRegistry.register
+class Ebuild(base.NovaObject, base.NovaObjectDictCompat, base.NovaPersistentObject):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'uuid': fields.UUIDField(),
+ 'package_uuid': fields.UUIDField(),
+ 'version' : fields.StringField(nullable=True),
+ 'checksum': fields.StringField(nullable=True),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(Ebuild, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_ebuilds = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(Ebuild, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, ebuild, db_ebuild, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ ebuild._context = context
+ for name, field in ebuild.fields.items():
+ value = db_ebuild[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ ebuild[name] = value
+
+ ebuild.obj_reset_changes()
+ return ebuild
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _ebuild_get_query_from_db(context):
+ query = context.session.query(models.Ebuilds)
+ return query
+
+ @staticmethod
+ @require_context
+ def _ebuild_get_from_db(context, uuid):
+ """Returns a dict describing specific ebuilds."""
+ result = Ebuild._ebuild_get_query_from_db(context).\
+ filter_by(uuid=uuid).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(ebuild_id=uuid)
+ return result
+
+ @staticmethod
+ @require_context
+ def _ebuild_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = Ebuild._ebuild_get_query_from_db(context).\
+ filter_by(version=name).\
+ first()
+ if not result:
+ return None
+ return result
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(Ebuild, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(Ebuild, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_uuid(cls, context, uuid):
+ db_ebuild = cls._ebuild_get_from_db(context, uuid)
+ return cls._from_db_object(context, cls(context), db_ebuild,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, version, filters=None):
+ filters = filters or {}
+ db_ebuild = Ebuild._ebuild_get_query_from_db(context)
+ db_ebuild = db_ebuild.filter_by(version=version)
+ if 'deleted' in filters:
+ db_ebuild = db_ebuild.filter(
+ models.Ebuilds.deleted == filters['deleted'])
+ if 'package_uuid' in filters:
+ db_ebuild = db_ebuild.filter(
+ models.Ebuilds.package_uuid == filters['package_uuid'])
+ db_ebuild = db_ebuild.first()
+ if not db_ebuild:
+ return None
+ return cls._from_db_object(context, cls(context), db_ebuild,
+ expected_attrs=[])
+
+ @staticmethod
+ def _ebuild_create(context, updates):
+ return _ebuild_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_ebuild = self._ebuild_create(context, updates)
+ self._from_db_object(context, self, db_ebuild)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a ebuilds.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_ebuild = context.session.query(models.Ebuilds).\
+ filter_by(uuid=self.uuid).first()
+ if not db_ebuild:
+ raise exception.ImagesNotFound(ebuild_id=self.uuid)
+ db_ebuild.update(values)
+ db_ebuild.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_ebuild)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _ebuild_destroy(context, ebuild_id=None, ebuildid=None):
+ _ebuild_destroy(context, ebuild_id=ebuild_id, ebuildid=ebuildid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a ebuilds
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a ebuilds object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'uuid' in self:
+ self._ebuild_destroy(context, ebuild_id=self.uuid)
+ else:
+ self._ebuild_destroy(context, ebuildid=self.ebuildid)
+ #self._from_db_object(context, self, db_ebuild)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+ db_ebuild = Ebuild._ebuild_get_query_from_db(context)
+
+ return cls._from_db_object(context, cls(context), db_ebuild,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _ebuild_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all ebuildss.
+ """
+ filters = filters or {}
+
+ query = Ebuild._ebuild_get_query_from_db(context)
+
+ if 'deleted' in filters:
+ query = query.filter(
+ models.Ebuilds.deleted == filters['deleted'])
+ if 'package_uuid' in filters:
+ query = query.filter(
+ models.Ebuilds.package_uuid == filters['package_uuid'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = Ebuild._ebuild_get_query_from_db(context).\
+ filter_by(uuid=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.Ebuilds,
+ limit,
+ [sort_key, 'uuid'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class EbuildList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('Ebuild'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='uuid', sort_dir='asc', limit=None, marker=None):
+ db_ebuilds = _ebuild_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.ebuild.Ebuild,
+ db_ebuilds,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.Ebuilds).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_ebuild = context.session.query(models.Ebuilds).filter_by(auto=True)
+ db_ebuild.update(values)
diff --git a/gosbs/objects/ebuild_iuse.py b/gosbs/objects/ebuild_iuse.py
new file mode 100644
index 0000000..6a8c568
--- /dev/null
+++ b/gosbs/objects/ebuild_iuse.py
@@ -0,0 +1,280 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it fit what we need.
+# I need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+PROJECT_STATUS = ['failed', 'completed', 'in-progress', 'waiting']
+
+def _dict_with_extra_specs(ebuild_iuse_model):
+ extra_specs = {}
+ return dict(ebuild_iuse_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _ebuild_iuse_create(context, values):
+ db_ebuild_iuse = models.EbuildsIUses()
+ db_ebuild_iuse.update(values)
+
+ try:
+ db_ebuild_iuse.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'ebuild_iuseid' in e.columns:
+ raise exception.ImagesIdExists(ebuild_iuse_id=values['ebuild_iuseid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_ebuild_iuse)
+
+
+@db_api.main_context_manager.writer
+def _ebuild_iuse_destroy(context, ebuild_iuse_id=None, ebuild_iuseid=None):
+ query = context.session.query(models.EbuildsIUses)
+
+ if ebuild_iuse_id is not None:
+ query.filter(models.EbuildsIUses.id == ebuild_iuse_id).delete()
+ else:
+ query.filter(models.EbuildsIUses.id == ebuild_iuseid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class EbuildIUse(base.NovaObject, base.NovaObjectDictCompat):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'id': fields.IntegerField(),
+ 'ebuild_uuid': fields.UUIDField(),
+ 'use_id': fields.IntegerField(),
+ 'status' : fields.BooleanField(),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(EbuildIUse, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_ebuild_iuses = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(EbuildIUse, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, ebuild_iuse, db_ebuild_iuse, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ ebuild_iuse._context = context
+ for name, field in ebuild_iuse.fields.items():
+ value = db_ebuild_iuse[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ ebuild_iuse[name] = value
+
+ ebuild_iuse.obj_reset_changes()
+ return ebuild_iuse
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _ebuild_iuse_get_query_from_db(context):
+ query = context.session.query(models.EbuildsIUses)
+ return query
+
+ @staticmethod
+ @require_context
+ def _ebuild_iuse_get_from_db(context, id):
+ """Returns a dict describing specific ebuild_iuses."""
+ result = EbuildIUse._ebuild_iuse_get_query_from_db(context).\
+ filter_by(id=id).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(ebuild_iuse_id=id)
+ return result
+
+ @staticmethod
+ @require_context
+ def _ebuild_iuse_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = EbuildIUse._ebuild_iuse_get_query_from_db(context).\
+ filter_by(name=name).\
+ first()
+ if not result:
+ raise exception.FlavorNotFoundByName(ebuild_iuses_name=name)
+ return _dict_with_extra_specs(result)
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(EbuildIUse, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(EbuildIUse, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_id(cls, context, id):
+ db_ebuild_iuse = cls._ebuild_iuse_get_from_db(context, id)
+ return cls._from_db_object(context, cls(context), db_ebuild_iuse,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name):
+ db_ebuild_iuse = cls._ebuild_iuse_get_by_name_from_db(context, name)
+ return cls._from_db_object(context, cls(context), db_ebuild_iuse,
+ expected_attrs=[])
+
+ @staticmethod
+ def _ebuild_iuse_create(context, updates):
+ return _ebuild_iuse_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_ebuild_iuse = self._ebuild_iuse_create(context, updates)
+ self._from_db_object(context, self, db_ebuild_iuse)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a ebuild_iuses.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_ebuild_iuse = context.session.query(models.EbuildsIUses).\
+ filter_by(id=self.id).first()
+ if not db_ebuild_iuse:
+ raise exception.ImagesNotFound(ebuild_iuse_id=self.id)
+ db_ebuild_iuse.update(values)
+ db_ebuild_iuse.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_ebuild_iuse)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _ebuild_iuse_destroy(context, ebuild_iuse_id=None, ebuild_iuseid=None):
+ _ebuild_iuse_destroy(context, ebuild_iuse_id=ebuild_iuse_id, ebuild_iuseid=ebuild_iuseid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a ebuild_iuses
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a ebuild_iuses object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._ebuild_iuse_destroy(context, ebuild_iuse_id=self.id)
+ else:
+ self._ebuild_iuse_destroy(context, ebuild_iuseid=self.ebuild_iuseid)
+ #self._from_db_object(context, self, db_ebuild_iuse)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+ print('foo')
+ db_ebuild_iuse = EbuildIUses._ebuild_iuse_get_query_from_db(context)
+
+ if 'status' in filters:
+ db_ebuild_iuse = db_ebuild_iuse.filter(
+ models.EbuildsIUses.status == filters['status']).first()
+ return cls._from_db_object(context, cls(context), db_ebuild_iuse,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _ebuild_iuse_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all ebuild_iusess.
+ """
+ filters = filters or {}
+
+ query = EbuildIUse._ebuild_iuse_get_query_from_db(context)
+
+ if 'status' in filters:
+ query = query.filter(
+ models.EbuildsIUses.status == filters['status'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = EbuildIUse._ebuild_iuse_get_query_from_db(context).\
+ filter_by(id=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.EbuildsIUses,
+ limit,
+ [sort_key, 'id'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class EbuildIUseList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('EbuildIUse'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='id', sort_dir='asc', limit=None, marker=None):
+ db_ebuild_iuses = _ebuild_iuse_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.ebuild_iuse.EbuildIUse,
+ db_ebuild_iuses,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.EbuildsIUses).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_ebuild_iuse = context.session.query(models.EbuildsIUses).filter_by(auto=True)
+ db_ebuild_iuse.update(values)
diff --git a/gosbs/objects/ebuild_keyword.py b/gosbs/objects/ebuild_keyword.py
new file mode 100644
index 0000000..dfde6e7
--- /dev/null
+++ b/gosbs/objects/ebuild_keyword.py
@@ -0,0 +1,280 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it fit what we need.
+# I need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+KEYWORD_STATUS = ['stable','unstable','negative']
+
+def _dict_with_extra_specs(ebuild_keyword_model):
+ extra_specs = {}
+ return dict(ebuild_keyword_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _ebuild_keyword_create(context, values):
+ db_ebuild_keyword = models.EbuildsKeywords()
+ db_ebuild_keyword.update(values)
+
+ try:
+ db_ebuild_keyword.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'ebuild_keywordid' in e.columns:
+ raise exception.ImagesIdExists(ebuild_keyword_id=values['ebuild_keywordid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_ebuild_keyword)
+
+
+@db_api.main_context_manager.writer
+def _ebuild_keyword_destroy(context, ebuild_keyword_id=None, ebuild_keywordid=None):
+ query = context.session.query(models.EbuildsKeywords)
+
+ if ebuild_keyword_id is not None:
+ query.filter(models.EbuildsKeywords.id == ebuild_keyword_id).delete()
+ else:
+ query.filter(models.EbuildsKeywords.id == ebuild_keywordid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class EbuildKeyword(base.NovaObject, base.NovaObjectDictCompat):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'id': fields.IntegerField(),
+ 'ebuild_uuid': fields.UUIDField(),
+ 'keyword_id': fields.IntegerField(),
+ 'status' : fields.EnumField(valid_values=KEYWORD_STATUS),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(EbuildKeyword, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_ebuild_keywords = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(EbuildKeyword, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, ebuild_keyword, db_ebuild_keyword, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ ebuild_keyword._context = context
+ for name, field in ebuild_keyword.fields.items():
+ value = db_ebuild_keyword[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ ebuild_keyword[name] = value
+
+ ebuild_keyword.obj_reset_changes()
+ return ebuild_keyword
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _ebuild_keyword_get_query_from_db(context):
+ query = context.session.query(models.EbuildsKeywords)
+ return query
+
+ @staticmethod
+ @require_context
+ def _ebuild_keyword_get_from_db(context, id):
+ """Returns a dict describing specific ebuild_keywords."""
+ result = EbuildKeyword._ebuild_keyword_get_query_from_db(context).\
+ filter_by(id=id).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(ebuild_keyword_id=id)
+ return result
+
+ @staticmethod
+ @require_context
+ def _ebuild_keyword_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = EbuildKeyword._ebuild_keyword_get_query_from_db(context).\
+ filter_by(name=name).\
+ first()
+ if not result:
+ raise exception.FlavorNotFoundByName(ebuild_keywords_name=name)
+ return _dict_with_extra_specs(result)
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(EbuildKeyword, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(EbuildKeyword, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_id(cls, context, id):
+ db_ebuild_keyword = cls._ebuild_keyword_get_from_db(context, id)
+ return cls._from_db_object(context, cls(context), db_ebuild_keyword,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name):
+ db_ebuild_keyword = cls._ebuild_keyword_get_by_name_from_db(context, name)
+ return cls._from_db_object(context, cls(context), db_ebuild_keyword,
+ expected_attrs=[])
+
+ @staticmethod
+ def _ebuild_keyword_create(context, updates):
+ return _ebuild_keyword_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_ebuild_keyword = self._ebuild_keyword_create(context, updates)
+ self._from_db_object(context, self, db_ebuild_keyword)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a ebuild_keywords.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_ebuild_keyword = context.session.query(models.EbuildsKeywords).\
+ filter_by(id=self.id).first()
+ if not db_ebuild_keyword:
+ raise exception.ImagesNotFound(ebuild_keyword_id=self.id)
+ db_ebuild_keyword.update(values)
+ db_ebuild_keyword.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_ebuild_keyword)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _ebuild_keyword_destroy(context, ebuild_keyword_id=None, ebuild_keywordid=None):
+ _ebuild_keyword_destroy(context, ebuild_keyword_id=ebuild_keyword_id, ebuild_keywordid=ebuild_keywordid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a ebuild_keywords
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a ebuild_keywords object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._ebuild_keyword_destroy(context, ebuild_keyword_id=self.id)
+ else:
+ self._ebuild_keyword_destroy(context, ebuild_keywordid=self.ebuild_keywordid)
+ #self._from_db_object(context, self, db_ebuild_keyword)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+ print('foo')
+ db_ebuild_keyword = EbuildKeywords._ebuild_keyword_get_query_from_db(context)
+
+ if 'status' in filters:
+ db_ebuild_keyword = db_ebuild_keyword.filter(
+ models.EbuildsKeywords.status == filters['status']).first()
+ return cls._from_db_object(context, cls(context), db_ebuild_keyword,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _ebuild_keyword_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all ebuild_keywordss.
+ """
+ filters = filters or {}
+
+ query = EbuildKeyword._ebuild_keyword_get_query_from_db(context)
+
+ if 'status' in filters:
+ query = query.filter(
+ models.EbuildsKeywords.status == filters['status'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = EbuildKeyword._ebuild_keyword_get_query_from_db(context).\
+ filter_by(id=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.EbuildsKeywords,
+ limit,
+ [sort_key, 'id'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class EbuildKeywordList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('EbuildKeyword'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='id', sort_dir='asc', limit=None, marker=None):
+ db_ebuild_keywords = _ebuild_keyword_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.ebuild_keyword.EbuildKeyword,
+ db_ebuild_keywords,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.EbuildsKeywords).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_ebuild_keyword = context.session.query(models.EbuildsKeywords).filter_by(auto=True)
+ db_ebuild_keyword.update(values)
diff --git a/gosbs/objects/ebuild_metadata.py b/gosbs/objects/ebuild_metadata.py
new file mode 100644
index 0000000..9a886ee
--- /dev/null
+++ b/gosbs/objects/ebuild_metadata.py
@@ -0,0 +1,282 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it fit what we need.
+# I need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+PROJECT_STATUS = ['failed', 'completed', 'in-progress', 'waiting']
+
+def _dict_with_extra_specs(ebuild_metadata_model):
+ extra_specs = {}
+ return dict(ebuild_metadata_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _ebuild_metadata_create(context, values):
+ db_ebuild_metadata = models.EbuildsMetadata()
+ db_ebuild_metadata.update(values)
+
+ try:
+ db_ebuild_metadata.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'ebuild_metadataid' in e.columns:
+ raise exception.ImagesIdExists(ebuild_metadata_id=values['ebuild_metadataid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_ebuild_metadata)
+
+
+@db_api.main_context_manager.writer
+def _ebuild_metadata_destroy(context, ebuild_metadata_id=None, ebuild_metadataid=None):
+ query = context.session.query(models.EbuildsMetadata)
+
+ if ebuild_metadata_id is not None:
+ query.filter(models.EbuildsMetadata.uuid == ebuild_metadata_id).delete()
+ else:
+ query.filter(models.EbuildsMetadata.uuid == ebuild_metadataid).delete()
+
+
+@base.NovaObjectRegistry.register
+class EbuildMetadata(base.NovaObject, base.NovaObjectDictCompat):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'id': fields.IntegerField(),
+ 'ebuild_uuid': fields.UUIDField(),
+ 'commit': fields.StringField(nullable=True),
+ 'commit_msg' : fields.StringField(nullable=True),
+ 'description' : fields.StringField(nullable=True),
+ 'slot': fields.StringField(nullable=True),
+ 'homepage': fields.StringField(nullable=True),
+ 'license': fields.StringField(nullable=True),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(EbuildMetadata, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_ebuild_metadatas = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(EbuildMetadata, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, ebuild_metadata, db_ebuild_metadata, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ ebuild_metadata._context = context
+ for name, field in ebuild_metadata.fields.items():
+ value = db_ebuild_metadata[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ ebuild_metadata[name] = value
+
+ ebuild_metadata.obj_reset_changes()
+ return ebuild_metadata
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _ebuild_metadata_get_query_from_db(context):
+ query = context.session.query(models.EbuildsMetadata)
+ return query
+
+ @staticmethod
+ @require_context
+ def _ebuild_metadata_get_from_db(context, uuid):
+ """Returns a dict describing specific ebuild_metadatas."""
+ result = EbuildMetadata._ebuild_metadata_get_query_from_db(context).\
+ filter_by(uuid=uuid).\
+ first()
+ return result
+
+ @staticmethod
+ @require_context
+ def _ebuild_metadata_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = EbuildMetadata._ebuild_metadata_get_query_from_db(context).\
+ filter_by(name=name).\
+ first()
+ return result
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(EbuildMetadata, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(EbuildMetadata, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_uuid(cls, context, uuid):
+ db_ebuild_metadata = cls._ebuild_metadata_get_from_db(context, uuid)
+ if not db_ebuild_metadata:
+ return None
+ return cls._from_db_object(context, cls(context), db_ebuild_metadata,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name):
+ db_ebuild_metadata = cls._ebuild_metadata_get_by_name_from_db(context, name)
+ if not db_ebuild_metadata:
+ return None
+ return cls._from_db_object(context, cls(context), db_ebuild_metadata,
+ expected_attrs=[])
+
+ @staticmethod
+ def _ebuild_metadata_create(context, updates):
+ return _ebuild_metadata_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_ebuild_metadata = self._ebuild_metadata_create(context, updates)
+ self._from_db_object(context, self, db_ebuild_metadata)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a ebuild_metadatas.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_ebuild_metadata = context.session.query(models.EbuildsMetadata).\
+ filter_by(uuid=self.id).first()
+ if not db_ebuild_metadata:
+ raise exception.ImagesNotFound(ebuild_metadata_id=self.id)
+ db_ebuild_metadata.update(values)
+ db_ebuild_metadata.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_ebuild_metadata)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _ebuild_metadata_destroy(context, ebuild_metadata_id=None, ebuild_metadataid=None):
+ _ebuild_metadata_destroy(context, ebuild_metadata_id=ebuild_metadata_id, ebuild_metadataid=ebuild_metadataid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a ebuild_metadatas
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a ebuild_metadatas object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._ebuild_metadata_destroy(context, ebuild_metadata_id=self.id)
+ else:
+ self._ebuild_metadata_destroy(context, ebuild_metadataid=self.ebuild_metadataid)
+ #self._from_db_object(context, self, db_ebuild_metadata)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+ print('foo')
+ db_ebuild_metadata = EbuildMetadata._ebuild_metadata_get_query_from_db(context)
+
+ if 'status' in filters:
+ db_ebuild_metadata = db_ebuild_metadata.filter(
+ models.EbuildsMetadata.status == filters['status']).first()
+ return cls._from_db_object(context, cls(context), db_ebuild_metadata,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _ebuild_metadata_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all ebuild_metadatass.
+ """
+ filters = filters or {}
+
+ query = EbuildMetadata._ebuild_metadata_get_query_from_db(context)
+
+ if 'status' in filters:
+ query = query.filter(
+ models.EbuildsMetadata.status == filters['status'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = EbuildMetadata._ebuild_metadata_get_query_from_db(context).\
+ filter_by(id=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.EbuildsMetadata,
+ limit,
+ [sort_key, 'uuid'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class EbuildMetadataList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('EbuildMetadata'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='id', sort_dir='asc', limit=None, marker=None):
+ db_ebuild_metadatas = _ebuild_metadata_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.ebuild_metadata.EbuildMetadata,
+ db_ebuild_metadatas,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.EbuildsMetadata).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_ebuild_metadata = context.session.query(models.EbuildsMetadata).filter_by(auto=True)
+ db_ebuild_metadata.update(values)
diff --git a/gosbs/objects/ebuild_restriction.py b/gosbs/objects/ebuild_restriction.py
new file mode 100644
index 0000000..e3e046c
--- /dev/null
+++ b/gosbs/objects/ebuild_restriction.py
@@ -0,0 +1,281 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it fit what we need.
+# I need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+PROJECT_STATUS = ['failed', 'completed', 'in-progress', 'waiting']
+
+def _dict_with_extra_specs(ebuild_restriction_model):
+ extra_specs = {}
+ return dict(ebuild_restriction_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _ebuild_restriction_create(context, values):
+ db_ebuild_restriction = models.EbuildsRestrictions()
+ db_ebuild_restriction.update(values)
+
+ try:
+ db_ebuild_restriction.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'ebuild_restrictionid' in e.columns:
+ raise exception.ImagesIdExists(ebuild_restriction_id=values['ebuild_restrictionid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_ebuild_restriction)
+
+
+@db_api.main_context_manager.writer
+def _ebuild_restriction_destroy(context, ebuild_restriction_id=None, ebuild_restrictionid=None):
+ query = context.session.query(models.EbuildsRestrictions)
+
+ if ebuild_restriction_id is not None:
+ query.filter(models.EbuildsRestrictions.id == ebuild_restriction_id).delete()
+ else:
+ query.filter(models.EbuildsRestrictions.id == ebuild_restrictionid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class EbuildRestriction(base.NovaObject, base.NovaObjectDictCompat):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'id': fields.IntegerField(),
+ 'ebuild_uuid': fields.UUIDField(),
+ 'restriction_id': fields.IntegerField(),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(EbuildRestriction, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_ebuild_restrictions = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(EbuildRestriction, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, ebuild_restriction, db_ebuild_restriction, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ ebuild_restriction._context = context
+ for name, field in ebuild_restriction.fields.items():
+ value = db_ebuild_restriction[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ ebuild_restriction[name] = value
+
+ ebuild_restriction.obj_reset_changes()
+ return ebuild_restriction
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _ebuild_restriction_get_query_from_db(context):
+ query = context.session.query(models.EbuildsRestrictions)
+ return query
+
+ @staticmethod
+ @require_context
+ def _ebuild_restriction_get_from_db(context, id):
+ """Returns a dict describing specific ebuild_restrictions."""
+ result = EbuildRestriction._ebuild_restriction_get_query_from_db(context).\
+ filter_by(id=id).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(ebuild_restriction_id=id)
+ return result
+
+ @staticmethod
+ @require_context
+ def _ebuild_restriction_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = EbuildRestriction._ebuild_restriction_get_query_from_db(context).\
+ filter_by(name=name).\
+ first()
+ if not result:
+ raise exception.FlavorNotFoundByName(ebuild_restrictions_name=name)
+ return _dict_with_extra_specs(result)
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(EbuildRestriction, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(EbuildRestriction, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_id(cls, context, id):
+ db_ebuild_restriction = cls._ebuild_restriction_get_from_db(context, id)
+ return cls._from_db_object(context, cls(context), db_ebuild_restriction,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name):
+ db_ebuild_restriction = cls._ebuild_restriction_get_by_name_from_db(context, name)
+ return cls._from_db_object(context, cls(context), db_ebuild_restriction,
+ expected_attrs=[])
+
+ @staticmethod
+ def _ebuild_restriction_create(context, updates):
+ return _ebuild_restriction_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_ebuild_restriction = self._ebuild_restriction_create(context, updates)
+ self._from_db_object(context, self, db_ebuild_restriction)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a ebuild_restrictions.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_ebuild_restriction = context.session.query(models.EbuildsRestrictions).\
+ filter_by(id=self.id).first()
+ if not db_ebuild_restriction:
+ raise exception.ImagesNotFound(ebuild_restriction_id=self.id)
+ db_ebuild_restriction.update(values)
+ db_ebuild_restriction.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_ebuild_restriction)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _ebuild_restriction_destroy(context, ebuild_restriction_id=None, ebuild_restrictionid=None):
+ _ebuild_restriction_destroy(context, ebuild_restriction_id=ebuild_restriction_id, ebuild_restrictionid=ebuild_restrictionid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a ebuild_restrictions
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a ebuild_restrictions object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._ebuild_restriction_destroy(context, ebuild_restriction_id=self.id)
+ else:
+ self._ebuild_restriction_destroy(context, ebuild_restrictionid=self.ebuild_restrictionid)
+ #self._from_db_object(context, self, db_ebuild_restriction)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+ print('foo')
+ db_ebuild_restriction = EbuildRestrictions._ebuild_restriction_get_query_from_db(context)
+
+ if 'status' in filters:
+ db_ebuild_restriction = db_ebuild_restriction.filter(
+ models.EbuildsRestrictions.status == filters['status']).first()
+ return cls._from_db_object(context, cls(context), db_ebuild_restriction,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _ebuild_restriction_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all ebuild_restrictionss.
+ """
+ filters = filters or {}
+
+ query = EbuildRestriction._ebuild_restriction_get_query_from_db(context)
+
+ if 'ebuild_uuid' in filters:
+ query = query.filter(
+ models.EbuildsRestrictions.ebuild_uuid == filters['ebuild_uuid'])
+ if not query:
+ return None
+
+ marker_row = None
+ if marker is not None:
+ marker_row = EbuildRestriction._ebuild_restriction_get_query_from_db(context).\
+ filter_by(id=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.EbuildsRestrictions,
+ limit,
+ [sort_key, 'id'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class EbuildRestrictionList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('EbuildRestriction'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='id', sort_dir='asc', limit=None, marker=None):
+ db_ebuild_restrictions = _ebuild_restriction_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.ebuild_restriction.EbuildRestriction,
+ db_ebuild_restrictions,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.EbuildsRestrictions).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_ebuild_restriction = context.session.query(models.EbuildsRestrictions).filter_by(auto=True)
+ db_ebuild_restriction.update(values)
diff --git a/gosbs/objects/email.py b/gosbs/objects/email.py
new file mode 100644
index 0000000..375c429
--- /dev/null
+++ b/gosbs/objects/email.py
@@ -0,0 +1,269 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it fit what we need.
+# I need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+
+def _dict_with_extra_specs(email_model):
+ extra_specs = {}
+ return dict(email_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _email_create(context, values):
+ db_email = models.Emails()
+ db_email.update(values)
+
+ try:
+ db_email.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'emailid' in e.columns:
+ raise exception.ImagesIdExists(email_id=values['emailid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_email)
+
+
+@db_api.main_context_manager.writer
+def _email_destroy(context, email_id=None, emailid=None):
+ query = context.session.query(models.Emails)
+
+ if email_id is not None:
+ query.filter(models.Emails.id == email_id).delete()
+ else:
+ query.filter(models.Emails.id == emailid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class Email(base.NovaObject, base.NovaObjectDictCompat, base.NovaPersistentObject):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'id': fields.IntegerField(),
+ 'email': fields.StringField(),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(Email, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_emails = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(Email, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, email, db_email, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ email._context = context
+ for name, field in email.fields.items():
+ value = db_email[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ email[name] = value
+
+ email.obj_reset_changes()
+ return email
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _email_get_query_from_db(context):
+ query = context.session.query(models.Emails)
+ return query
+
+ @staticmethod
+ @require_context
+ def _email_get_from_db(context, id):
+ """Returns a dict describing specific emails."""
+ result = Email._email_get_query_from_db(context).\
+ filter_by(id=id).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(email_id=id)
+ return result
+
+ @staticmethod
+ @require_context
+ def _email_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = Email._email_get_query_from_db(context).\
+ filter_by(email=name).\
+ first()
+ return result
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(Email, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(Email, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_id(cls, context, id):
+ db_email = cls._email_get_from_db(context, id)
+ return cls._from_db_object(context, cls(context), db_email,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name):
+ db_email = cls._email_get_by_name_from_db(context, name)
+ if not db_email:
+ return None
+ return cls._from_db_object(context, cls(context), db_email,
+ expected_attrs=[])
+
+ @staticmethod
+ def _email_create(context, updates):
+ return _email_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_email = self._email_create(context, updates)
+ self._from_db_object(context, self, db_email)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a emails.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_email = context.session.query(models.Emails).\
+ filter_by(uuid=self.id).first()
+ if not db_email:
+ raise exception.ImagesNotFound(email_id=self.id)
+ db_email.update(values)
+ db_email.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_email)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _email_destroy(context, email_id=None, emailid=None):
+ _email_destroy(context, email_id=email_id, emailid=emailid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a emails
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a emails object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._email_destroy(context, email_id=self.id)
+ else:
+ self._email_destroy(context, emailid=self.emailid)
+ #self._from_db_object(context, self, db_email)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+ db_email = Email._email_get_query_from_db(context)
+
+ return cls._from_db_object(context, cls(context), db_email,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _email_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all emailss.
+ """
+ filters = filters or {}
+
+ query = Email._email_get_query_from_db(context)
+
+ marker_row = None
+ if marker is not None:
+ marker_row = Email._email_get_query_from_db(context).\
+ filter_by(id=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.Emails,
+ limit,
+ [sort_key, 'id'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class EmailList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('Email'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='id', sort_dir='asc', limit=None, marker=None):
+ db_emails = _email_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.email.Email,
+ db_emails,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.Emails).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_email = context.session.query(models.Emails).filter_by(auto=True)
+ db_email.update(values)
diff --git a/gosbs/objects/fields.py b/gosbs/objects/fields.py
new file mode 100644
index 0000000..02dd297
--- /dev/null
+++ b/gosbs/objects/fields.py
@@ -0,0 +1,70 @@
+# Copyright 2013 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/fields.py
+
+import os
+import re
+
+from cursive import signature_utils
+from oslo_serialization import jsonutils
+from oslo_versionedobjects import fields
+import six
+
+from gosbs import exception
+from gosbs.i18n import _
+
+
+# Import field errors from oslo.versionedobjects
+KeyTypeError = fields.KeyTypeError
+ElementTypeError = fields.ElementTypeError
+
+
+# Import fields from oslo.versionedobjects
+BooleanField = fields.BooleanField
+UnspecifiedDefault = fields.UnspecifiedDefault
+IntegerField = fields.IntegerField
+NonNegativeIntegerField = fields.NonNegativeIntegerField
+UUIDField = fields.UUIDField
+FloatField = fields.FloatField
+NonNegativeFloatField = fields.NonNegativeFloatField
+StringField = fields.StringField
+SensitiveStringField = fields.SensitiveStringField
+EnumField = fields.EnumField
+DateTimeField = fields.DateTimeField
+DictOfStringsField = fields.DictOfStringsField
+DictOfNullableStringsField = fields.DictOfNullableStringsField
+DictOfIntegersField = fields.DictOfIntegersField
+ListOfStringsField = fields.ListOfStringsField
+SetOfIntegersField = fields.SetOfIntegersField
+ListOfSetsOfIntegersField = fields.ListOfSetsOfIntegersField
+ListOfDictOfNullableStringsField = fields.ListOfDictOfNullableStringsField
+DictProxyField = fields.DictProxyField
+ObjectField = fields.ObjectField
+ListOfObjectsField = fields.ListOfObjectsField
+VersionPredicateField = fields.VersionPredicateField
+FlexibleBooleanField = fields.FlexibleBooleanField
+DictOfListOfStringsField = fields.DictOfListOfStringsField
+IPAddressField = fields.IPAddressField
+IPV4AddressField = fields.IPV4AddressField
+IPV6AddressField = fields.IPV6AddressField
+IPV4AndV6AddressField = fields.IPV4AndV6AddressField
+IPNetworkField = fields.IPNetworkField
+IPV4NetworkField = fields.IPV4NetworkField
+IPV6NetworkField = fields.IPV6NetworkField
+AutoTypedField = fields.AutoTypedField
+BaseEnumField = fields.BaseEnumField
+MACAddressField = fields.MACAddressField
+ListOfIntegersField = fields.ListOfIntegersField
+PCIAddressField = fields.PCIAddressField
diff --git a/gosbs/objects/flavor.py b/gosbs/objects/flavor.py
new file mode 100644
index 0000000..28739a8
--- /dev/null
+++ b/gosbs/objects/flavor.py
@@ -0,0 +1,228 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it will fit what we need.
+# It need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+
+OPTIONAL_FIELDS = []
+# Remove these fields in version 2.0 of the object.
+DEPRECATED_FIELDS = ['deleted', 'deleted_at']
+
+# Non-joined fields which can be updated.
+MUTABLE_FIELDS = set(['description'])
+
+CONF = gosbs.conf.CONF
+
+
+def _dict_with_extra_specs(flavor_model):
+ extra_specs = {}
+ return dict(flavor_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _flavor_create(context, values):
+ db_flavor = models.Flavors()
+ db_flavor.update(values)
+
+ try:
+ db_flavor.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'flavorid' in e.columns:
+ raise exception.FlavorIdExists(flavor_id=values['flavorid'])
+ raise exception.FlavorExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_flavor)
+
+
+@db_api.main_context_manager.writer
+def _flavor_destroy(context, flavor_id=None, flavorid=None):
+ query = context.session.query(models.Flavors)
+
+ if flavor_id is not None:
+ query = query.filter(models.Flavors.id == flavor_id)
+ else:
+ query = query.filter(models.Flavors.flavorid == flavorid)
+ result = query.first()
+
+ if not result:
+ raise exception.FlavorNotFound(flavor_id=(flavor_id or flavorid))
+
+ return result
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class Flavor(base.NovaObject, base.NovaObjectDictCompat):
+ # Version 1.0: Initial version
+ # Version 1.1: Added save_projects(), save_extra_specs(), removed
+ # remotable from save()
+ # Version 1.2: Added description field. Note: this field should not be
+ # persisted with the embedded instance.flavor.
+ VERSION = '1.2'
+
+ fields = {
+ 'id': fields.IntegerField(),
+ 'name': fields.StringField(nullable=True),
+ 'ram': fields.IntegerField(),
+ 'vcpus': fields.IntegerField(),
+ 'disk': fields.IntegerField(),
+ 'swap': fields.IntegerField(),
+ 'description': fields.StringField(nullable=True)
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(Flavor, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_projects = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(Flavor, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+ if target_version < (1, 2) and 'description' in primitive:
+ del primitive['description']
+
+ @staticmethod
+ def _from_db_object(context, flavor, db_flavor, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ flavor._context = context
+ for name, field in flavor.fields.items():
+ if name in OPTIONAL_FIELDS:
+ continue
+ if name in DEPRECATED_FIELDS and name not in db_flavor:
+ continue
+ value = db_flavor[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ flavor[name] = value
+
+ # NOTE(danms): This is to support processing the API flavor
+ # model, which does not have these deprecated fields. When we
+ # remove compatibility with the old InstanceType model, we can
+ # remove this as well.
+ if any(f not in db_flavor for f in DEPRECATED_FIELDS):
+ flavor.deleted_at = None
+ flavor.deleted = False
+
+ flavor.obj_reset_changes()
+ return flavor
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _flavor_get_query_from_db(context):
+ query = context.session.query(models.Flavors)
+ return query
+
+ @staticmethod
+ @require_context
+ def _flavor_get_from_db(context, id):
+ """Returns a dict describing specific flavor."""
+ result = Flavor._flavor_get_query_from_db(context).\
+ filter_by(id=id).\
+ first()
+ if not result:
+ raise exception.FlavorNotFound(flavor_id=id)
+ return result
+
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(Flavor, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(Flavor, self).obj_what_changed()
+ return changes
+
+
+ @base.remotable_classmethod
+ def get_by_id(cls, context, id):
+ db_flavor = cls._flavor_get_from_db(context, id)
+ return cls._from_db_object(context, cls(context), db_flavor,
+ expected_attrs=[])
+
+
+ @staticmethod
+ def _flavor_create(context, updates):
+ return _flavor_create(context, updates)
+
+ @base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_flavor = self._flavor_create(context, updates)
+ self._from_db_object(context, self, db_flavor)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a flavor.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_flavor = context.session.query(models.Flavors).\
+ filter_by(id=self.id).first()
+ if not db_flavor:
+ raise exception.FlavorNotFound(flavor_id=self.id)
+ db_flavor.update(values)
+ db_flavor.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_flavor)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _flavor_destroy(context, flavor_id=None, flavorid=None):
+ return _flavor_destroy(context, flavor_id=flavor_id, flavorid=flavorid)
+
+ @base.remotable
+ def destroy(self):
+ # NOTE(danms): Historically the only way to delete a flavor
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a flavor object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ db_flavor = self._flavor_destroy(self._context,
+ flavor_id=self.id)
+ else:
+ db_flavor = self._flavor_destroy(self._context,
+ flavorid=self.flavorid)
+ self._from_db_object(self._context, self, db_flavor)
diff --git a/gosbs/objects/image.py b/gosbs/objects/image.py
new file mode 100644
index 0000000..45d48eb
--- /dev/null
+++ b/gosbs/objects/image.py
@@ -0,0 +1,204 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it will fit what we need.
+# It need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+
+CONF = gosbs.conf.CONF
+
+
+def _dict_with_extra_specs(image_model):
+ extra_specs = {}
+ return dict(image_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _image_create(context, values):
+ db_image = models.Images()
+ db_image.update(values)
+
+ try:
+ db_image.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'imageid' in e.columns:
+ raise exception.ImagesIdExists(image_id=values['imageid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_image)
+
+
+@db_api.main_context_manager.writer
+def _image_destroy(context, image_id=None, imageid=None):
+ query = context.session.query(models.Images)
+
+ if image_id is not None:
+ query = query.filter(models.Images.id == image_id)
+ else:
+ query = query.filter(models.Images.imageid == imageid)
+ result = query.first()
+
+ if not result:
+ raise exception.ImagesNotFound(image_id=(image_id or imageid))
+
+ return result
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class Image(base.NovaObject, base.NovaObjectDictCompat):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'id': fields.UUIDField(),
+ 'name': fields.StringField(),
+ 'min_ram': fields.IntegerField(),
+ 'min_disk': fields.IntegerField(),
+ 'size': fields.IntegerField(),
+ 'status': fields.StringField()
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(Image, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_projects = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(Image, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, image, db_image, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ image._context = context
+ for name, field in image.fields.items():
+ value = db_image[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ image[name] = value
+
+ image.obj_reset_changes()
+ return image
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _image_get_query_from_db(context):
+ query = context.session.query(models.Images)
+ return query
+
+ @staticmethod
+ @require_context
+ def _image_get_from_db(context, id):
+ """Returns a dict describing specific image."""
+ result = Image._image_get_query_from_db(context).\
+ filter_by(id=id).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(image_id=id)
+ return result
+
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(Image, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(Image, self).obj_what_changed()
+ return changes
+
+
+ @base.remotable_classmethod
+ def get_by_id(cls, context, id):
+ db_image = cls._image_get_from_db(context, id)
+ return cls._from_db_object(context, cls(context), db_image,
+ expected_attrs=[])
+
+
+ @staticmethod
+ def _image_create(context, updates):
+ return _image_create(context, updates)
+
+ @base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_image = self._image_create(context, updates)
+ self._from_db_object(context, self, db_image)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a image.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_image = context.session.query(models.Images).\
+ filter_by(id=self.id).first()
+ if not db_image:
+ raise exception.ImagesNotFound(image_id=self.id)
+ db_image.update(values)
+ db_image.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_image)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _image_destroy(context, image_id=None, imageid=None):
+ return _image_destroy(context, image_id=image_id, imageid=imageid)
+
+ @base.remotable
+ def destroy(self):
+ # NOTE(danms): Historically the only way to delete a image
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a image object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ db_image = self._image_destroy(self._context,
+ image_id=self.id)
+ else:
+ db_image = self._image_destroy(self._context,
+ imageid=self.imageid)
+ self._from_db_object(self._context, self, db_image)
diff --git a/gosbs/objects/keyword.py b/gosbs/objects/keyword.py
new file mode 100644
index 0000000..4cf2e0c
--- /dev/null
+++ b/gosbs/objects/keyword.py
@@ -0,0 +1,277 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it will fit what we need.
+# It need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+
+def _dict_with_extra_specs(keyword_model):
+ extra_specs = {}
+ return dict(keyword_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _keyword_create(context, values):
+ db_keyword = models.Keywords()
+ db_keyword.update(values)
+
+ try:
+ db_keyword.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'keywordid' in e.columns:
+ raise exception.ImagesIdExists(keyword_id=values['keywordid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_keyword)
+
+
+@db_api.main_context_manager.writer
+def _keyword_destroy(context, keyword_id=None, keywordid=None):
+ query = context.session.query(models.Keywords)
+
+ if keyword_id is not None:
+ query.filter(models.Keywords.uuid == keyword_id).delete()
+ else:
+ query.filter(models.Keywords.uuid == keywordid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class Keyword(base.NovaObject, base.NovaObjectDictCompat, base.NovaPersistentObject2):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'id': fields.IntegerField(),
+ 'keyword': fields.StringField(),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(Keyword, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_keywords = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(Keyword, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, keyword, db_keyword, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ keyword._context = context
+ for name, field in keyword.fields.items():
+ value = db_keyword[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ keyword[name] = value
+
+ keyword.obj_reset_changes()
+ return keyword
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _keyword_get_query_from_db(context):
+ query = context.session.query(models.Keywords)
+ return query
+
+ @staticmethod
+ @require_context
+ def _keyword_get_from_db(context, id):
+ """Returns a dict describing specific keywords."""
+ result = Keyword._keyword_get_query_from_db(context).\
+ filter_by(uuid=id).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(keyword_id=id)
+ return result
+
+ @staticmethod
+ @require_context
+ def _keyword_get_by_keyword_from_db(context, keyword):
+ """Returns a dict describing specific flavor."""
+ result = Keyword._keyword_get_query_from_db(context).\
+ filter_by(keyword=keyword).\
+ first()
+ return result
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(Keyword, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(Keyword, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_id(cls, context, id):
+ db_keyword = cls._keyword_get_from_db(context, id)
+ return cls._from_db_object(context, cls(context), db_keyword,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, keyword):
+ db_keyword = cls._keyword_get_by_keyword_from_db(context, keyword)
+ if not db_keyword:
+ return None
+ return cls._from_db_object(context, cls(context), db_keyword,
+ expected_attrs=[])
+
+ @staticmethod
+ def _keyword_create(context, updates):
+ return _keyword_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_keyword = self._keyword_create(context, updates)
+ self._from_db_object(context, self, db_keyword)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a keywords.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_keyword = context.session.query(models.Keywords).\
+ filter_by(uuid=self.id).first()
+ if not db_keyword:
+ raise exception.ImagesNotFound(keyword_id=self.id)
+ db_keyword.update(values)
+ db_keyword.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_keyword)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _keyword_destroy(context, keyword_id=None, keywordid=None):
+ _keyword_destroy(context, keyword_id=keyword_id, keywordid=keywordid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a keywords
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a keywords object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._keyword_destroy(context, keyword_id=self.id)
+ else:
+ self._keyword_destroy(context, keywordid=self.keywordid)
+ #self._from_db_object(context, self, db_keyword)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+ print('foo')
+ db_keyword = Keyword._keyword_get_query_from_db(context)
+
+ if 'status' in filters:
+ db_keyword = db_keyword.filter(
+ models.Keywords.status == filters['status']).first()
+ return cls._from_db_object(context, cls(context), db_keyword,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _keyword_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all keywordss.
+ """
+ filters = filters or {}
+
+ query = Keyword._keyword_get_query_from_db(context)
+
+ if 'status' in filters:
+ query = query.filter(
+ models.Keywords.status == filters['status'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = Keyword._keyword_get_query_from_db(context).\
+ filter_by(id=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.Keywords,
+ limit,
+ [sort_key, 'uuid'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class KeywordList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('Keyword'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='id', sort_dir='asc', limit=None, marker=None):
+ db_keywords = _keyword_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.keyword.Keyword,
+ db_keywords,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.Keywords).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_keyword = context.session.query(models.Keywords).filter_by(auto=True)
+ db_keyword.update(values)
diff --git a/gosbs/objects/package.py b/gosbs/objects/package.py
new file mode 100644
index 0000000..7f3ac6b
--- /dev/null
+++ b/gosbs/objects/package.py
@@ -0,0 +1,300 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it will fit what we need.
+# It need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+PACKAGE_STATUS = ['failed', 'completed', 'in-progress', 'waiting']
+
+def _dict_with_extra_specs(package_model):
+ extra_specs = {}
+ return dict(package_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _package_create(context, values):
+ db_package = models.Packages()
+ db_package.update(values)
+
+ try:
+ db_package.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'packageid' in e.columns:
+ raise exception.ImagesIdExists(package_id=values['packageid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_package)
+
+
+@db_api.main_context_manager.writer
+def _package_destroy(context, package_id=None, packageid=None):
+ query = context.session.query(models.Packages)
+
+ if package_id is not None:
+ query.filter(models.Packages.uuid == package_id).delete()
+ else:
+ query.filter(models.Packages.uuid == packageid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class Package(base.NovaObject, base.NovaObjectDictCompat, base.NovaPersistentObject):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'uuid': fields.UUIDField(),
+ 'name': fields.StringField(),
+ 'status' : fields.EnumField(valid_values=PACKAGE_STATUS),
+ 'category_uuid': fields.UUIDField(),
+ 'repo_uuid': fields.UUIDField(),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(Package, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_packages = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(Package, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, package, db_package, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ package._context = context
+ for name, field in package.fields.items():
+ value = db_package[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ package[name] = value
+
+ package.obj_reset_changes()
+ return package
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _package_get_query_from_db(context):
+ query = context.session.query(models.Packages)
+ return query
+
+ @staticmethod
+ @require_context
+ def _package_get_from_db(context, uuid):
+ """Returns a dict describing specific packages."""
+ result = Package._package_get_query_from_db(context).\
+ filter_by(uuid=uuid).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(package_id=uuid)
+ return result
+
+ @staticmethod
+ @require_context
+ def _package_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = Package._package_get_query_from_db(context).\
+ filter_by(name=name).\
+ first()
+ if not result:
+ return None
+ return result
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(Package, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(Package, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_uuid(cls, context, uuid):
+ db_package = cls._package_get_from_db(context, uuid)
+ return cls._from_db_object(context, cls(context), db_package,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name, filters=None):
+ filters = filters or {}
+ db_package = Package._package_get_query_from_db(context)
+ db_package = db_package.filter_by(name=name)
+ if 'repo_uuid' in filters:
+ db_package = db_package.filter(
+ models.Packages.repo_uuid == filters['repo_uuid'])
+ if 'deleted' in filters:
+ db_package = db_package.filter(
+ models.Packages.deleted == filters['deleted'])
+ if 'category_uuid' in filters:
+ db_package = db_package.filter(
+ models.Packages.category_uuid == filters['category_uuid'])
+ db_package = db_package.first()
+ if not db_package:
+ return None
+ return cls._from_db_object(context, cls(context), db_package,
+ expected_attrs=[])
+
+ @staticmethod
+ def _package_create(context, updates):
+ return _package_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_package = self._package_create(context, updates)
+ self._from_db_object(context, self, db_package)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a packages.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_package = context.session.query(models.Packages).\
+ filter_by(uuid=self.uuid).first()
+ if not db_package:
+ raise exception.ImagesNotFound(package_id=self.uuid)
+ db_package.update(values)
+ db_package.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_package)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _package_destroy(context, package_id=None, packageid=None):
+ _package_destroy(context, package_id=package_id, packageid=packageid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a packages
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a packages object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._package_destroy(context, package_id=self.uuid)
+ else:
+ self._package_destroy(context, packageid=self.packageid)
+ #self._from_db_object(context, self, db_package)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+ db_package = Package._package_get_query_from_db(context)
+
+ if 'status' in filters:
+ db_package = db_package.filter(
+ models.Packages.status == filters['status']).first()
+ return cls._from_db_object(context, cls(context), db_package,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _package_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all packagess.
+ """
+ filters = filters or {}
+
+ query = Package._package_get_query_from_db(context)
+
+ if 'status' in filters:
+ query = query.filter(
+ models.Packages.status == filters['status'])
+ if 'repo_uuid' in filters:
+ query = query.filter(
+ models.Packages.repo_uuid == filters['repo_uuid'])
+ if 'category_uuid' in filters:
+ query = query.filter(
+ models.Packages.category_uuid == filters['category_uuid'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = Package._package_get_query_from_db(context).\
+ filter_by(uuid=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.Packages,
+ limit,
+ [sort_key, 'uuid'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class PackageList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('Package'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='uuid', sort_dir='asc', limit=None, marker=None):
+ db_packages = _package_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.package.Package,
+ db_packages,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.Packages).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_package = context.session.query(models.Packages).filter_by(auto=True)
+ db_package.update(values)
diff --git a/gosbs/objects/package_email.py b/gosbs/objects/package_email.py
new file mode 100644
index 0000000..c6a5f2d
--- /dev/null
+++ b/gosbs/objects/package_email.py
@@ -0,0 +1,301 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it will fit what we need.
+# It need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+
+def _dict_with_extra_specs(package_model):
+ extra_specs = {}
+ return dict(package_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _package_create(context, values):
+ db_package = models.PackagesEmails()
+ db_package.update(values)
+
+ try:
+ db_package.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'packageid' in e.columns:
+ raise exception.ImagesIdExists(package_id=values['packageid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_package)
+
+
+@db_api.main_context_manager.writer
+def _package_destroy(context, package_id=None, packageid=None):
+ query = context.session.query(models.PackagesEmails)
+
+ if package_id is not None:
+ query.filter(models.PackagesEmails.id == package_id).delete()
+ else:
+ query.filter(models.PackagesEmails.id == packageid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class PackageEmail(base.NovaObject, base.NovaObjectDictCompat):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'id': fields.IntegerField(),
+ 'package_uuid': fields.UUIDField(),
+ 'email_id': fields.IntegerField(),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(PackageEmail, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_packages = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(PackageEmail, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, package, db_package, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ package._context = context
+ for name, field in package.fields.items():
+ value = db_package[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ package[name] = value
+
+ package.obj_reset_changes()
+ return package
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _package_get_query_from_db(context):
+ query = context.session.query(models.PackagesEmails)
+ return query
+
+ @staticmethod
+ @require_context
+ def _package_get_from_db(context, id):
+ """Returns a dict describing specific packages."""
+ result = PackageEmail._package_get_query_from_db(context).\
+ filter_by(id=id).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(package_id=id)
+ return result
+
+ @staticmethod
+ @require_context
+ def _email_id_get_from_db(context, email_id):
+ """Returns a dict describing specific packages."""
+ result = PackageEmail._package_get_query_from_db(context).\
+ filter_by(email_id=email_id).\
+ first()
+ return result
+
+ @staticmethod
+ @require_context
+ def _package_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = PackageEmail._package_get_query_from_db(context).\
+ filter_by(name=email).\
+ first()
+ if not result:
+ raise exception.FlavorNotFoundByName(packages_name=name)
+ return _dict_with_extra_specs(result)
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(PackageEmail, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(PackageEmail, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_id(cls, context, id, filters=None):
+ db_package = cls._package_get_from_db(context, id)
+ if 'package_uuid' in filters:
+ db_package = db_package.filter(
+ models.PackagesEmails.package_uuid == filters['package_uuid'])
+ return cls._from_db_object(context, cls(context), db_package,
+ expected_attrs=[])
+
+ @base.remotable_classmethod
+ def get_by_email_id(cls, context, email_id, filters=None):
+ filters = filters or {}
+ db_package = PackageEmail._package_get_query_from_db(context)
+ db_package = db_package.filter_by(email_id=email_id)
+ if 'package_uuid' in filters:
+ db_package = db_package.filter(
+ models.PackagesEmails.package_uuid == filters['package_uuid'])
+ db_package = db_package.first()
+ if not db_package:
+ return None
+ return cls._from_db_object(context, cls(context), db_package,
+ expected_attrs=[])
+
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name, filters=None):
+ filters = filters or {}
+ db_package = cls._package_get_by_name_from_db(context, name)
+ if 'package_uuid' in filters:
+ db_package = db_package.filter(
+ models.PackagesEmails.package_uuid == filters['package_uuid'])
+ return cls._from_db_object(context, cls(context), db_package,
+ expected_attrs=[])
+
+ @staticmethod
+ def _package_create(context, updates):
+ return _package_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_package = self._package_create(context, updates)
+ self._from_db_object(context, self, db_package)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a packages.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_package = context.session.query(models.PackagesEmails).\
+ filter_by(id=self.id).first()
+ if not db_package:
+ raise exception.ImagesNotFound(package_id=self.id)
+ db_package.update(values)
+ db_package.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_package)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _package_destroy(context, package_id=None, packageid=None):
+ _package_destroy(context, package_id=package_id, packageid=packageid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a packages
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a packages object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._package_destroy(context, package_id=self.id)
+ else:
+ self._package_destroy(context, packageid=self.packageid)
+ #self._from_db_object(context, self, db_package)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+ db_package = PackageEmail._package_get_query_from_db(context)
+
+ return cls._from_db_object(context, cls(context), db_package,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _package_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all packagess.
+ """
+ filters = filters or {}
+
+ query = PackageEmail._package_get_query_from_db(context)
+
+ marker_row = None
+ if marker is not None:
+ marker_row = PackageEmail._package_get_query_from_db(context).\
+ filter_by(id=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.PackageEmails,
+ limit,
+ [sort_key, 'id'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class PackageEmailList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('PackageEmail'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='id', sort_dir='asc', limit=None, marker=None):
+ db_packages = _package_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.package.PackageEmail,
+ db_packages,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.PackagesEmails).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_package = context.session.query(models.PackagesEmails).filter_by(auto=True)
+ db_package.update(values)
diff --git a/gosbs/objects/package_metadata.py b/gosbs/objects/package_metadata.py
new file mode 100644
index 0000000..5f6270d
--- /dev/null
+++ b/gosbs/objects/package_metadata.py
@@ -0,0 +1,279 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it will fit what we need.
+# It need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+
+def _dict_with_extra_specs(package_metadata_model):
+ extra_specs = {}
+ return dict(package_metadata_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _package_metadata_create(context, values):
+ db_package_metadata = models.PackagesMetadata()
+ db_package_metadata.update(values)
+
+ try:
+ db_package_metadata.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'package_metadataid' in e.columns:
+ raise exception.ImagesIdExists(package_metadata_id=values['package_metadataid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_package_metadata)
+
+
+@db_api.main_context_manager.writer
+def _package_metadata_destroy(context, package_metadata_id=None, package_metadataid=None):
+ query = context.session.query(models.PackagesMetadata)
+
+ if package_metadata_id is not None:
+ query.filter(models.PackagesMetadata.id == package_metadata_id).delete()
+ else:
+ query.filter(models.PackagesMetadata.id == package_metadataid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class PackageMetadata(base.NovaObject, base.NovaObjectDictCompat):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'id': fields.IntegerField(),
+ 'package_uuid': fields.UUIDField(),
+ 'description' : fields.StringField(),
+ 'gitlog' : fields.StringField(),
+ 'checksum': fields.StringField(nullable=True),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(PackageMetadata, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_package_metadatas = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(PackageMetadata, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, package_metadata, db_package_metadata, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ package_metadata._context = context
+ for name, field in package_metadata.fields.items():
+ value = db_package_metadata[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ package_metadata[name] = value
+
+ package_metadata.obj_reset_changes()
+ return package_metadata
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _package_metadata_get_query_from_db(context):
+ query = context.session.query(models.PackagesMetadata)
+ return query
+
+ @staticmethod
+ @require_context
+ def _package_metadata_get_from_db(context, uuid):
+ """Returns a dict describing specific package_metadatas."""
+ result = PackageMetadata._package_metadata_get_query_from_db(context).\
+ filter_by(package_uuid=uuid).\
+ first()
+ return result
+
+ @staticmethod
+ @require_context
+ def _package_metadata_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = PackageMetadata._package_metadata_get_query_from_db(context).\
+ filter_by(name=name).\
+ first()
+ if not result:
+ raise exception.FlavorNotFoundByName(package_metadatas_name=name)
+ return result
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(PackageMetadata, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(PackageMetadata, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_uuid(cls, context, uuid):
+ db_package_metadata = cls._package_metadata_get_from_db(context, uuid)
+ if not db_package_metadata:
+ return None
+ return cls._from_db_object(context, cls(context), db_package_metadata,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name):
+ db_package_metadata = cls._package_metadata_get_by_name_from_db(context, name)
+ return cls._from_db_object(context, cls(context), db_package_metadata,
+ expected_attrs=[])
+
+ @staticmethod
+ def _package_metadata_create(context, updates):
+ return _package_metadata_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_package_metadata = self._package_metadata_create(context, updates)
+ self._from_db_object(context, self, db_package_metadata)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a package_metadatas.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_package_metadata = context.session.query(models.PackagesMetadata).\
+ filter_by(id=self.id).first()
+ if not db_package_metadata:
+ raise exception.ImagesNotFound(package_metadata_id=self.id)
+ db_package_metadata.update(values)
+ db_package_metadata.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_package_metadata)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _package_metadata_destroy(context, package_metadata_id=None, package_metadataid=None):
+ _package_metadata_destroy(context, package_metadata_id=package_metadata_id, package_metadataid=package_metadataid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a package_metadatas
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a package_metadatas object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._package_metadata_destroy(context, package_metadata_id=self.id)
+ else:
+ self._package_metadata_destroy(context, package_metadataid=self.package_metadataid)
+ #self._from_db_object(context, self, db_package_metadata)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+ db_package_metadata = PackageMetadata._package_metadata_get_query_from_db(context)
+
+ if 'status' in filters:
+ db_package_metadata = db_package_metadata.filter(
+ models.PackagesMetadata.status == filters['status']).first()
+ return cls._from_db_object(context, cls(context), db_package_metadata,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _package_metadata_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all package_metadatass.
+ """
+ filters = filters or {}
+
+ query = PackageMetadata._package_metadata_get_query_from_db(context)
+
+ if 'status' in filters:
+ query = query.filter(
+ models.PackagesMetadata.status == filters['status'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = PackageMetadata._package_metadata_get_query_from_db(context).\
+ filter_by(id=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.PackagesMetadata,
+ limit,
+ [sort_key, 'id'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class PackageMetadataList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('PackageMetadata'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='id', sort_dir='asc', limit=None, marker=None):
+ db_package_metadatas = _package_metadata_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.package_metadata.PackageMetadata,
+ db_package_metadatas,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.PackagesMetadata).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_package_metadata = context.session.query(models.PackagesMetadata).filter_by(auto=True)
+ db_package_metadata.update(values)
diff --git a/gosbs/objects/project.py b/gosbs/objects/project.py
new file mode 100644
index 0000000..1e1917f
--- /dev/null
+++ b/gosbs/objects/project.py
@@ -0,0 +1,279 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it will fit what we need.
+# It need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+PROJECT_STATUS = ['failed', 'completed', 'in-progress', 'waiting', 'stopped']
+
+def _dict_with_extra_specs(project_model):
+ extra_specs = {}
+ return dict(project_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _project_create(context, values):
+ db_project = models.Projects()
+ db_project.update(values)
+
+ try:
+ db_project.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'project_uuid' in e.columns:
+ raise exception.ImagesIdExists(project_uuid=values['projectuuid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_project)
+
+
+@db_api.main_context_manager.writer
+def _project_destroy(context, project_uuid=None, projectuuid=None):
+ query = context.session.query(models.Projects)
+
+ if project_id is not None:
+ query.filter(models.Projects.uuid == project_uuid).delete()
+ else:
+ query.filter(models.Projects.uuid == projectuuid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class Project(base.NovaObject, base.NovaObjectDictCompat, base.NovaPersistentObject2):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'uuid': fields.UUIDField(),
+ 'name': fields.StringField(),
+ 'active' : fields.BooleanField(),
+ 'auto' : fields.BooleanField(),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(Project, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_projects = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(Project, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, project, db_project, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ project._context = context
+ for name, field in project.fields.items():
+ value = db_project[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ project[name] = value
+
+ project.obj_reset_changes()
+ return project
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _project_get_query_from_db(context):
+ query = context.session.query(models.Projects)
+ return query
+
+ @staticmethod
+ @require_context
+ def _project_get_from_db(context, uuid):
+ """Returns a dict describing specific projects."""
+ result = Project._project_get_query_from_db(context).\
+ filter_by(uuid=uuid).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(project_uuid=uuid)
+ return result
+
+ @staticmethod
+ @require_context
+ def _project_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = Project._project_get_query_from_db(context).\
+ filter_by(name=name).\
+ first()
+ if not result:
+ raise exception.FlavorNotFoundByName(projects_name=name)
+ return _dict_with_extra_specs(result)
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(Project, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(Project, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_uuid(cls, context, uuid):
+ db_project = cls._project_get_from_db(context, uuid)
+ return cls._from_db_object(context, cls(context), db_project,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name):
+ db_project = cls._project_get_by_name_from_db(context, name)
+ return cls._from_db_object(context, cls(context), db_project,
+ expected_attrs=[])
+
+ @staticmethod
+ def _project_create(context, updates):
+ return _project_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_project = self._project_create(context, updates)
+ self._from_db_object(context, self, db_project)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a projects.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_project = context.session.query(models.Projects).\
+ filter_by(uuid=self.uuid).first()
+ if not db_project:
+ raise exception.ImagesNotFound(project_uuid=self.uuid)
+ db_project.update(values)
+ db_project.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_project)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _project_destroy(context, project_uuid=None, projectuuid=None):
+ _project_destroy(context, project_uuid=project_uuid, projectuuid=projectuuid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a projects
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a projects object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'uuid' in self:
+ self._project_destroy(context, project_uuid=self.uuid)
+ else:
+ self._project_destroy(context, projectuuid=self.projectuuid)
+ #self._from_db_object(context, self, db_project)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+ db_project = Project._project_get_query_from_db(context)
+
+ if 'status' in filters:
+ db_project = db_project.filter(
+ models.Projects.status == filters['status']).first()
+ return cls._from_db_object(context, cls(context), db_project,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _project_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all projectss.
+ """
+ filters = filters or {}
+
+ query = Project._project_get_query_from_db(context)
+
+ if 'status' in filters:
+ query = query.filter(
+ models.Projects.status == filters['status'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = Project._project_get_query_from_db(context).\
+ filter_by(uuid=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.Projects,
+ limit,
+ [sort_key, 'uuid'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class ProjectList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('Project'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='uuid', sort_dir='asc', limit=None, marker=None):
+ db_projects = _project_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.project.Project,
+ db_projects,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.Projects).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_project = context.session.query(models.Projects).filter_by(auto=True)
+ db_project.update(values)
diff --git a/gosbs/objects/project_build.py b/gosbs/objects/project_build.py
new file mode 100644
index 0000000..e8f1885
--- /dev/null
+++ b/gosbs/objects/project_build.py
@@ -0,0 +1,286 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it will fit what we need.
+# It need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+
+BUILD_STATUS = ['failed', 'completed', 'in-progress', 'waiting']
+def _dict_with_extra_specs(project_build_model):
+ extra_specs = {}
+ return dict(project_build_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _project_build_create(context, values):
+ db_project_build = models.ProjectsBuilds()
+ db_project_build.update(values)
+
+ try:
+ db_project_build.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'project_buildid' in e.columns:
+ raise exception.ImagesIdExists(project_build_id=values['project_buildid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_project_build)
+
+
+@db_api.main_context_manager.writer
+def _project_build_destroy(context, project_build_uuid=None, project_builduuid=None):
+ query = context.session.query(models.ProjectsBuilds)
+
+ if project_build_id is not None:
+ query.filter(models.ProjectsBuilds.uuid == project_build_uuid).delete()
+ else:
+ query.filter(models.ProjectsBuilds.uuid == project_builduuid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class ProjectBuild(base.NovaObject, base.NovaObjectDictCompat, ):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'uuid': fields.UUIDField(),
+ 'user_id': fields.IntegerField(),
+ 'project_uuid': fields.UUIDField(),
+ 'ebuild_uuid': fields.UUIDField(),
+ 'status' : fields.EnumField(valid_values=BUILD_STATUS),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(ProjectBuild, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_project_builds = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(ProjectBuild, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, project_build, db_project_build, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ project_build._context = context
+ for name, field in project_build.fields.items():
+ value = db_project_build[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ project_build[name] = value
+
+ project_build.obj_reset_changes()
+ return project_build
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _project_build_get_query_from_db(context):
+ query = context.session.query(models.ProjectsBuilds)
+ return query
+
+ @staticmethod
+ @require_context
+ def _project_build_get_from_db(context, uuid):
+ """Returns a dict describing specific project_builds."""
+ result = ProjectBuild._project_build_get_query_from_db(context).\
+ filter_by(uuid=uuid).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(project_build_uuid=uuid)
+ return result
+
+ @staticmethod
+ @require_context
+ def _project_build_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = ProjectBuild._project_build_get_query_from_db(context).\
+ filter_by(name=name).\
+ first()
+ if not result:
+ raise exception.FlavorNotFoundByName(project_builds_name=name)
+ return _dict_with_extra_specs(result)
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(ProjectBuild, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(ProjectBuild, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_uuid(cls, context, uuid):
+ db_project_build = cls._project_build_get_from_db(context, uuid)
+ return cls._from_db_object(context, cls(context), db_project_build,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name):
+ db_project_build = cls._project_build_get_by_name_from_db(context, name)
+ return cls._from_db_object(context, cls(context), db_project_build,
+ expected_attrs=[])
+
+ @staticmethod
+ def _project_build_create(context, updates):
+ return _project_build_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_project_build = self._project_build_create(context, updates)
+ self._from_db_object(context, self, db_project_build)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a project_builds.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_project_build = context.session.query(models.ProjectsBuilds).\
+ filter_by(id=self.id).first()
+ if not db_project_build:
+ raise exception.ImagesNotFound(project_build_id=self.id)
+ db_project_build.update(values)
+ db_project_build.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_project_build)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _project_build_destroy(context, project_build_id=None, project_buildid=None):
+ _project_build_destroy(context, project_build_id=project_build_id, project_buildid=project_buildid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a project_builds
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a project_builds object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._project_build_destroy(context, project_build_id=self.id)
+ else:
+ self._project_build_destroy(context, project_buildid=self.project_buildid)
+ #self._from_db_object(context, self, db_project_build)
+
+ @base.remotable_classmethod
+ def get_by_filters(cls, context, filters=None):
+ filters = filters or {}
+ db_project_build = ProjectBuild._project_build_get_query_from_db(context)
+
+ if 'project_uuid' in filters:
+ db_project_build = db_project_build.filter(
+ models.ProjectsBuilds.project_uuid == filters['project_uuid'])
+ if 'repo_uuid' in filters:
+ db_project_build = db_project_build.filter(
+ models.ProjectsBuilds.repo_uuid == filters['repo_uuid'])
+ db_project_build = db_project_build.first()
+ if not db_project_build:
+ return None
+ return cls._from_db_object(context, cls(context), db_project_build,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _project_build_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all project_buildss.
+ """
+ filters = filters or {}
+
+ query = ProjectBuild._project_build_get_query_from_db(context)
+
+ if 'project_uuid' in filters:
+ query = query.filter(
+ models.ProjectsBuilds.project_uuid == filters['project_uuid'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = ProjectBuild._project_build_get_query_from_db(context).\
+ filter_by(id=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.ProjectsBuilds,
+ limit,
+ [sort_key, 'id'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class ProjectBuildList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('ProjectBuild'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='id', sort_dir='asc', limit=None, marker=None):
+ db_project_builds = _project_build_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.project_build.ProjectBuild,
+ db_project_builds,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.ProjectsBuilds).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_project_build = context.session.query(models.ProjectsBuilds).filter_by(auto=True)
+ db_project_build.update(values)
diff --git a/gosbs/objects/project_metadata.py b/gosbs/objects/project_metadata.py
new file mode 100644
index 0000000..4edbb55
--- /dev/null
+++ b/gosbs/objects/project_metadata.py
@@ -0,0 +1,308 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it will fit what we need.
+# It need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+
+def _dict_with_extra_specs(projectmetadata_model):
+ extra_specs = {}
+ return dict(projectmetadata_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _projectmetadata_create(context, values):
+ db_projectmetadata = models.ProjectsMetadata()
+ db_projectmetadata.update(values)
+
+ try:
+ db_projectmetadata.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'projectmetadataid' in e.columns:
+ raise exception.ImagesIdExists(projectmetadata_id=values['projectmetadataid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_projectmetadata)
+
+
+@db_api.main_context_manager.writer
+def _projectmetadata_destroy(context, projectmetadata_id=None, projectmetadataid=None):
+ query = context.session.query(models.ProjectsMetadata)
+
+ if projectmetadata_id is not None:
+ query.filter(models.ProjectsMetadata.id == projectmetadata_id).delete()
+ else:
+ query.filter(models.ProjectsMetadata.id == projectmetadataid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class ProjectMetadata(base.NovaObject, base.NovaObjectDictCompat):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'id': fields.IntegerField(),
+ 'project_uuid': fields.UUIDField(),
+ 'titel': fields.StringField(),
+ 'description' : fields.StringField(),
+ 'project_repo_uuid': fields.UUIDField(),
+ 'project_profile' : fields.StringField(),
+ 'project_profile_repo_uuid': fields.UUIDField(),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(ProjectMetadata, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_projectmetadatas = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(ProjectMetadata, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, projectmetadata, db_projectmetadata, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ projectmetadata._context = context
+ for name, field in projectmetadata.fields.items():
+ value = db_projectmetadata[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ projectmetadata[name] = value
+
+ projectmetadata.obj_reset_changes()
+ return projectmetadata
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _projectmetadata_get_query_from_db(context):
+ query = context.session.query(models.ProjectsMetadata)
+ return query
+
+ @staticmethod
+ @require_context
+ def _projectmetadata_get_from_db(context, id):
+ """Returns a dict describing specific projectmetadatas."""
+ result = ProjectMetadata._projectmetadata_get_query_from_db(context).\
+ filter_by(id=id).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(projectmetadata_id=id)
+ return result
+
+ @staticmethod
+ @require_context
+ def _projectmetadata_get_from_db(context, id):
+ """Returns a dict describing specific projectmetadatas."""
+ result = ProjectMetadata._projectmetadata_get_query_from_db(context).\
+ filter_by(id=id).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(projectmetadata_id=id)
+ return result
+
+ @staticmethod
+ @require_context
+ def _projectmetadata_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = ProjectMetadata._projectmetadata_get_query_from_db(context).\
+ filter_by(name=name).\
+ first()
+ if not result:
+ raise exception.FlavorNotFoundByName(projectmetadatas_name=name)
+ return _dict_with_extra_specs(result)
+
+ @staticmethod
+ @require_context
+ def _projectmetadata_get_by_uuid_from_db(context, uuid):
+ """Returns a dict describing specific flavor."""
+ result = ProjectMetadata._projectmetadata_get_query_from_db(context).\
+ filter_by(project_uuid=uuid).\
+ first()
+ if not result:
+ raise exception.FlavorNotFoundByName(projectmetadatas_name=name)
+ return _dict_with_extra_specs(result)
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(ProjectMetadata, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(ProjectMetadata, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_id(cls, context, id):
+ db_projectmetadata = cls._projectmetadata_get_from_db(context, id)
+ return cls._from_db_object(context, cls(context), db_projectmetadata,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name):
+ db_projectmetadata = cls._projectmetadata_get_by_name_from_db(context, name)
+ return cls._from_db_object(context, cls(context), db_projectmetadata,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_uuid(cls, context, uuid):
+ db_projectmetadata = cls._projectmetadata_get_by_uuid_from_db(context, uuid)
+ return cls._from_db_object(context, cls(context), db_projectmetadata,
+ expected_attrs=[])
+
+ @staticmethod
+ def _projectmetadata_create(context, updates):
+ return _projectmetadata_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_projectmetadata = self._projectmetadata_create(context, updates)
+ self._from_db_object(context, self, db_projectmetadata)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a projectmetadatas.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_projectmetadata = context.session.query(models.ProjectsMetadata).\
+ filter_by(id=self.id).first()
+ if not db_projectmetadata:
+ raise exception.ImagesNotFound(projectmetadata_id=self.id)
+ db_projectmetadata.update(values)
+ db_projectmetadata.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_projectmetadata)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _projectmetadata_destroy(context, projectmetadata_id=None, projectmetadataid=None):
+ _projectmetadata_destroy(context, projectmetadata_id=projectmetadata_id, projectmetadataid=projectmetadataid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a projectmetadatas
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a projectmetadatas object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._projectmetadata_destroy(context, projectmetadata_id=self.id)
+ else:
+ self._projectmetadata_destroy(context, projectmetadataid=self.projectmetadataid)
+ #self._from_db_object(context, self, db_projectmetadata)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+ db_projectmetadata = ProjectMetadata._projectmetadata_get_query_from_db(context)
+
+ if 'status' in filters:
+ db_projectmetadata = db_projectmetadata.filter(
+ models.ProjectsMetadata.status == filters['status']).first()
+ return cls._from_db_object(context, cls(context), db_projectmetadata,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _projectmetadata_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all projectmetadatass.
+ """
+ filters = filters or {}
+
+ query = ProjectMetadata._projectmetadata_get_query_from_db(context)
+
+ if 'status' in filters:
+ query = query.filter(
+ models.ProjectsMetadata.status == filters['status'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = ProjectMetadata._projectmetadata_get_query_from_db(context).\
+ filter_by(id=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.ProjectsMetadata,
+ limit,
+ [sort_key, 'id'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class ProjectMetadataList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('ProjectMetadata'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='id', sort_dir='asc', limit=None, marker=None):
+ db_projectmetadatas = _projectmetadata_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.projectmetadata.ProjectMetadata,
+ db_projectmetadatas,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.ProjectsMetadata).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_projectmetadata = context.session.query(models.ProjectsMetadata).filter_by(auto=True)
+ db_projectmetadata.update(values)
diff --git a/gosbs/objects/project_repo.py b/gosbs/objects/project_repo.py
new file mode 100644
index 0000000..85e458d
--- /dev/null
+++ b/gosbs/objects/project_repo.py
@@ -0,0 +1,295 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it will fit what we need.
+# It need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+
+def _dict_with_extra_specs(project_repo_model):
+ extra_specs = {}
+ return dict(project_repo_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _project_repo_create(context, values):
+ db_project_repo = models.ProjectsRepos()
+ db_project_repo.update(values)
+
+ try:
+ db_project_repo.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'project_repoid' in e.columns:
+ raise exception.ImagesIdExists(project_repo_id=values['project_repoid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_project_repo)
+
+
+@db_api.main_context_manager.writer
+def _project_repo_destroy(context, project_repo_id=None, project_repoid=None):
+ query = context.session.query(models.ProjectsRepos)
+
+ if project_repo_id is not None:
+ query.filter(models.ProjectsRepos.uuid == project_repo_id).delete()
+ else:
+ query.filter(models.ProjectsRepos.uuid == project_repoid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class ProjectRepo(base.NovaObject, base.NovaObjectDictCompat, ):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'id': fields.IntegerField(),
+ 'repo_uuid': fields.UUIDField(),
+ 'project_uuid': fields.UUIDField(),
+ 'auto' : fields.BooleanField(),
+ 'build' : fields.BooleanField(),
+ 'test' : fields.BooleanField(),
+ 'qa' : fields.BooleanField(),
+ 'depclean' : fields.BooleanField(),
+ 'repoman' : fields.BooleanField(),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(ProjectRepo, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_project_repos = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(ProjectRepo, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, project_repo, db_project_repo, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ project_repo._context = context
+ for name, field in project_repo.fields.items():
+ value = db_project_repo[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ project_repo[name] = value
+
+ project_repo.obj_reset_changes()
+ return project_repo
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _project_repo_get_query_from_db(context):
+ query = context.session.query(models.ProjectsRepos)
+ return query
+
+ @staticmethod
+ @require_context
+ def _project_repo_get_from_db(context, id):
+ """Returns a dict describing specific project_repos."""
+ result = ProjectRepo._project_repo_get_query_from_db(context).\
+ filter_by(uuid=id).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(project_repo_id=id)
+ return result
+
+ @staticmethod
+ @require_context
+ def _project_repo_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = ProjectRepo._project_repo_get_query_from_db(context).\
+ filter_by(name=name).\
+ first()
+ if not result:
+ raise exception.FlavorNotFoundByName(project_repos_name=name)
+ return _dict_with_extra_specs(result)
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(ProjectRepo, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(ProjectRepo, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_id(cls, context, id):
+ db_project_repo = cls._project_repo_get_from_db(context, id)
+ return cls._from_db_object(context, cls(context), db_project_repo,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name):
+ db_project_repo = cls._project_repo_get_by_name_from_db(context, name)
+ return cls._from_db_object(context, cls(context), db_project_repo,
+ expected_attrs=[])
+
+ @staticmethod
+ def _project_repo_create(context, updates):
+ return _project_repo_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_project_repo = self._project_repo_create(context, updates)
+ self._from_db_object(context, self, db_project_repo)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a project_repos.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_project_repo = context.session.query(models.ProjectsRepos).\
+ filter_by(uuid=self.id).first()
+ if not db_project_repo:
+ raise exception.ImagesNotFound(project_repo_id=self.id)
+ db_project_repo.update(values)
+ db_project_repo.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_project_repo)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _project_repo_destroy(context, project_repo_id=None, project_repoid=None):
+ _project_repo_destroy(context, project_repo_id=project_repo_id, project_repoid=project_repoid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a project_repos
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a project_repos object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._project_repo_destroy(context, project_repo_id=self.id)
+ else:
+ self._project_repo_destroy(context, project_repoid=self.project_repoid)
+ #self._from_db_object(context, self, db_project_repo)
+
+ @base.remotable_classmethod
+ def get_by_filters(cls, context, filters=None):
+ filters = filters or {}
+ db_project_repo = ProjectRepo._project_repo_get_query_from_db(context)
+
+ if 'project_uuid' in filters:
+ db_project_repo = db_project_repo.filter(
+ models.ProjectsRepos.project_uuid == filters['project_uuid'])
+ if 'repo_uuid' in filters:
+ db_project_repo = db_project_repo.filter(
+ models.ProjectsRepos.repo_uuid == filters['repo_uuid'])
+ db_project_repo = db_project_repo.first()
+ if not db_project_repo:
+ return None
+ return cls._from_db_object(context, cls(context), db_project_repo,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _project_repo_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all project_reposs.
+ """
+ filters = filters or {}
+
+ query = ProjectRepo._project_repo_get_query_from_db(context)
+
+ if 'project_uuid' in filters:
+ query = query.filter(
+ models.ProjectsRepos.project_uuid == filters['project_uuid'])
+ if 'repo_uuid' in filters:
+ query = query.filter(
+ models.ProjectsRepos.repo_uuid == filters['repo_uuid'])
+ if 'build' in filters:
+ query = query.filter(
+ models.ProjectsRepos.build == filters['build'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = ProjectRepo._project_repo_get_query_from_db(context).\
+ filter_by(id=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.ProjectsRepos,
+ limit,
+ [sort_key, 'id'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class ProjectRepoList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('ProjectRepo'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='id', sort_dir='asc', limit=None, marker=None):
+ db_project_repos = _project_repo_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.project_repo.ProjectRepo,
+ db_project_repos,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.ProjectsRepos).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_project_repo = context.session.query(models.ProjectsRepos).filter_by(auto=True)
+ db_project_repo.update(values)
diff --git a/gosbs/objects/repo.py b/gosbs/objects/repo.py
new file mode 100644
index 0000000..747016d
--- /dev/null
+++ b/gosbs/objects/repo.py
@@ -0,0 +1,290 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it will fit what we need.
+# It need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+REPO_STATUSES = ['failed', 'completed', 'in-progress', 'waiting']
+REPO2_STATUSES = ['failed', 'completed', 'in-progress', 'waiting', 'db_rebuild']
+REPO_TYPE = ['ebuild', 'project']
+
+def _dict_with_extra_specs(repo_model):
+ extra_specs = {}
+ return dict(repo_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _repo_create(context, values):
+ db_repo = models.Repos()
+ db_repo.update(values)
+
+ try:
+ db_repo.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'repoid' in e.columns:
+ raise exception.ImagesIdExists(repo_id=values['repoid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_repo)
+
+
+@db_api.main_context_manager.writer
+def _repo_destroy(context, repo_id=None, repoid=None):
+ query = context.session.query(models.Repos)
+
+ if repo_id is not None:
+ query.filter(models.Repos.uuid == repo_uuid).delete()
+ else:
+ query.filter(models.Repos.repouuid == repouuid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class Repo(base.NovaObject, base.NovaObjectDictCompat, base.NovaPersistentObject2):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'uuid': fields.UUIDField(),
+ 'name': fields.StringField(),
+ 'status' : fields.EnumField(valid_values=REPO_STATUSES),
+ 'description' : fields.StringField(),
+ 'src_url': fields.StringField(),
+ 'auto' : fields.BooleanField(),
+ 'repo_type' : fields.EnumField(valid_values=REPO_TYPE),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(Repo, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_projects = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(Repo, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, repo, db_repo, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ repo._context = context
+ for name, field in repo.fields.items():
+ value = db_repo[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ repo[name] = value
+
+ repo.obj_reset_changes()
+ return repo
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _repo_get_query_from_db(context):
+ query = context.session.query(models.Repos)
+ return query
+
+ @staticmethod
+ @require_context
+ def _repo_get_from_db(context, uuid):
+ """Returns a dict describing specific repos."""
+ result = Repo._repo_get_query_from_db(context).\
+ filter_by(uuid=uuid).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(repo_id=uuid)
+ return result
+
+ @staticmethod
+ @require_context
+ def _repo_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = Repo._repo_get_query_from_db(context).\
+ filter_by(name=name).\
+ first()
+ if not result:
+ raise exception.FlavorNotFoundByName(repos_name=name)
+ return _dict_with_extra_specs(result)
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(Repo, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(Repo, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_uuid(cls, context, uuid):
+ db_repo = cls._repo_get_from_db(context, uuid)
+ return cls._from_db_object(context, cls(context), db_repo,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name):
+ db_repo = cls._repo_get_by_name_from_db(context, name)
+ return cls._from_db_object(context, cls(context), db_repo,
+ expected_attrs=[])
+
+ @staticmethod
+ def _repo_create(context, updates):
+ return _repo_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_repo = self._repo_create(context, updates)
+ self._from_db_object(context, self, db_repo)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a repos.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_repo = context.session.query(models.Repos).\
+ filter_by(uuid=self.uuid).first()
+ if not db_repo:
+ raise exception.ImagesNotFound(repo_uuid=self.uuid)
+ db_repo.update(values)
+ db_repo.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_repo)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _repo_destroy(context, repo_uuid=None, repouuid=None):
+ _repo_destroy(context, repo_uuid=repo_uuid, repouuid=repouuid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a repos
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a repos object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'uuid' in self:
+ self._repo_destroy(context, repo_uuid=self.uuid)
+ else:
+ self._repo_destroy(context, repouuid=self.repouuid)
+ #self._from_db_object(context, self, db_repo)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+ db_repo = Repo._repo_get_query_from_db(context)
+
+ if 'status' in filters:
+ db_repo = db_repo.filter(
+ models.Repos.status == filters['status']).first()
+ return cls._from_db_object(context, cls(context), db_repo,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _repo_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all reposs.
+ """
+ filters = filters or {}
+
+ query = Repo._repo_get_query_from_db(context)
+
+ if 'status' in filters:
+ query = query.filter(
+ models.Repos.status == filters['status'])
+ if 'repo_type' in filters:
+ query = query.filter(
+ models.Repos.repo_type == filters['repo_type'])
+ if 'deleted' in filters:
+ query = query.filter(
+ models.Repos.deleted == filters['deleted'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = Repo._repo_get_query_from_db(context).\
+ filter_by(id=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.Repos,
+ limit,
+ [sort_key, 'uuid'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class RepoList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('Repo'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='uuid', sort_dir='asc', limit=None, marker=None):
+ db_repos = _repo_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.repo.Repo,
+ db_repos,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.Repos).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_repo = context.session.query(models.Repos).filter_by(auto=True)
+ db_repo.update(values)
diff --git a/gosbs/objects/restriction.py b/gosbs/objects/restriction.py
new file mode 100644
index 0000000..4aede29
--- /dev/null
+++ b/gosbs/objects/restriction.py
@@ -0,0 +1,281 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it will fit what we need.
+# It need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+
+def _dict_with_extra_specs(restriction_model):
+ extra_specs = {}
+ return dict(restriction_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _restriction_create(context, values):
+ db_restriction = models.Restrictions()
+ db_restriction.update(values)
+
+ try:
+ db_restriction.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'restrictionid' in e.columns:
+ raise exception.ImagesIdExists(restriction_id=values['restrictionid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_restriction)
+
+
+@db_api.main_context_manager.writer
+def _restriction_destroy(context, restriction_id=None, restrictionid=None):
+ query = context.session.query(models.Restrictions)
+
+ if restriction_id is not None:
+ query.filter(models.Restrictions.id == restriction_id).delete()
+ else:
+ query.filter(models.Restrictions.id == restrictionid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class Restriction(base.NovaObject, base.NovaObjectDictCompat, base.NovaPersistentObject):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'id': fields.IntegerField(),
+ 'restriction': fields.StringField(),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(Restriction, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_restrictions = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(Restriction, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, restriction, db_restriction, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ restriction._context = context
+ for name, field in restriction.fields.items():
+ value = db_restriction[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ restriction[name] = value
+
+ restriction.obj_reset_changes()
+ return restriction
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _restriction_get_query_from_db(context):
+ query = context.session.query(models.Restrictions)
+ return query
+
+ @staticmethod
+ @require_context
+ def _restriction_get_from_db(context, id):
+ """Returns a dict describing specific restrictions."""
+ result = Restriction._restriction_get_query_from_db(context).\
+ filter_by(id=id).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(restriction_id=id)
+ return result
+
+ @staticmethod
+ @require_context
+ def _restriction_get_by_restriction_from_db(context, restriction):
+ """Returns a dict describing specific flavor."""
+ result = Restriction._restriction_get_query_from_db(context).\
+ filter_by(restriction=restriction).\
+ first()
+ if not result:
+ raise exception.FlavorNotFoundByName(restrictions_restriction=restriction)
+ return _dict_with_extra_specs(result)
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(Restriction, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(Restriction, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_id(cls, context, id):
+ db_restriction = cls._restriction_get_from_db(context, id)
+ return cls._from_db_object(context, cls(context), db_restriction,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, restriction):
+ db_restriction = Restriction._restriction_get_query_from_db(context)
+ db_restriction = db_restriction.filter_by(restriction=restriction)
+ db_restriction = db_restriction.first()
+ if not db_restriction:
+ return None
+ return cls._from_db_object(context, cls(context), db_restriction,
+ expected_attrs=[])
+
+ @staticmethod
+ def _restriction_create(context, updates):
+ return _restriction_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_restriction = self._restriction_create(context, updates)
+ self._from_db_object(context, self, db_restriction)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a restrictions.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_restriction = context.session.query(models.Restrictions).\
+ filter_by(id=self.id).first()
+ if not db_restriction:
+ raise exception.ImagesNotFound(restriction_id=self.id)
+ db_restriction.update(values)
+ db_restriction.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_restriction)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _restriction_destroy(context, restriction_id=None, restrictionid=None):
+ _restriction_destroy(context, restriction_id=restriction_id, restrictionid=restrictionid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a restrictions
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a restrictions object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._restriction_destroy(context, restriction_id=self.id)
+ else:
+ self._restriction_destroy(context, restrictionid=self.restrictionid)
+ #self._from_db_object(context, self, db_restriction)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+ print('foo')
+ db_restriction = Restriction._restriction_get_query_from_db(context)
+
+ if 'status' in filters:
+ db_restriction = db_restriction.filter(
+ models.Restrictions.status == filters['status']).first()
+ return cls._from_db_object(context, cls(context), db_restriction,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _restriction_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all restrictionss.
+ """
+ filters = filters or {}
+
+ query = Restriction._restriction_get_query_from_db(context)
+
+ if 'status' in filters:
+ query = query.filter(
+ models.Restrictions.status == filters['status'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = Restriction._restriction_get_query_from_db(context).\
+ filter_by(id=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.Restrictions,
+ limit,
+ [sort_key, 'uuid'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class RestrictionList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('Restriction'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='id', sort_dir='asc', limit=None, marker=None):
+ db_restrictions = _restriction_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.restriction.Restriction,
+ db_restrictions,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.Restrictions).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_restriction = context.session.query(models.Restrictions).filter_by(auto=True)
+ db_restriction.update(values)
diff --git a/gosbs/objects/service.py b/gosbs/objects/service.py
new file mode 100644
index 0000000..1fc0c4f
--- /dev/null
+++ b/gosbs/objects/service.py
@@ -0,0 +1,486 @@
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/service.py
+# We have change the code so it will fit what we need.
+# It need more cleaning.
+
+from oslo_log import log as logging
+from oslo_utils import uuidutils
+from oslo_utils import versionutils
+
+from gosbs import context as gosbs_context
+from gosbs.db import api as db
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+
+LOG = logging.getLogger(__name__)
+
+
+# NOTE(danms): This is the global service version counter
+SERVICE_VERSION = 36
+
+
+# NOTE(danms): This is our SERVICE_VERSION history. The idea is that any
+# time we bump the version, we will put an entry here to record the change,
+# along with any pertinent data. For things that we can programatically
+# detect that need a bump, we put something in _collect_things() below to
+# assemble a dict of things we can check. For example, we pretty much always
+# want to consider the compute RPC API version a thing that requires a service
+# bump so that we can drive version pins from it. We could include other
+# service RPC versions at some point, minimum object versions, etc.
+#
+# The TestServiceVersion test will fail if the calculated set of
+# things differs from the value in the last item of the list below,
+# indicating that a version bump is needed.
+#
+# Also note that there are other reasons we may want to bump this,
+# which will not be caught by the test. An example of this would be
+# triggering (or disabling) an online data migration once all services
+# in the cluster are at the same level.
+#
+# If a version bump is required for something mechanical, just document
+# that generic thing here (like compute RPC version bumps). No need to
+# replicate the details from compute/rpcapi.py here. However, for more
+# complex service interactions, extra detail should be provided
+SERVICE_VERSION_HISTORY = (
+ # Version 0: Pre-history
+ {'scheduler_rpc': '1.0'},
+)
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+@base.NovaObjectRegistry.register
+class Service(base.NovaPersistentObject, base.NovaObject,
+ base.NovaObjectDictCompat):
+ # Version 1.0: Initial version
+ VERSION = '1.0'
+
+ fields = {
+ 'id': fields.IntegerField(read_only=True),
+ 'uuid': fields.UUIDField(),
+ 'host': fields.StringField(nullable=True),
+ 'binary': fields.StringField(nullable=True),
+ 'topic': fields.StringField(nullable=True),
+ 'report_count': fields.IntegerField(),
+ 'disabled': fields.BooleanField(),
+ 'disabled_reason': fields.StringField(nullable=True),
+ #'availability_zone': fields.StringField(nullable=True),
+ #'compute_node': fields.ObjectField('ComputeNode'),
+ 'last_seen_up': fields.DateTimeField(nullable=True),
+ 'forced_down': fields.BooleanField(),
+ 'version': fields.IntegerField(),
+ }
+
+ _MIN_VERSION_CACHE = {}
+ _SERVICE_VERSION_CACHING = False
+
+ def __init__(self, *args, **kwargs):
+ # NOTE(danms): We're going against the rules here and overriding
+ # init. The reason is that we want to *ensure* that we're always
+ # setting the current service version on our objects, overriding
+ # whatever else might be set in the database, or otherwise (which
+ # is the normal reason not to override init).
+ #
+ # We also need to do this here so that it's set on the client side
+ # all the time, such that create() and save() operations will
+ # include the current service version.
+ if 'version' in kwargs:
+ raise exception.ObjectActionError(
+ action='init',
+ reason='Version field is immutable')
+
+ super(Service, self).__init__(*args, **kwargs)
+ self.version = SERVICE_VERSION
+
+ def obj_make_compatible_from_manifest(self, primitive, target_version,
+ version_manifest):
+ super(Service, self).obj_make_compatible_from_manifest(
+ primitive, target_version, version_manifest)
+ _target_version = versionutils.convert_version_to_tuple(target_version)
+ if _target_version < (1, 21) and 'uuid' in primitive:
+ del primitive['uuid']
+ if _target_version < (1, 16) and 'version' in primitive:
+ del primitive['version']
+ if _target_version < (1, 14) and 'forced_down' in primitive:
+ del primitive['forced_down']
+ if _target_version < (1, 13) and 'last_seen_up' in primitive:
+ del primitive['last_seen_up']
+ if _target_version < (1, 10):
+ # service.compute_node was not lazy-loaded, we need to provide it
+ # when called
+ self._do_compute_node(self._context, primitive,
+ version_manifest)
+
+ def _do_compute_node(self, context, primitive, version_manifest):
+ try:
+ target_version = version_manifest['ComputeNode']
+ # NOTE(sbauza): Ironic deployments can have multiple
+ # nodes for the same service, but for keeping same behaviour,
+ # returning only the first elem of the list
+ compute = objects.ComputeNodeList.get_all_by_host(
+ context, primitive['host'])[0]
+ except Exception:
+ return
+ primitive['compute_node'] = compute.obj_to_primitive(
+ target_version=target_version,
+ version_manifest=version_manifest)
+
+ @staticmethod
+ def _from_db_object(context, service, db_service):
+ allow_missing = ('availability_zone',)
+ for key in service.fields:
+ if key in allow_missing and key not in db_service:
+ continue
+ if key == 'compute_node':
+ # NOTE(sbauza); We want to only lazy-load compute_node
+ continue
+ elif key == 'version':
+ # NOTE(danms): Special handling of the version field, since
+ # it is read_only and set in our init.
+ setattr(service, base.get_attrname(key), db_service[key])
+ elif key == 'uuid' and not db_service.get(key):
+ # Leave uuid off the object if undefined in the database
+ # so that it will be generated below.
+ continue
+ else:
+ service[key] = db_service[key]
+
+ service._context = context
+ service.obj_reset_changes()
+
+ # TODO(dpeschman): Drop this once all services have uuids in database
+ if 'uuid' not in service:
+ service.uuid = uuidutils.generate_uuid()
+ LOG.debug('Generated UUID %(uuid)s for service %(id)i',
+ dict(uuid=service.uuid, id=service.id))
+ service.save()
+
+ return service
+
+ def obj_load_attr(self, attrname):
+ if not self._context:
+ raise exception.OrphanedObjectError(method='obj_load_attr',
+ objtype=self.obj_name())
+
+ LOG.debug("Lazy-loading '%(attr)s' on %(name)s id %(id)s",
+ {'attr': attrname,
+ 'name': self.obj_name(),
+ 'id': self.id,
+ })
+ if attrname != 'compute_node':
+ raise exception.ObjectActionError(
+ action='obj_load_attr',
+ reason='attribute %s not lazy-loadable' % attrname)
+ if self.binary == 'nova-compute':
+ # Only n-cpu services have attached compute_node(s)
+ compute_nodes = objects.ComputeNodeList.get_all_by_host(
+ self._context, self.host)
+ else:
+ # NOTE(sbauza); Previous behaviour was raising a ServiceNotFound,
+ # we keep it for backwards compatibility
+ raise exception.ServiceNotFound(service_id=self.id)
+ # NOTE(sbauza): Ironic deployments can have multiple nodes
+ # for the same service, but for keeping same behaviour, returning only
+ # the first elem of the list
+ #self.compute_node = compute_nodes[0]
+
+ @base.remotable_classmethod
+ def get_by_id(cls, context, service_id):
+ db_service = db.service_get(context, service_id)
+ return cls._from_db_object(context, cls(), db_service)
+
+ @base.remotable_classmethod
+ def get_by_uuid(cls, context, service_uuid):
+ db_service = db.service_get_by_uuid(context, service_uuid)
+ return cls._from_db_object(context, cls(), db_service)
+
+ @base.remotable_classmethod
+ def get_by_host_and_topic(cls, context, host, topic):
+ db_service = db.service_get_by_host_and_topic(context, host, topic)
+ return cls._from_db_object(context, cls(), db_service)
+
+ @base.remotable_classmethod
+ def get_by_topic(cls, context, topic):
+ db_service = db.service_get_by_topic(context, topic)
+ return cls._from_db_object(context, cls(), db_service)
+
+ @base.remotable_classmethod
+ def get_by_host_and_binary(cls, context, host, binary):
+ try:
+ db_service = db.service_get_by_host_and_binary(context,
+ host, binary)
+ except exception.HostBinaryNotFound:
+ return
+ return cls._from_db_object(context, cls(), db_service)
+
+ @staticmethod
+ @db.select_db_reader_mode
+ def _db_service_get_by_compute_host(context, host, use_slave=False):
+ return db.service_get_by_compute_host(context, host)
+
+ @base.remotable_classmethod
+ def get_by_compute_host(cls, context, host, use_slave=False):
+ db_service = cls._db_service_get_by_compute_host(context, host,
+ use_slave=use_slave)
+ return cls._from_db_object(context, cls(), db_service)
+
+ # NOTE(ndipanov): This is deprecated and should be removed on the next
+ # major version bump
+ @base.remotable_classmethod
+ def get_by_args(cls, context, host, binary):
+ db_service = db.service_get_by_host_and_binary(context, host, binary)
+ return cls._from_db_object(context, cls(), db_service)
+
+ def _check_minimum_version(self):
+ """Enforce that we are not older that the minimum version.
+
+ This is a loose check to avoid creating or updating our service
+ record if we would do so with a version that is older that the current
+ minimum of all services. This could happen if we were started with
+ older code by accident, either due to a rollback or an old and
+ un-updated node suddenly coming back onto the network.
+
+ There is technically a race here between the check and the update,
+ but since the minimum version should always roll forward and never
+ backwards, we don't need to worry about doing it atomically. Further,
+ the consequence for getting this wrong is minor, in that we'll just
+ fail to send messages that other services understand.
+ """
+ if not self.obj_attr_is_set('version'):
+ return
+ if not self.obj_attr_is_set('binary'):
+ return
+ minver = self.get_minimum_version(self._context, self.binary)
+ if minver > self.version:
+ raise exception.ServiceTooOld(thisver=self.version,
+ minver=minver)
+
+ @base.remotable
+ def create(self):
+ if self.obj_attr_is_set('id'):
+ raise exception.ObjectActionError(action='create',
+ reason='already created')
+ self._check_minimum_version()
+ updates = self.obj_get_changes()
+
+ if 'uuid' not in updates:
+ updates['uuid'] = uuidutils.generate_uuid()
+ self.uuid = updates['uuid']
+
+ db_service = db.service_create(self._context, updates)
+ self._from_db_object(self._context, self, db_service)
+
+ @base.remotable
+ def save(self):
+ updates = self.obj_get_changes()
+ updates.pop('id', None)
+ self._check_minimum_version()
+ db_service = db.service_update(self._context, self.id, updates)
+ self._from_db_object(self._context, self, db_service)
+
+ #self._send_status_update_notification(updates)
+
+ def _send_status_update_notification(self, updates):
+ # Note(gibi): We do not trigger notification on version as that field
+ # is always dirty, which would cause that nova sends notification on
+ # every other field change. See the comment in save() too.
+ if set(updates.keys()).intersection(
+ {'disabled', 'disabled_reason', 'forced_down'}):
+ self._send_notification(fields.NotificationAction.UPDATE)
+
+ def _send_notification(self, action):
+ payload = service_notification.ServiceStatusPayload(self)
+ service_notification.ServiceStatusNotification(
+ publisher=notification.NotificationPublisher.from_service_obj(
+ self),
+ event_type=notification.EventType(
+ object='service',
+ action=action),
+ priority=fields.NotificationPriority.INFO,
+ payload=payload).emit(self._context)
+
+ @base.remotable
+ def destroy(self):
+ db.service_destroy(self._context, self.id)
+ #self._send_notification(fields.NotificationAction.DELETE)
+
+ @classmethod
+ def enable_min_version_cache(cls):
+ cls.clear_min_version_cache()
+ cls._SERVICE_VERSION_CACHING = True
+
+ @classmethod
+ def clear_min_version_cache(cls):
+ cls._MIN_VERSION_CACHE = {}
+
+ @staticmethod
+ @db.select_db_reader_mode
+ def _db_service_get_minimum_version(context, binaries, use_slave=False):
+ return db.service_get_minimum_version(context, binaries)
+
+ @base.remotable_classmethod
+ def get_minimum_version_multi(cls, context, binaries, use_slave=False):
+ if not all(binary.startswith('gosbs-') for binary in binaries):
+ LOG.warning('get_minimum_version called with likely-incorrect '
+ 'binaries `%s\'', ','.join(binaries))
+ raise exception.ObjectActionError(action='get_minimum_version',
+ reason='Invalid binary prefix')
+
+ if (not cls._SERVICE_VERSION_CACHING or
+ any(binary not in cls._MIN_VERSION_CACHE
+ for binary in binaries)):
+ min_versions = cls._db_service_get_minimum_version(
+ context, binaries, use_slave=use_slave)
+ if min_versions:
+ min_versions = {binary: version or 0
+ for binary, version in
+ min_versions.items()}
+ cls._MIN_VERSION_CACHE.update(min_versions)
+ else:
+ min_versions = {binary: cls._MIN_VERSION_CACHE[binary]
+ for binary in binaries}
+
+ if min_versions:
+ version = min(min_versions.values())
+ else:
+ version = 0
+ # NOTE(danms): Since our return value is not controlled by object
+ # schema, be explicit here.
+ version = int(version)
+
+ return version
+
+ @base.remotable_classmethod
+ def get_minimum_version(cls, context, binary, use_slave=False):
+ return cls.get_minimum_version_multi(context, [binary],
+ use_slave=use_slave)
+
+
+def get_minimum_version_all_cells(context, binaries, require_all=False):
+ """Get the minimum service version, checking all cells.
+
+ This attempts to calculate the minimum service version for a set
+ of binaries across all the cells in the system. If require_all
+ is False, then any cells that fail to report a version will be
+ ignored (assuming they won't be candidates for scheduling and thus
+ excluding them from the minimum version calculation is reasonable).
+ If require_all is True, then a failing cell will cause this to raise
+ exception.CellTimeout, as would be appropriate for gating some
+ data migration until everything is new enough.
+
+ Note that services that do not report a positive version are excluded
+ from this, as it crosses all cells which will naturally not have all
+ services.
+ """
+
+ if not all(binary.startswith('gosbs-') for binary in binaries):
+ LOG.warning('get_minimum_version_all_cells called with '
+ 'likely-incorrect binaries `%s\'', ','.join(binaries))
+ raise exception.ObjectActionError(
+ action='get_minimum_version_all_cells',
+ reason='Invalid binary prefix')
+
+ # NOTE(danms): Instead of using Service.get_minimum_version_multi(), we
+ # replicate the call directly to the underlying DB method here because
+ # we want to defeat the caching and we need to filter non-present
+ # services differently from the single-cell method.
+
+ results = nova_context.scatter_gather_all_cells(
+ context,
+ Service._db_service_get_minimum_version,
+ binaries)
+
+ min_version = None
+ for cell_uuid, result in results.items():
+ if result is nova_context.did_not_respond_sentinel:
+ LOG.warning('Cell %s did not respond when getting minimum '
+ 'service version', cell_uuid)
+ if require_all:
+ raise exception.CellTimeout()
+ elif isinstance(result, Exception):
+ LOG.warning('Failed to get minimum service version for cell %s',
+ cell_uuid)
+ if require_all:
+ # NOTE(danms): Okay, this isn't necessarily a timeout, but
+ # it's functionally the same from the caller's perspective
+ # and we logged the fact that it was actually a failure
+ # for the forensic investigator during the scatter/gather
+ # routine.
+ raise exception.CellTimeout()
+ else:
+ # NOTE(danms): Don't consider a zero or None result as the minimum
+ # since we're crossing cells and will likely not have all the
+ # services being probed.
+ relevant_versions = [version for version in result.values()
+ if version]
+ if relevant_versions:
+ min_version_cell = min(relevant_versions)
+ min_version = (min(min_version, min_version_cell)
+ if min_version else min_version_cell)
+
+ # NOTE(danms): If we got no matches at all (such as at first startup)
+ # then report that as zero to be consistent with the other such
+ # methods.
+ return min_version or 0
+
+
+@base.NovaObjectRegistry.register
+class ServiceList(base.ObjectListBase, base.NovaObject):
+ # Version 1.0: Initial version
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('Service'),
+ }
+
+ @base.remotable_classmethod
+ def get_by_topic(cls, context, topic):
+ db_services = db.service_get_all_by_topic(context, topic)
+ return base.obj_make_list(context, cls(context), objects.Service,
+ db_services)
+
+ # NOTE(paul-carlton2): In v2.0 of the object the include_disabled flag
+ # will be removed so both enabled and disabled hosts are returned
+ @base.remotable_classmethod
+ def get_by_binary(cls, context, binary, include_disabled=False):
+ db_services = db.service_get_all_by_binary(
+ context, binary, include_disabled=include_disabled)
+ return base.obj_make_list(context, cls(context), objects.Service,
+ db_services)
+
+ @base.remotable_classmethod
+ def get_by_host(cls, context, host):
+ db_services = db.service_get_all_by_host(context, host)
+ return base.obj_make_list(context, cls(context), objects.Service,
+ db_services)
+
+ @base.remotable_classmethod
+ def get_all(cls, context, disabled=None, set_zones=False):
+ db_services = db.service_get_all(context, disabled=disabled)
+ if set_zones:
+ db_services = availability_zones.set_availability_zones(
+ context, db_services)
+ return base.obj_make_list(context, cls(context), objects.Service,
+ db_services)
+
+ @base.remotable_classmethod
+ def get_all_computes_by_hv_type(cls, context, hv_type):
+ db_services = db.service_get_all_computes_by_hv_type(
+ context, hv_type, include_disabled=False)
+ return base.obj_make_list(context, cls(context), objects.Service,
+ db_services)
diff --git a/gosbs/objects/service_repo.py b/gosbs/objects/service_repo.py
new file mode 100644
index 0000000..f4761aa
--- /dev/null
+++ b/gosbs/objects/service_repo.py
@@ -0,0 +1,296 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it will fit what we need.
+# It need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+REPO_STATUSES = ['failed', 'completed', 'in-progress', 'waiting', 'update_db', 'rebuild_db']
+
+def _dict_with_extra_specs(service_repo_model):
+ extra_specs = {}
+ return dict(service_repo_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _service_repo_create(context, values):
+ db_service_repo = models.ServicesRepos()
+ db_service_repo.update(values)
+
+ try:
+ db_service_repo.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'service_repoid' in e.columns:
+ raise exception.ImagesIdExists(service_repo_id=values['service_repoid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_service_repo)
+
+
+@db_api.main_context_manager.writer
+def _service_repo_destroy(context, service_repo_id=None, service_repoid=None):
+ query = context.session.query(models.ServicesRepos)
+
+ if service_repo_id is not None:
+ query.filter(models.ServicesRepos.id == service_repo_id).delete()
+ else:
+ query.filter(models.ServicesRepos.id == service_repoid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class ServiceRepo(base.NovaObject, base.NovaObjectDictCompat, base.NovaPersistentObject2,):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'id': fields.IntegerField(read_only=True),
+ 'repo_uuid': fields.UUIDField(),
+ 'service_uuid': fields.UUIDField(),
+ 'auto' : fields.BooleanField(),
+ 'status' : fields.EnumField(valid_values=REPO_STATUSES),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(ServiceRepo, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_service_repos = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(ServiceRepo, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, service_repo, db_service_repo, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ service_repo._context = context
+ for name, field in service_repo.fields.items():
+ value = db_service_repo[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ service_repo[name] = value
+
+ service_repo.obj_reset_changes()
+ return service_repo
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _service_repo_get_query_from_db(context):
+ query = context.session.query(models.ServicesRepos)
+ return query
+
+ @staticmethod
+ @require_context
+ def _service_repo_get_from_db(context, id):
+ """Returns a dict describing specific service_repos."""
+ result = ServiceRepo._service_repo_get_query_from_db(context).\
+ filter_by(id=id).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(service_repo_id=id)
+ return result
+
+ @staticmethod
+ @require_context
+ def _service_repo_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = ServiceRepo._service_repo_get_query_from_db(context).\
+ filter_by(name=name).\
+ first()
+ if not result:
+ raise exception.FlavorNotFoundByName(service_repos_name=name)
+ return _dict_with_extra_specs(result)
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(ServiceRepo, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(ServiceRepo, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_id(cls, context, id):
+ db_service_repo = cls._service_repo_get_from_db(context, id)
+ return cls._from_db_object(context, cls(context), db_service_repo,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name):
+ db_service_repo = cls._service_repo_get_by_name_from_db(context, name)
+ return cls._from_db_object(context, cls(context), db_service_repo,
+ expected_attrs=[])
+
+ @staticmethod
+ def _service_repo_create(context, updates):
+ return _service_repo_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_service_repo = self._service_repo_create(context, updates)
+ self._from_db_object(context, self, db_service_repo)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a service_repos.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_service_repo = context.session.query(models.ServicesRepos).\
+ filter_by(id=self.id).first()
+ if not db_service_repo:
+ raise exception.ImagesNotFound(service_repo_id=self.id)
+ db_service_repo.update(values)
+ db_service_repo.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_service_repo)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _service_repo_destroy(context, service_repo_id=None, service_repoid=None):
+ _service_repo_destroy(context, service_repo_id=service_repo_id, service_repoid=service_repoid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a service_repos
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a service_repos object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._service_repo_destroy(context, service_repo_id=self.id)
+ else:
+ self._service_repo_destroy(context, service_repoid=self.service_repoid)
+ #self._from_db_object(context, self, db_service_repo)
+
+ @base.remotable_classmethod
+ def get_by_filters(cls, context, filters=None):
+ filters = filters or {}
+ db_service_repo = ServiceRepo._service_repo_get_query_from_db(context)
+
+ if 'status' in filters:
+ db_service_repo = db_service_repo.filter(
+ models.ServicesRepos.status == filters['status'])
+ if 'repo_uuid' in filters:
+ db_service_repo = db_service_repo.filter(
+ models.ServicesRepos.repo_uuid == filters['repo_uuid'])
+ if 'service_uuid' in filters:
+ db_service_repo = db_service_repo.filter(
+ models.ServicesRepos.service_uuid == filters['service_uuid'])
+ db_service_repo = db_service_repo.first()
+ if not db_service_repo:
+ return None
+ return cls._from_db_object(context, cls(context), db_service_repo,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _service_repo_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all service_reposs.
+ """
+ filters = filters or {}
+
+ query = ServiceRepo._service_repo_get_query_from_db(context)
+
+ if 'service_uuid' in filters:
+ query = query.filter(
+ models.ServicesRepos.service_uuid == filters['service_uuid'])
+ if 'status' in filters:
+ query = query.filter(
+ models.ServicesRepos.status == filters['status'])
+ if 'repo_uuid' in filters:
+ query = query.filter(
+ models.ServicesRepos.repo_uuid == filters['repo_uuid'])
+ if not query:
+ return None
+ marker_row = None
+ if marker is not None:
+ marker_row = ServiceRepo._service_repo_get_query_from_db(context).\
+ filter_by(id=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.ServicesRepos,
+ limit,
+ [sort_key, 'id'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class ServiceRepoList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('ServiceRepo'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='id', sort_dir='asc', limit=None, marker=None):
+ db_service_repos = _service_repo_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.service_repo.ServiceRepo,
+ db_service_repos,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.ServicesRepos).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_service_repo = context.session.query(models.ServicesRepos).filter_by(auto=True)
+ db_service_repo.update(values)
diff --git a/gosbs/objects/task.py b/gosbs/objects/task.py
new file mode 100644
index 0000000..83f9c5c
--- /dev/null
+++ b/gosbs/objects/task.py
@@ -0,0 +1,291 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it will fit what we need.
+# It need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+TASK_STATUSES = ['failed', 'completed', 'in-progress', 'waiting']
+
+def _dict_with_extra_specs(task_model):
+ extra_specs = {}
+ return dict(task_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _task_create(context, values):
+ db_task = models.Tasks()
+ db_task.update(values)
+
+ try:
+ db_task.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'taskid' in e.columns:
+ raise exception.ImagesIdExists(task_id=values['taskid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return _dict_with_extra_specs(db_task)
+
+
+@db_api.main_context_manager.writer
+def _task_destroy(context, task_id=None, taskid=None):
+ query = context.session.query(models.Tasks)
+
+ if task_id is not None:
+ query.filter(models.Tasks.id == task_id).delete()
+ else:
+ query.filter(models.Tasks.taskid == taskid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class Task(base.NovaObject, base.NovaObjectDictCompat):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'uuid': fields.UUIDField(),
+ 'name': fields.StringField(),
+ 'service_uuid': fields.UUIDField(),
+ 'repet': fields.BooleanField(),
+ 'run': fields.DateTimeField(nullable=True),
+ 'status' : fields.EnumField(valid_values=TASK_STATUSES),
+ 'last' : fields.DateTimeField(nullable=True),
+ 'priority' : fields.IntegerField(default=5),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(Task, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_projects = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(Task, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, task, db_task, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ task._context = context
+ for name, field in task.fields.items():
+ value = db_task[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ task[name] = value
+
+ task.obj_reset_changes()
+ return task
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _task_get_query_from_db(context):
+ query = context.session.query(models.Tasks)
+ return query
+
+ @staticmethod
+ @require_context
+ def _task_get_from_db(context, uuid):
+ """Returns a dict describing specific tasks."""
+ result = Task._task_get_query_from_db(context).\
+ filter_by(uuid=uuid).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(task_id=uuid)
+ return result
+
+ @staticmethod
+ @require_context
+ def _task_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = Task._task_get_query_from_db(context).\
+ filter_by(name=name).\
+ first()
+ if not result:
+ raise exception.FlavorNotFoundByName(tasks_name=name)
+ return _dict_with_extra_specs(result)
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(Task, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(Task, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_uuid(cls, context, uuid):
+ db_task = cls._task_get_from_db(context, uuid)
+ return cls._from_db_object(context, cls(context), db_task,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name):
+ db_task = cls._task_get_by_name_from_db(context, name)
+ return cls._from_db_object(context, cls(context), db_task,
+ expected_attrs=[])
+
+ @base.remotable_classmethod
+ def get_by_server_uuid(cls, context, service_uuid, filters=None):
+ filters = filters or {}
+ db_task = Task._task_get_query_from_db(context)
+
+ db_task = db_task.filter_by(service_uuid=service_uuid)
+ if 'repet' in filters:
+ db_task = db_task.filter(
+ models.Tasks.repet == filters['repet'])
+ if 'name' in filters:
+ db_task = db_task.filter(
+ models.Tasks.name == filters['name'])
+ db_task = db_task.first()
+ if not db_task:
+ return None
+ return cls._from_db_object(context, cls(context), db_task,
+ expected_attrs=[])
+
+ @staticmethod
+ def _task_create(context, updates):
+ return _task_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_task = self._task_create(context, updates)
+ self._from_db_object(context, self, db_task)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a tasks.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_task = context.session.query(models.Tasks).\
+ filter_by(uuid=self.uuid).first()
+ if not db_task:
+ raise exception.ImagesNotFound(task_id=self.uuid)
+ db_task.update(values)
+ db_task.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_task)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _task_destroy(context, task_id=None, taskid=None):
+ _task_destroy(context, task_id=task_id, taskid=taskid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a tasks
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a tasks object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._task_destroy(context, task_id=self.uuid)
+ else:
+ self._task_destroy(context, taskid=self.taskid)
+ #self._from_db_object(context, self, db_task)
+
+@db_api.main_context_manager
+def _task_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all taskss.
+ """
+ filters = filters or {}
+
+ query = Task._task_get_query_from_db(context)
+
+ if 'name' in filters:
+ query = query.filter(
+ models.Tasks.name == filters['name'])
+
+ if 'service_uuid' in filters:
+ query = query.filter(
+ models.Tasks.service_uuid == filters['service_uuid'])
+
+ if 'status' in filters:
+ query = query.filter(
+ models.Tasks.status == filters['status'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = Task._task_get_query_from_db(context).\
+ filter_by(uuid=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.Tasks,
+ limit,
+ [sort_key, 'uuid'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class TaskList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('Task'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='uuid', sort_dir='asc', limit=None, marker=None):
+ db_tasks = _task_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.task.Task,
+ db_tasks,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.Tasks).delete()
diff --git a/gosbs/objects/use.py b/gosbs/objects/use.py
new file mode 100644
index 0000000..dd71073
--- /dev/null
+++ b/gosbs/objects/use.py
@@ -0,0 +1,278 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it will fit what we need.
+# It need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+
+def _dict_with_extra_specs(use_model):
+ extra_specs = {}
+ return dict(use_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _use_create(context, values):
+ db_use = models.Uses()
+ db_use.update(values)
+
+ try:
+ db_use.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'useid' in e.columns:
+ raise exception.ImagesIdExists(use_id=values['useid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return db_use
+
+
+@db_api.main_context_manager.writer
+def _use_destroy(context, use_id=None, useid=None):
+ query = context.session.query(models.Uses)
+
+ if use_id is not None:
+ query.filter(models.Uses.uuid == use_id).delete()
+ else:
+ query.filter(models.Uses.uuid == useid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class Use(base.NovaObject, base.NovaObjectDictCompat, base.NovaPersistentObject):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'id': fields.IntegerField(),
+ 'flag': fields.StringField(),
+ 'description' : fields.StringField(nullable=True),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(Use, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_uses = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(Use, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, use, db_use, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ use._context = context
+ for name, field in use.fields.items():
+ value = db_use[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ use[name] = value
+
+ use.obj_reset_changes()
+ return use
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _use_get_query_from_db(context):
+ query = context.session.query(models.Uses)
+ return query
+
+ @staticmethod
+ @require_context
+ def _use_get_from_db(context, id):
+ """Returns a dict describing specific uses."""
+ result = Use._use_get_query_from_db(context).\
+ filter_by(id=id).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(use_id=id)
+ return result
+
+ @staticmethod
+ @require_context
+ def _use_get_by_flag_from_db(context, flag):
+ """Returns a dict describing specific flavor."""
+ result = Use._use_get_query_from_db(context).\
+ filter_by(flag=flag).\
+ first()
+ return result
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(Use, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(Use, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_id(cls, context, id):
+ db_use = cls._use_get_from_db(context, id)
+ return cls._from_db_object(context, cls(context), db_use,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, flag):
+ db_use = cls._use_get_by_flag_from_db(context, flag)
+ if not db_use:
+ return None
+ return cls._from_db_object(context, cls(context), db_use,
+ expected_attrs=[])
+
+ @staticmethod
+ def _use_create(context, updates):
+ return _use_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_use = self._use_create(context, updates)
+ self._from_db_object(context, self, db_use)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a uses.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_use = context.session.query(models.Uses).\
+ filter_by(uuid=self.id).first()
+ if not db_use:
+ raise exception.ImagesNotFound(use_id=self.id)
+ db_use.update(values)
+ db_use.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_use)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _use_destroy(context, use_id=None, useid=None):
+ _use_destroy(context, use_id=use_id, useid=useid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a uses
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a uses object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._use_destroy(context, use_id=self.id)
+ else:
+ self._use_destroy(context, useid=self.useid)
+ #self._from_db_object(context, self, db_use)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+ print('foo')
+ db_use = Use._use_get_query_from_db(context)
+
+ if 'status' in filters:
+ db_use = db_use.filter(
+ models.Uses.status == filters['status']).first()
+ return cls._from_db_object(context, cls(context), db_use,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _use_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all usess.
+ """
+ filters = filters or {}
+
+ query = Use._use_get_query_from_db(context)
+
+ if 'status' in filters:
+ query = query.filter(
+ models.Uses.status == filters['status'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = Use._use_get_query_from_db(context).\
+ filter_by(id=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.Uses,
+ limit,
+ [sort_key, 'uuid'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class UseList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('Use'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='id', sort_dir='asc', limit=None, marker=None):
+ db_uses = _use_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.use.Use,
+ db_uses,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.Uses).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_use = context.session.query(models.Uses).filter_by(auto=True)
+ db_use.update(values)
diff --git a/gosbs/objects/user.py b/gosbs/objects/user.py
new file mode 100644
index 0000000..f4d6783
--- /dev/null
+++ b/gosbs/objects/user.py
@@ -0,0 +1,278 @@
+# Copyright 2013 Red Hat, Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not user this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/objects/flavor.py
+# We have change the code so it will fit what we need.
+# It need more cleaning.
+
+from oslo_db import exception as db_exc
+from oslo_db.sqlalchemy import utils as sqlalchemyutils
+from oslo_utils import versionutils
+from sqlalchemy import or_
+from sqlalchemy.orm import joinedload
+from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql import true
+
+import gosbs.conf
+from gosbs.db.sqlalchemy import api as db_api
+from gosbs.db.sqlalchemy.api import require_context
+from gosbs.db.sqlalchemy import models
+from gosbs import exception
+from gosbs import objects
+from gosbs.objects import base
+from gosbs.objects import fields
+
+CONF = gosbs.conf.CONF
+
+def _dict_with_extra_specs(userr_model):
+ extra_specs = {}
+ return dict(userr_model, extra_specs=extra_specs)
+
+
+@db_api.main_context_manager.writer
+def _userr_create(context, values):
+ db_userr = models.Users()
+ db_userr.update(values)
+
+ try:
+ db_userr.save(context.session)
+ except db_exc.DBDuplicateEntry as e:
+ if 'userrid' in e.columns:
+ raise exception.ImagesIdExists(userr_id=values['userrid'])
+ raise exception.ImagesExists(name=values['name'])
+ except Exception as e:
+ raise db_exc.DBError(e)
+
+ return db_userr
+
+
+@db_api.main_context_manager.writer
+def _userr_destroy(context, userr_id=None, userrid=None):
+ query = context.session.query(models.Users)
+
+ if user_id is not None:
+ query.filter(models.Users.uuid == user_id).delete()
+ else:
+ query.filter(models.Users.uuid == userid).delete()
+
+
+# TODO(berrange): Remove NovaObjectDictCompat
+# TODO(mriedem): Remove NovaPersistentObject in version 2.0
+@base.NovaObjectRegistry.register
+class User(base.NovaObject, base.NovaObjectDictCompat):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+
+ fields = {
+ 'id': fields.IntegerField(),
+ 'user_id': fields.IntegerField(),
+ 'name': fields.StringField(),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super(User, self).__init__(*args, **kwargs)
+ self._orig_extra_specs = {}
+ self._orig_users = []
+
+ def obj_make_compatible(self, primitive, target_version):
+ super(User, self).obj_make_compatible(primitive, target_version)
+ target_version = versionutils.convert_version_to_tuple(target_version)
+
+
+ @staticmethod
+ def _from_db_object(context, user, db_user, expected_attrs=None):
+ if expected_attrs is None:
+ expected_attrs = []
+ user._context = context
+ for name, field in user.fields.items():
+ value = db_user[name]
+ if isinstance(field, fields.IntegerField):
+ value = value if value is not None else 0
+ user[name] = value
+
+ user.obj_reset_changes()
+ return user
+
+ @staticmethod
+ @db_api.main_context_manager.reader
+ def _user_get_query_from_db(context):
+ query = context.session.query(models.Users)
+ return query
+
+ @staticmethod
+ @require_context
+ def _user_get_from_db(context, id):
+ """Returns a dict describing specific users."""
+ result = User._user_get_query_from_db(context).\
+ filter_by(id=id).\
+ first()
+ if not result:
+ raise exception.ImagesNotFound(user_id=id)
+ return result
+
+ @staticmethod
+ @require_context
+ def _user_get_by_name_from_db(context, name):
+ """Returns a dict describing specific flavor."""
+ result = User._user_get_query_from_db(context).\
+ filter_by(name=name).\
+ first()
+ return result
+
+ def obj_reset_changes(self, fields=None, recursive=False):
+ super(User, self).obj_reset_changes(fields=fields,
+ recursive=recursive)
+
+ def obj_what_changed(self):
+ changes = super(User, self).obj_what_changed()
+ return changes
+
+ @base.remotable_classmethod
+ def get_by_id(cls, context, id):
+ db_user = cls._user_get_from_db(context, id)
+ return cls._from_db_object(context, cls(context), db_user,
+ expected_attrs=[])
+ @base.remotable_classmethod
+ def get_by_name(cls, context, name):
+ db_user = cls._user_get_by_name_from_db(context, name)
+ if not db_user:
+ return None
+ return cls._from_db_object(context, cls(context), db_user,
+ expected_attrs=[])
+
+ @staticmethod
+ def _user_create(context, updates):
+ return _user_create(context, updates)
+
+ #@base.remotable
+ def create(self, context):
+ #if self.obj_attr_is_set('id'):
+ # raise exception.ObjectActionError(action='create',
+ #reason='already created')
+ updates = self.obj_get_changes()
+ db_user = self._user_create(context, updates)
+ self._from_db_object(context, self, db_user)
+
+
+ # NOTE(mriedem): This method is not remotable since we only expect the API
+ # to be able to make updates to a users.
+ @db_api.main_context_manager.writer
+ def _save(self, context, values):
+ db_user = context.session.query(models.Users).\
+ filter_by(id=self.id).first()
+ if not db_user:
+ raise exception.ImagesNotFound(user_id=self.id)
+ db_user.update(values)
+ db_user.save(context.session)
+ # Refresh ourselves from the DB object so we get the new updated_at.
+ self._from_db_object(context, self, db_user)
+ self.obj_reset_changes()
+
+ def save(self, context):
+ updates = self.obj_get_changes()
+ if updates:
+ self._save(context, updates)
+
+ @staticmethod
+ def _user_destroy(context, user_id=None, userid=None):
+ _user_destroy(context, user_id=user_id, userid=userid)
+
+ #@base.remotable
+ def destroy(self, context):
+ # NOTE(danms): Historically the only way to delete a users
+ # is via name, which is not very precise. We need to be able to
+ # support the light construction of a users object and subsequent
+ # delete request with only our name filled out. However, if we have
+ # our id property, we should instead delete with that since it's
+ # far more specific.
+ if 'id' in self:
+ self._user_destroy(context, user_id=self.id)
+ else:
+ self._user_destroy(context, userid=self.userid)
+ #self._from_db_object(context, self, db_user)
+
+ @base.remotable_classmethod
+ def get_by_filters_first(cls, context, filters=None):
+ filters = filters or {}
+ print('foo')
+ db_user = User._user_get_query_from_db(context)
+
+ if 'status' in filters:
+ db_user = db_user.filter(
+ models.Users.status == filters['status']).first()
+ return cls._from_db_object(context, cls(context), db_user,
+ expected_attrs=[])
+
+
+@db_api.main_context_manager
+def _user_get_all_from_db(context, inactive, filters, sort_key, sort_dir,
+ limit, marker):
+ """Returns all userss.
+ """
+ filters = filters or {}
+
+ query = User._user_get_query_from_db(context)
+
+ if 'status' in filters:
+ query = query.filter(
+ models.Users.status == filters['status'])
+
+ marker_row = None
+ if marker is not None:
+ marker_row = User._user_get_query_from_db(context).\
+ filter_by(id=marker).\
+ first()
+ if not marker_row:
+ raise exception.MarkerNotFound(marker=marker)
+
+ query = sqlalchemyutils.paginate_query(query, models.Users,
+ limit,
+ [sort_key, 'id'],
+ marker=marker_row,
+ sort_dir=sort_dir)
+ return [_dict_with_extra_specs(i) for i in query.all()]
+
+
+@base.NovaObjectRegistry.register
+class UseList(base.ObjectListBase, base.NovaObject):
+ VERSION = '1.0'
+
+ fields = {
+ 'objects': fields.ListOfObjectsField('User'),
+ }
+
+ @base.remotable_classmethod
+ def get_all(cls, context, inactive=False, filters=None,
+ sort_key='id', sort_dir='asc', limit=None, marker=None):
+ db_users = _user_get_all_from_db(context,
+ inactive=inactive,
+ filters=filters,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ limit=limit,
+ marker=marker)
+ return base.obj_make_list(context, cls(context), objects.user.Use,
+ db_users,
+ expected_attrs=[])
+
+ @db_api.main_context_manager.writer
+ def destroy_all(context):
+ context.session.query(models.Users).delete()
+
+ @db_api.main_context_manager.writer
+ def update_all(context):
+ values = {'status': 'waiting', }
+ db_user = context.session.query(models.Users).filter_by(auto=True)
+ db_user.update(values)
diff --git a/gosbs/policies/__init__.py b/gosbs/policies/__init__.py
new file mode 100644
index 0000000..41a3820
--- /dev/null
+++ b/gosbs/policies/__init__.py
@@ -0,0 +1,26 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/policies/__init__.py
+
+import itertools
+
+from gosbs.policies import base
+from gosbs.policies import hosts
+from gosbs.policies import services
+
+def list_rules():
+ return itertools.chain(
+ base.list_rules(),
+ hosts.list_rules(),
+ services.list_rules(),
+ )
diff --git a/gosbs/policies/base.py b/gosbs/policies/base.py
new file mode 100644
index 0000000..b6a009f
--- /dev/null
+++ b/gosbs/policies/base.py
@@ -0,0 +1,41 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_policy import policy
+
+COMPUTE_API = 'os_compute_api'
+
+RULE_ADMIN_OR_OWNER = 'rule:admin_or_owner'
+RULE_ADMIN_API = 'rule:admin_api'
+RULE_ANY = '@'
+
+# NOTE(johngarbutt) The base rules here affect so many APIs the list
+# of related API operations has not been populated. It would be
+# crazy hard to manually maintain such a list.
+rules = [
+ policy.RuleDefault(
+ "context_is_admin",
+ "role:admin",
+ "Decides what is required for the 'is_admin:True' check to succeed."),
+ policy.RuleDefault(
+ "admin_or_owner",
+ "is_admin:True or project_id:%(project_id)s",
+ "Default rule for most non-Admin APIs."),
+ policy.RuleDefault(
+ "admin_api",
+ "is_admin:True",
+ "Default rule for most Admin APIs.")
+]
+
+
+def list_rules():
+ return rules
diff --git a/gosbs/policies/hosts.py b/gosbs/policies/hosts.py
new file mode 100644
index 0000000..e39ba9d
--- /dev/null
+++ b/gosbs/policies/hosts.py
@@ -0,0 +1,63 @@
+# Copyright 2016 Cloudbase Solutions Srl
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/policies/hosts.py
+
+from oslo_policy import policy
+
+from gosbs.policies import base
+
+
+BASE_POLICY_NAME = 'os_compute_api:os-hosts'
+
+
+hosts_policies = [
+ policy.DocumentedRuleDefault(
+ BASE_POLICY_NAME,
+ base.RULE_ADMIN_API,
+ """List, show and manage physical hosts.
+
+These APIs are all deprecated in favor of os-hypervisors and os-services.""",
+ [
+ {
+ 'method': 'GET',
+ 'path': '/os-hosts'
+ },
+ {
+ 'method': 'GET',
+ 'path': '/os-hosts/{host_name}'
+ },
+ {
+ 'method': 'PUT',
+ 'path': '/os-hosts/{host_name}'
+ },
+ {
+ 'method': 'GET',
+ 'path': '/os-hosts/{host_name}/reboot'
+ },
+ {
+ 'method': 'GET',
+ 'path': '/os-hosts/{host_name}/shutdown'
+ },
+ {
+ 'method': 'GET',
+ 'path': '/os-hosts/{host_name}/startup'
+ }
+ ]),
+]
+
+
+def list_rules():
+ return hosts_policies
diff --git a/gosbs/policies/services.py b/gosbs/policies/services.py
new file mode 100644
index 0000000..5985865
--- /dev/null
+++ b/gosbs/policies/services.py
@@ -0,0 +1,69 @@
+# Copyright 2016 Cloudbase Solutions Srl
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/policies/services.py
+
+from oslo_policy import policy
+
+from gosbs.policies import base
+
+
+BASE_POLICY_NAME = 'os_compute_api:os-services'
+
+
+services_policies = [
+ policy.DocumentedRuleDefault(
+ BASE_POLICY_NAME,
+ base.RULE_ADMIN_API,
+ "List all running Compute services in a region, enables or disable "
+ "scheduling for a Compute service, logs disabled Compute service "
+ "information, set or unset forced_down flag for the compute service "
+ "and delete a Compute service",
+ [
+ {
+ 'method': 'GET',
+ 'path': '/os-services'
+ },
+ {
+ 'method': 'PUT',
+ 'path': '/os-services/enable'
+ },
+ {
+ 'method': 'PUT',
+ 'path': '/os-services/disable'
+ },
+ {
+ 'method': 'PUT',
+ 'path': '/os-services/disable-log-reason'
+ },
+ {
+ 'method': 'PUT',
+ 'path': '/os-services/force-down'
+ },
+ {
+ # Added in microversion 2.53.
+ 'method': 'PUT',
+ 'path': '/os-services/{service_id}'
+ },
+ {
+ 'method': 'DELETE',
+ 'path': '/os-services/{service_id}'
+ }
+ ]),
+]
+
+
+def list_rules():
+ return services_policies
diff --git a/gosbs/policy.py b/gosbs/policy.py
new file mode 100644
index 0000000..f8f8659
--- /dev/null
+++ b/gosbs/policy.py
@@ -0,0 +1,246 @@
+# Copyright (c) 2011 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/policy.py
+
+"""Policy Engine For Nova."""
+import copy
+import re
+
+from oslo_config import cfg
+from oslo_log import log as logging
+from oslo_policy import policy
+from oslo_utils import excutils
+
+
+from gosbs import exception
+from gosbs.i18n import _LE, _LW
+from gosbs import policies
+
+
+CONF = cfg.CONF
+LOG = logging.getLogger(__name__)
+_ENFORCER = None
+# This list is about the resources which support user based policy enforcement.
+# Avoid sending deprecation warning for those resources.
+USER_BASED_RESOURCES = ['os-keypairs']
+# oslo_policy will read the policy configuration file again when the file
+# is changed in runtime so the old policy rules will be saved to
+# saved_file_rules and used to compare with new rules to determine the
+# rules whether were updated.
+saved_file_rules = []
+KEY_EXPR = re.compile(r'%\((\w+)\)s')
+
+
+def reset():
+ global _ENFORCER
+ if _ENFORCER:
+ _ENFORCER.clear()
+ _ENFORCER = None
+
+
+def init(policy_file=None, rules=None, default_rule=None, use_conf=True):
+ """Init an Enforcer class.
+
+ :param policy_file: Custom policy file to use, if none is specified,
+ `CONF.policy_file` will be used.
+ :param rules: Default dictionary / Rules to use. It will be
+ considered just in the first instantiation.
+ :param default_rule: Default rule to use, CONF.default_rule will
+ be used if none is specified.
+ :param use_conf: Whether to load rules from config file.
+ """
+
+ global _ENFORCER
+ global saved_file_rules
+
+ if not _ENFORCER:
+ _ENFORCER = policy.Enforcer(CONF,
+ policy_file=policy_file,
+ rules=rules,
+ default_rule=default_rule,
+ use_conf=use_conf)
+ register_rules(_ENFORCER)
+ _ENFORCER.load_rules()
+
+ # Only the rules which are loaded from file may be changed.
+ current_file_rules = _ENFORCER.file_rules
+ current_file_rules = _serialize_rules(current_file_rules)
+
+ # Checks whether the rules are updated in the runtime
+ if saved_file_rules != current_file_rules:
+ _warning_for_deprecated_user_based_rules(current_file_rules)
+ saved_file_rules = copy.deepcopy(current_file_rules)
+
+
+def _serialize_rules(rules):
+ """Serialize all the Rule object as string which is used to compare the
+ rules list.
+ """
+ result = [(rule_name, str(rule))
+ for rule_name, rule in rules.items()]
+ return sorted(result, key=lambda rule: rule[0])
+
+
+def _warning_for_deprecated_user_based_rules(rules):
+ """Warning user based policy enforcement used in the rule but the rule
+ doesn't support it.
+ """
+ for rule in rules:
+ # We will skip the warning for the resources which support user based
+ # policy enforcement.
+ if [resource for resource in USER_BASED_RESOURCES
+ if resource in rule[0]]:
+ continue
+ if 'user_id' in KEY_EXPR.findall(rule[1]):
+ LOG.warning(_LW("The user_id attribute isn't supported in the "
+ "rule '%s'. All the user_id based policy "
+ "enforcement will be removed in the "
+ "future."), rule[0])
+
+
+def set_rules(rules, overwrite=True, use_conf=False):
+ """Set rules based on the provided dict of rules.
+
+ :param rules: New rules to use. It should be an instance of dict.
+ :param overwrite: Whether to overwrite current rules or update them
+ with the new rules.
+ :param use_conf: Whether to reload rules from config file.
+ """
+
+ init(use_conf=False)
+ _ENFORCER.set_rules(rules, overwrite, use_conf)
+
+
+def authorize(context, action, target, do_raise=True, exc=None):
+ """Verifies that the action is valid on the target in this context.
+
+ :param context: nova context
+ :param action: string representing the action to be checked
+ this should be colon separated for clarity.
+ i.e. ``compute:create_instance``,
+ ``compute:attach_volume``,
+ ``volume:attach_volume``
+ :param target: dictionary representing the object of the action
+ for object creation this should be a dictionary representing the
+ location of the object e.g. ``{'project_id': context.project_id}``
+ :param do_raise: if True (the default), raises PolicyNotAuthorized;
+ if False, returns False
+ :param exc: Class of the exception to raise if the check fails.
+ Any remaining arguments passed to :meth:`authorize` (both
+ positional and keyword arguments) will be passed to
+ the exception class. If not specified,
+ :class:`PolicyNotAuthorized` will be used.
+
+ :raises nova.exception.PolicyNotAuthorized: if verification fails
+ and do_raise is True. Or if 'exc' is specified it will raise an
+ exception of that type.
+
+ :return: returns a non-False value (not necessarily "True") if
+ authorized, and the exact value False if not authorized and
+ do_raise is False.
+ """
+ init()
+ credentials = context.to_policy_values()
+ if not exc:
+ exc = exception.PolicyNotAuthorized
+ try:
+ result = _ENFORCER.authorize(action, target, credentials,
+ do_raise=do_raise, exc=exc, action=action)
+ except policy.PolicyNotRegistered:
+ with excutils.save_and_reraise_exception():
+ LOG.exception(_LE('Policy not registered'))
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ LOG.debug('Policy check for %(action)s failed with credentials '
+ '%(credentials)s',
+ {'action': action, 'credentials': credentials})
+ return result
+
+
+def check_is_admin(context):
+ """Whether or not roles contains 'admin' role according to policy setting.
+
+ """
+
+ init()
+ # the target is user-self
+ credentials = context.to_policy_values()
+ target = credentials
+ return _ENFORCER.authorize('context_is_admin', target, credentials)
+
+
+@policy.register('is_admin')
+class IsAdminCheck(policy.Check):
+ """An explicit check for is_admin."""
+
+ def __init__(self, kind, match):
+ """Initialize the check."""
+
+ self.expected = (match.lower() == 'true')
+
+ super(IsAdminCheck, self).__init__(kind, str(self.expected))
+
+ def __call__(self, target, creds, enforcer):
+ """Determine whether is_admin matches the requested value."""
+
+ return creds['is_admin'] == self.expected
+
+
+def get_rules():
+ if _ENFORCER:
+ return _ENFORCER.rules
+
+
+def register_rules(enforcer):
+ enforcer.register_defaults(policies.list_rules())
+
+
+def get_enforcer():
+ # This method is used by oslopolicy CLI scripts in order to generate policy
+ # files from overrides on disk and defaults in code.
+ cfg.CONF([], project='nova')
+ init()
+ return _ENFORCER
+
+
+def verify_deprecated_policy(old_policy, new_policy, default_rule, context):
+ """Check the rule of the deprecated policy action
+
+ If the current rule of the deprecated policy action is set to a non-default
+ value, then a warning message is logged stating that the new policy
+ action should be used to dictate permissions as the old policy action is
+ being deprecated.
+
+ :param old_policy: policy action that is being deprecated
+ :param new_policy: policy action that is replacing old_policy
+ :param default_rule: the old_policy action default rule value
+ :param context: the nova context
+ """
+
+ if _ENFORCER:
+ current_rule = str(_ENFORCER.rules[old_policy])
+ else:
+ current_rule = None
+
+ if current_rule != default_rule:
+ LOG.warning("Start using the new action '{0}'. The existing "
+ "action '{1}' is being deprecated and will be "
+ "removed in future release.".format(new_policy,
+ old_policy))
+ context.can(old_policy)
+ return True
+ else:
+ return False
diff --git a/gosbs/profiler.py b/gosbs/profiler.py
new file mode 100644
index 0000000..de9ed9b
--- /dev/null
+++ b/gosbs/profiler.py
@@ -0,0 +1,51 @@
+# Copyright 2016 IBM Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/profiler.py
+
+from oslo_utils import importutils
+
+import gosbs.conf
+
+profiler = importutils.try_import('osprofiler.profiler')
+
+CONF = gosbs.conf.CONF
+
+def get_traced_meta():
+ if profiler and 'profiler' in CONF and CONF.profiler.enabled:
+ return profiler.TracedMeta
+ else:
+ # NOTE(rpodolyaka): if we do not return a child of type, then Python
+ # fails to build a correct MRO when osprofiler is not installed
+ class NoopMeta(type):
+ pass
+ return NoopMeta
+
+
+def trace_cls(name, **kwargs):
+ """Wrap the OSProfiler trace_cls decorator so that it will not try to
+ patch the class unless OSProfiler is present and enabled in the config
+
+ :param name: The name of action. E.g. wsgi, rpc, db, etc..
+ :param kwargs: Any other keyword args used by profiler.trace_cls
+ """
+
+ def decorator(cls):
+ if profiler and 'profiler' in CONF and CONF.profiler.enabled:
+ trace_decorator = profiler.trace_cls(name, kwargs)
+ return trace_decorator(cls)
+ return cls
+
+ return decorator
diff --git a/gosbs/rpc.py b/gosbs/rpc.py
new file mode 100644
index 0000000..73154ea
--- /dev/null
+++ b/gosbs/rpc.py
@@ -0,0 +1,448 @@
+# Copyright 2013 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/rpc.py
+
+import functools
+
+from oslo_log import log as logging
+import oslo_messaging as messaging
+from oslo_messaging.rpc import dispatcher
+from oslo_serialization import jsonutils
+from oslo_service import periodic_task
+from oslo_utils import importutils
+import six
+
+import gosbs.conf
+import gosbs.context
+import gosbs.exception
+from gosbs.i18n import _
+
+__all__ = [
+ 'init',
+ 'cleanup',
+ 'set_defaults',
+ 'add_extra_exmods',
+ 'clear_extra_exmods',
+ 'get_allowed_exmods',
+ 'RequestContextSerializer',
+ 'get_client',
+ 'get_server',
+ 'get_notifier',
+]
+
+profiler = importutils.try_import("osprofiler.profiler")
+
+
+CONF = gosbs.conf.CONF
+
+LOG = logging.getLogger(__name__)
+
+# TODO(stephenfin): These should be private
+TRANSPORT = None
+LEGACY_NOTIFIER = None
+NOTIFICATION_TRANSPORT = None
+NOTIFIER = None
+
+# NOTE(danms): If rpc_response_timeout is over this value (per-call or
+# globally), we will enable heartbeating
+HEARTBEAT_THRESHOLD = 60
+
+ALLOWED_EXMODS = [
+ gosbs.exception.__name__,
+]
+EXTRA_EXMODS = []
+
+
+def init(conf):
+ global TRANSPORT, NOTIFICATION_TRANSPORT, LEGACY_NOTIFIER, NOTIFIER
+ exmods = get_allowed_exmods()
+ TRANSPORT = create_transport(get_transport_url())
+ NOTIFICATION_TRANSPORT = messaging.get_notification_transport(
+ conf, allowed_remote_exmods=exmods)
+ serializer = RequestContextSerializer(JsonPayloadSerializer())
+ if conf.notifications.notification_format == 'unversioned':
+ LEGACY_NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT,
+ serializer=serializer)
+ NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT,
+ serializer=serializer, driver='noop')
+ elif conf.notifications.notification_format == 'both':
+ LEGACY_NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT,
+ serializer=serializer)
+ NOTIFIER = messaging.Notifier(
+ NOTIFICATION_TRANSPORT,
+ serializer=serializer,
+ topics=conf.notifications.versioned_notifications_topics)
+ else:
+ LEGACY_NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT,
+ serializer=serializer,
+ driver='noop')
+ NOTIFIER = messaging.Notifier(
+ NOTIFICATION_TRANSPORT,
+ serializer=serializer,
+ topics=conf.notifications.versioned_notifications_topics)
+
+
+def cleanup():
+ global TRANSPORT, NOTIFICATION_TRANSPORT, LEGACY_NOTIFIER, NOTIFIER
+ assert TRANSPORT is not None
+ assert NOTIFICATION_TRANSPORT is not None
+ assert LEGACY_NOTIFIER is not None
+ assert NOTIFIER is not None
+ TRANSPORT.cleanup()
+ NOTIFICATION_TRANSPORT.cleanup()
+ TRANSPORT = NOTIFICATION_TRANSPORT = LEGACY_NOTIFIER = NOTIFIER = None
+
+
+def set_defaults(control_exchange):
+ messaging.set_transport_defaults(control_exchange)
+
+
+def add_extra_exmods(*args):
+ EXTRA_EXMODS.extend(args)
+
+
+def clear_extra_exmods():
+ del EXTRA_EXMODS[:]
+
+
+def get_allowed_exmods():
+ return ALLOWED_EXMODS + EXTRA_EXMODS
+
+
+class JsonPayloadSerializer(messaging.NoOpSerializer):
+
+ @staticmethod
+ def fallback(obj):
+ """Serializer fallback
+
+ This method is used to serialize an object which jsonutils.to_primitive
+ does not otherwise know how to handle.
+
+ This is mostly only needed in tests because of the use of the nova
+ CheatingSerializer fixture which keeps some non-serializable fields
+ on the RequestContext, like db_connection.
+ """
+ if isinstance(obj, gosbs.context.RequestContext):
+ # This matches RequestContextSerializer.serialize_context().
+ return obj.to_dict()
+ # The default fallback in jsonutils.to_primitive() is six.text_type.
+ return six.text_type(obj)
+
+ def serialize_entity(self, context, entity):
+ return jsonutils.to_primitive(entity, convert_instances=True,
+ fallback=self.fallback)
+
+
+class RequestContextSerializer(messaging.Serializer):
+
+ def __init__(self, base):
+ self._base = base
+
+ def serialize_entity(self, context, entity):
+ if not self._base:
+ return entity
+ return self._base.serialize_entity(context, entity)
+
+ def deserialize_entity(self, context, entity):
+ if not self._base:
+ return entity
+ return self._base.deserialize_entity(context, entity)
+
+ def serialize_context(self, context):
+ return context.to_dict()
+
+ def deserialize_context(self, context):
+ return gosbs.context.RequestContext.from_dict(context)
+
+
+class ProfilerRequestContextSerializer(RequestContextSerializer):
+ def serialize_context(self, context):
+ _context = super(ProfilerRequestContextSerializer,
+ self).serialize_context(context)
+
+ prof = profiler.get()
+ if prof:
+ # FIXME(DinaBelova): we'll add profiler.get_info() method
+ # to extract this info -> we'll need to update these lines
+ trace_info = {
+ "hmac_key": prof.hmac_key,
+ "base_id": prof.get_base_id(),
+ "parent_id": prof.get_id()
+ }
+ _context.update({"trace_info": trace_info})
+
+ return _context
+
+ def deserialize_context(self, context):
+ trace_info = context.pop("trace_info", None)
+ if trace_info:
+ profiler.init(**trace_info)
+
+ return super(ProfilerRequestContextSerializer,
+ self).deserialize_context(context)
+
+
+def get_transport_url(url_str=None):
+ return messaging.TransportURL.parse(CONF, url_str)
+
+
+def get_client(target, version_cap=None, serializer=None,
+ call_monitor_timeout=None):
+ assert TRANSPORT is not None
+
+ if profiler:
+ serializer = ProfilerRequestContextSerializer(serializer)
+ else:
+ serializer = RequestContextSerializer(serializer)
+
+ return messaging.RPCClient(TRANSPORT,
+ target,
+ version_cap=version_cap,
+ serializer=serializer,
+ call_monitor_timeout=call_monitor_timeout)
+
+
+def get_server(target, endpoints, serializer=None):
+ assert TRANSPORT is not None
+
+ if profiler:
+ serializer = ProfilerRequestContextSerializer(serializer)
+ else:
+ serializer = RequestContextSerializer(serializer)
+ access_policy = dispatcher.DefaultRPCAccessPolicy
+ return messaging.get_rpc_server(TRANSPORT,
+ target,
+ endpoints,
+ executor='eventlet',
+ serializer=serializer,
+ access_policy=access_policy)
+
+
+def get_notifier(service, host=None, publisher_id=None):
+ assert LEGACY_NOTIFIER is not None
+ if not publisher_id:
+ publisher_id = "%s.%s" % (service, host or CONF.host)
+ return LegacyValidatingNotifier(
+ LEGACY_NOTIFIER.prepare(publisher_id=publisher_id))
+
+
+def get_versioned_notifier(publisher_id):
+ assert NOTIFIER is not None
+ return NOTIFIER.prepare(publisher_id=publisher_id)
+
+
+def if_notifications_enabled(f):
+ """Calls decorated method only if versioned notifications are enabled."""
+ @functools.wraps(f)
+ def wrapped(*args, **kwargs):
+ if (NOTIFIER.is_enabled() and
+ CONF.notifications.notification_format in ('both',
+ 'versioned')):
+ return f(*args, **kwargs)
+ else:
+ return None
+ return wrapped
+
+
+def create_transport(url):
+ exmods = get_allowed_exmods()
+ return messaging.get_rpc_transport(CONF,
+ url=url,
+ allowed_remote_exmods=exmods)
+
+
+class LegacyValidatingNotifier(object):
+ """Wraps an oslo.messaging Notifier and checks for allowed event_types."""
+
+ # If true an exception is thrown if the event_type is not allowed, if false
+ # then only a WARNING is logged
+ fatal = False
+
+ # This list contains the already existing therefore allowed legacy
+ # notification event_types. New items shall not be added to the list as
+ # Nova does not allow new legacy notifications any more. This list will be
+ # removed when all the notification is transformed to versioned
+ # notifications.
+ allowed_legacy_notification_event_types = [
+ 'aggregate.addhost.end',
+ 'aggregate.addhost.start',
+ 'aggregate.create.end',
+ 'aggregate.create.start',
+ 'aggregate.delete.end',
+ 'aggregate.delete.start',
+ 'aggregate.removehost.end',
+ 'aggregate.removehost.start',
+ 'aggregate.updatemetadata.end',
+ 'aggregate.updatemetadata.start',
+ 'aggregate.updateprop.end',
+ 'aggregate.updateprop.start',
+ 'compute.instance.create.end',
+ 'compute.instance.create.error',
+ 'compute.instance.create_ip.end',
+ 'compute.instance.create_ip.start',
+ 'compute.instance.create.start',
+ 'compute.instance.delete.end',
+ 'compute.instance.delete_ip.end',
+ 'compute.instance.delete_ip.start',
+ 'compute.instance.delete.start',
+ 'compute.instance.evacuate',
+ 'compute.instance.exists',
+ 'compute.instance.finish_resize.end',
+ 'compute.instance.finish_resize.start',
+ 'compute.instance.live.migration.abort.start',
+ 'compute.instance.live.migration.abort.end',
+ 'compute.instance.live.migration.force.complete.start',
+ 'compute.instance.live.migration.force.complete.end',
+ 'compute.instance.live_migration.post.dest.end',
+ 'compute.instance.live_migration.post.dest.start',
+ 'compute.instance.live_migration._post.end',
+ 'compute.instance.live_migration._post.start',
+ 'compute.instance.live_migration.pre.end',
+ 'compute.instance.live_migration.pre.start',
+ 'compute.instance.live_migration.rollback.dest.end',
+ 'compute.instance.live_migration.rollback.dest.start',
+ 'compute.instance.live_migration._rollback.end',
+ 'compute.instance.live_migration._rollback.start',
+ 'compute.instance.pause.end',
+ 'compute.instance.pause.start',
+ 'compute.instance.power_off.end',
+ 'compute.instance.power_off.start',
+ 'compute.instance.power_on.end',
+ 'compute.instance.power_on.start',
+ 'compute.instance.reboot.end',
+ 'compute.instance.reboot.error',
+ 'compute.instance.reboot.start',
+ 'compute.instance.rebuild.end',
+ 'compute.instance.rebuild.error',
+ 'compute.instance.rebuild.scheduled',
+ 'compute.instance.rebuild.start',
+ 'compute.instance.rescue.end',
+ 'compute.instance.rescue.start',
+ 'compute.instance.resize.confirm.end',
+ 'compute.instance.resize.confirm.start',
+ 'compute.instance.resize.end',
+ 'compute.instance.resize.error',
+ 'compute.instance.resize.prep.end',
+ 'compute.instance.resize.prep.start',
+ 'compute.instance.resize.revert.end',
+ 'compute.instance.resize.revert.start',
+ 'compute.instance.resize.start',
+ 'compute.instance.restore.end',
+ 'compute.instance.restore.start',
+ 'compute.instance.resume.end',
+ 'compute.instance.resume.start',
+ 'compute.instance.shelve.end',
+ 'compute.instance.shelve_offload.end',
+ 'compute.instance.shelve_offload.start',
+ 'compute.instance.shelve.start',
+ 'compute.instance.shutdown.end',
+ 'compute.instance.shutdown.start',
+ 'compute.instance.snapshot.end',
+ 'compute.instance.snapshot.start',
+ 'compute.instance.soft_delete.end',
+ 'compute.instance.soft_delete.start',
+ 'compute.instance.suspend.end',
+ 'compute.instance.suspend.start',
+ 'compute.instance.trigger_crash_dump.end',
+ 'compute.instance.trigger_crash_dump.start',
+ 'compute.instance.unpause.end',
+ 'compute.instance.unpause.start',
+ 'compute.instance.unrescue.end',
+ 'compute.instance.unrescue.start',
+ 'compute.instance.unshelve.start',
+ 'compute.instance.unshelve.end',
+ 'compute.instance.update',
+ 'compute.instance.volume.attach',
+ 'compute.instance.volume.detach',
+ 'compute.libvirt.error',
+ 'compute.metrics.update',
+ 'compute_task.build_instances',
+ 'compute_task.migrate_server',
+ 'compute_task.rebuild_server',
+ 'HostAPI.power_action.end',
+ 'HostAPI.power_action.start',
+ 'HostAPI.set_enabled.end',
+ 'HostAPI.set_enabled.start',
+ 'HostAPI.set_maintenance.end',
+ 'HostAPI.set_maintenance.start',
+ 'keypair.create.start',
+ 'keypair.create.end',
+ 'keypair.delete.start',
+ 'keypair.delete.end',
+ 'keypair.import.start',
+ 'keypair.import.end',
+ 'network.floating_ip.allocate',
+ 'network.floating_ip.associate',
+ 'network.floating_ip.deallocate',
+ 'network.floating_ip.disassociate',
+ 'scheduler.select_destinations.end',
+ 'scheduler.select_destinations.start',
+ 'servergroup.addmember',
+ 'servergroup.create',
+ 'servergroup.delete',
+ 'volume.usage',
+ ]
+
+ message = _('%(event_type)s is not a versioned notification and not '
+ 'whitelisted. See ./doc/source/reference/notifications.rst')
+
+ def __init__(self, notifier):
+ self.notifier = notifier
+ for priority in ['debug', 'info', 'warn', 'error', 'critical']:
+ setattr(self, priority,
+ functools.partial(self._notify, priority))
+
+ def _is_wrap_exception_notification(self, payload):
+ # nova.exception_wrapper.wrap_exception decorator emits notification
+ # where the event_type is the name of the decorated function. This
+ # is used in many places but it will be converted to versioned
+ # notification in one run by updating the decorator so it is pointless
+ # to white list all the function names here we white list the
+ # notification itself detected by the special payload keys.
+ return {'exception', 'args'} == set(payload.keys())
+
+ def _notify(self, priority, ctxt, event_type, payload):
+ if (event_type not in self.allowed_legacy_notification_event_types and
+ not self._is_wrap_exception_notification(payload)):
+ if self.fatal:
+ raise AssertionError(self.message % {'event_type': event_type})
+ else:
+ LOG.warning(self.message, {'event_type': event_type})
+
+ getattr(self.notifier, priority)(ctxt, event_type, payload)
+
+
+class ClientRouter(periodic_task.PeriodicTasks):
+ """Creates RPC clients that honor the context's RPC transport
+ or provides a default.
+ """
+
+ def __init__(self, default_client):
+ super(ClientRouter, self).__init__(CONF)
+ self.default_client = default_client
+ self.target = default_client.target
+ self.version_cap = default_client.version_cap
+ self.serializer = default_client.serializer
+
+ def client(self, context):
+ transport = context.mq_connection
+ if transport:
+ cmt = self.default_client.call_monitor_timeout
+ return messaging.RPCClient(transport, self.target,
+ version_cap=self.version_cap,
+ serializer=self.serializer,
+ call_monitor_timeout=cmt)
+ else:
+ return self.default_client
diff --git a/gosbs/safe_utils.py b/gosbs/safe_utils.py
new file mode 100644
index 0000000..0ee7162
--- /dev/null
+++ b/gosbs/safe_utils.py
@@ -0,0 +1,41 @@
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Justin Santa Barbara
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Utilities and helper functions that won't produce circular imports."""
+
+
+def get_wrapped_function(function):
+ """Get the method at the bottom of a stack of decorators."""
+ if not hasattr(function, '__closure__') or not function.__closure__:
+ return function
+
+ def _get_wrapped_function(function):
+ if not hasattr(function, '__closure__') or not function.__closure__:
+ return None
+
+ for closure in function.__closure__:
+ func = closure.cell_contents
+
+ deeper_func = _get_wrapped_function(func)
+ if deeper_func:
+ return deeper_func
+ elif hasattr(closure.cell_contents, '__call__'):
+ return closure.cell_contents
+
+ return function
+
+ return _get_wrapped_function(function)
diff --git a/gosbs/scheduler/__init__.py b/gosbs/scheduler/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gosbs/scheduler/__init__.py
diff --git a/gosbs/scheduler/category.py b/gosbs/scheduler/category.py
new file mode 100644
index 0000000..c9b98e7
--- /dev/null
+++ b/gosbs/scheduler/category.py
@@ -0,0 +1,113 @@
+# Copyright 1999-2020 Gentoo Authors
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import re
+import os
+from portage.xml.metadata import MetaDataXML
+from portage.checksum import perform_checksum
+
+from oslo_log import log as logging
+from oslo_utils import uuidutils
+from gosbs import objects
+import gosbs.conf
+
+CONF = gosbs.conf.CONF
+LOG = logging.getLogger(__name__)
+
+def get_category_metadata_tree(c_path):
+ # Make categories_metadataDict
+ categories_metadataDict = {}
+ try:
+ metadata_xml_checksum = perform_checksum(c_path + "/metadata.xml", "SHA256")[0]
+ except Exception as e:
+ return None
+ categories_metadataDict['metadata_xml_checksum'] = metadata_xml_checksum
+ pkg_md = MetaDataXML(c_path + "/metadata.xml", None)
+ if pkg_md.descriptions():
+ metadata_xml_descriptions_tree = re.sub('\t', '', pkg_md.descriptions()[0])
+ metadata_xml_descriptions_tree = re.sub('\n', '', metadata_xml_descriptions_tree)
+ else:
+ metadata_xml_descriptions_tree = ''
+ categories_metadataDict['metadata_xml_descriptions'] = metadata_xml_descriptions_tree
+ return categories_metadataDict
+
+def create_cm_db(context, category_db, category_metadata_tree):
+ category_metadata_db = objects.category_metadata.CategoryMetadata()
+ category_metadata_db.category_uuid = category_db.uuid
+ category_metadata_db.description = category_metadata_tree['metadata_xml_descriptions']
+ category_metadata_db.checksum = category_metadata_tree['metadata_xml_checksum']
+ category_metadata_db.create(context)
+ return category_metadata_db
+
+def create_c_db(context, category):
+ category_db = objects.category.Category()
+ category_db.uuid = uuidutils.generate_uuid()
+ category_db.name = category
+ category_db.status = 'in-progress'
+ category_db.create(context)
+ return category_db
+
+def update_cm_db(context, category_metadata_db, category_metadata_tree):
+ category_metadata_db.description = category_metadata_tree['metadata_xml_descriptions']
+ category_metadata_db.checksum = category_metadata_tree['metadata_xml_checksum']
+ category_metadata_db.save(context)
+
+def check_c_db(context, category, repo_db):
+ LOG.debug("Checking %s", category)
+ c_path = CONF.repopath + '/' + repo_db.name + '.git/' + category
+ category_db = objects.category.Category.get_by_name(context, category)
+ if not os.path.isdir(c_path):
+ LOG.error("Path %s is not found for %s", c_path, category)
+ if category_db is not None:
+ category_db.deleted = True
+ category_db.save(context)
+ LOG.info("Deleting %s in the database", category)
+ return True
+ return False
+ if category_db is None:
+ LOG.info("Adding %s to the database", category)
+ category_db = create_c_db(context, category)
+ #if category_db.status == 'in-progress':
+ # return True
+ category_db.status = 'in-progress'
+ category_db.save(context)
+ category_metadata_tree = get_category_metadata_tree(c_path)
+ if category_metadata_tree is None:
+ category_db.status = 'failed'
+ category_db.save(context)
+ LOG.error("Failed to get metadata for %s", category)
+ return False
+ category_metadata_db = objects.category_metadata.CategoryMetadata.get_by_uuid(context, category_db.uuid)
+ if category_metadata_db is None:
+ category_metadata_db = create_cm_db(context, category_db, category_metadata_tree)
+ if category_metadata_db.checksum != category_metadata_tree['metadata_xml_checksum']:
+ update_cm_db(context, category_metadata_db, category_metadata_tree)
+ LOG.debug("Update %s metadata", category)
+ category_db.status = 'completed'
+ category_db.save(context)
+ return True
+
+def destroy_c_db(context, category):
+ category_db = objects.category.Category.get_by_name(context, category)
+ category_db.destroy(context)
+ return True
+
+def remove_c_db(context, category):
+ category_db = objects.category.Category.get_by_name(context, category)
+ if category_db.deleted is True:
+ category_metadata_db = objects.category_metadata.CategoryMetadata.get_by_uuid(context, category_db.uuid)
+ category_metadata_db.remove(context)
+ category_db.remove(context)
+ else:
+ return False
+ return True
diff --git a/gosbs/scheduler/ebuild.py b/gosbs/scheduler/ebuild.py
new file mode 100644
index 0000000..646a6c1
--- /dev/null
+++ b/gosbs/scheduler/ebuild.py
@@ -0,0 +1,203 @@
+# Copyright 1999-2020 Gentoo Authors
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import os
+import git
+import re
+import portage
+from portage.checksum import perform_checksum
+
+from oslo_log import log as logging
+from oslo_utils import uuidutils
+from gosbs import objects
+import gosbs.conf
+
+CONF = gosbs.conf.CONF
+LOG = logging.getLogger(__name__)
+
+def get_all_cpv_from_package(myportdb, cp, repo_path):
+ mytree = []
+ mytree.append(repo_path)
+ return myportdb.cp_list(cp, use_cache=1, mytree=mytree)
+
+def get_git_log_ebuild(repodir, ebuild_file):
+ git_log_ebuild = ''
+ g = git.Git(repodir)
+ index = 1
+ git_log_dict = {}
+ for line in g.log('-n 1', ebuild_file).splitlines():
+ git_log_dict[index] = line
+ index = index + 1
+ git_ebuild_commit = re.sub('commit ', '', git_log_dict[1])
+ git_ebuild_commit_msg = re.sub(' ', '', git_log_dict[5])
+ return git_ebuild_commit, git_ebuild_commit_msg
+
+def get_ebuild_metadata(myportdb, cpv, repo):
+ # Get the auxdbkeys infos for the ebuild
+ try:
+ ebuild_auxdb_list = myportdb.aux_get(cpv, portage.auxdbkeys, myrepo=repo)
+ except:
+ ebuild_auxdb_list = False
+ LOG.error("Failed to get aux data for %s", cpv)
+ else:
+ for i in range(len(ebuild_auxdb_list)):
+ if ebuild_auxdb_list[i] == '':
+ ebuild_auxdb_list[i] = ''
+ return ebuild_auxdb_list
+
+def create_cpv_db(context, ebuild_version_tree, ebuild_version_checksum_tree, package_uuid):
+ ebuild_version_db = objects.ebuild.Ebuild()
+ ebuild_version_db.ebuild_uuid = uuidutils.generate_uuid()
+ ebuild_version_db.version = ebuild_version_tree
+ ebuild_version_db.checksum = ebuild_version_checksum_tree
+ ebuild_version_db.package_uuid = package_uuid
+ ebuild_version_db.create(context)
+ return ebuild_version_db
+
+def deleted_cpv_db(context, uuid):
+ ebuild_version_db = objects.ebuild.Ebuild.get_by_uuid(context, uuid)
+ ebuild_version_db.deleted = True
+ ebuild_version_db.save(context)
+
+def create_use_db(context, use):
+ use_db = objects.use.Use()
+ use_db.flag = use
+ use_db.description = ''
+ use_db.create(context)
+ return use_db
+
+def check_use_db(context, use):
+ use_db = objects.use.Use.get_by_name(context, use)
+ if use_db is None:
+ use_db = create_use_db(context, use)
+ return use_db.id
+
+def create_cpv_use_db(context, ebuild_version_uuid, ebuild_version_metadata_tree):
+ for iuse in ebuild_version_metadata_tree[10].split():
+ status = False
+ if iuse[0] in ["+"]:
+ iuse = iuse[1:]
+ status = True
+ elif iuse[0] in ["-"]:
+ iuse = iuse[1:]
+ iuse_id = check_use_db(context, iuse)
+ ebuild_iuse_db = objects.ebuild_iuse.EbuildIUse()
+ ebuild_iuse_db.ebuild_uuid = ebuild_version_uuid
+ ebuild_iuse_db.use_id = iuse_id
+ ebuild_iuse_db.status = status
+ ebuild_iuse_db.create(context)
+
+def create_keyword_db(context, keyword):
+ keyword_db = objects.keyword.Keyword()
+ keyword_db.keyword = keyword
+ keyword_db.create(context)
+ return keyword_db
+
+def check_keyword_db(context, keyword):
+ keyword_db = objects.keyword.Keyword.get_by_name(context, keyword)
+ if keyword_db is None:
+ keyword_db = create_keyword_db(context, keyword)
+ return keyword_db.id
+
+def create_cpv_keyword_db(context, ebuild_version_uuid, ebuild_version_metadata_tree):
+ for keyword in ebuild_version_metadata_tree[8].split():
+ status = 'stable'
+ if keyword[0] in ["~"]:
+ keyword = keyword[1:]
+ status = 'unstable'
+ elif keyword[0] in ["-"]:
+ keyword = keyword[1:]
+ status = 'negative'
+ keyword_id = check_keyword_db(context, keyword)
+ ebuild_keyword_db = objects.ebuild_keyword.EbuildKeyword()
+ ebuild_keyword_db.ebuild_uuid = ebuild_version_uuid
+ ebuild_keyword_db.keyword_id = keyword_id
+ ebuild_keyword_db.status = status
+ ebuild_keyword_db.create(context)
+
+def create_restriction_db(context, restriction):
+ restriction_db = objects.restriction.Restriction()
+ restriction_db.restriction = restriction
+ restriction_db.create(context)
+ return restriction_db
+
+def check_restriction_db(context, restriction):
+ restriction_db = objects.restriction.Restriction.get_by_name(context, restriction)
+ if restriction_db is None:
+ restriction_db = create_restriction_db(context, restriction)
+ return restriction_db.id
+
+def create_cpv_restriction_db(context, ebuild_version_uuid, ebuild_version_metadata_tree):
+ for restriction in ebuild_version_metadata_tree[4].split():
+ if restriction in ["!"]:
+ restriction = restriction[1:]
+ if restriction in ["?"]:
+ restriction = restriction[:1]
+ if restriction != '(' or restriction != ')':
+ restriction_id = check_restriction_db(context, restriction)
+ ebuild_restriction_db = objects.ebuild_restriction.EbuildRestriction()
+ ebuild_restriction_db.ebuild_uuid = ebuild_version_uuid
+ ebuild_restriction_db.restriction_id = restriction_id
+ ebuild_restriction_db.create(context)
+
+def create_cpv_metadata_db(context, myportdb, cpv, ebuild_file, ebuild_version_db, repo_db):
+ repo_path = CONF.repopath + '/' + repo_db.name + '.git'
+ git_commit, git_commit_msg = get_git_log_ebuild(repo_path, ebuild_file)
+ ebuild_version_metadata_tree = get_ebuild_metadata(myportdb, cpv, repo_db.name)
+ ebuild_metadata_db = objects.ebuild_metadata.EbuildMetadata()
+ ebuild_metadata_db.ebuild_uuid = ebuild_version_db.uuid
+ ebuild_metadata_db.commit = git_commit
+ ebuild_metadata_db.commit_msg = git_commit_msg
+ ebuild_metadata_db.description = ebuild_version_metadata_tree[7]
+ ebuild_metadata_db.slot = ebuild_version_metadata_tree[2]
+ ebuild_metadata_db.homepage = ebuild_version_metadata_tree[5]
+ ebuild_metadata_db.license = ebuild_version_metadata_tree[6]
+ ebuild_metadata_db.create(context)
+ create_cpv_restriction_db(context, ebuild_version_db.uuid, ebuild_version_metadata_tree)
+ create_cpv_use_db(context, ebuild_version_db.uuid, ebuild_version_metadata_tree)
+ create_cpv_keyword_db(context, ebuild_version_db.uuid, ebuild_version_metadata_tree)
+ return True
+
+def check_cpv_db(context, myportdb, cp, repo_db, package_uuid):
+ repo_path = CONF.repopath + '/' + repo_db.name + '.git'
+ filters = { 'deleted' : False,
+ 'package_uuid' : package_uuid,
+ }
+ ebuild_version_tree_list = []
+ ebuild_version_tree_dict_new = {}
+ succes = True
+ for cpv in sorted(get_all_cpv_from_package(myportdb, cp, repo_path)):
+ LOG.debug("Checking %s", cpv)
+ ebuild_version_tree = portage.versions.cpv_getversion(cpv)
+ package = portage.versions.catpkgsplit(cpv)[1]
+ ebuild_file = repo_path + "/" + cp + "/" + package + "-" + ebuild_version_tree + ".ebuild"
+ if not os.path.isfile(ebuild_file):
+ LOG.error("File %s is not found for %s", ebuild_file, cpv)
+ return False, ebuild_version_tree_dict_new
+ ebuild_version_checksum_tree = perform_checksum(ebuild_file, "SHA256")[0]
+ ebuild_version_db = objects.ebuild.Ebuild.get_by_name(context, ebuild_version_tree, filters=filters)
+ if ebuild_version_db is None or ebuild_version_db.checksum != ebuild_version_checksum_tree:
+ if ebuild_version_db is not None and ebuild_version_db.checksum != ebuild_version_checksum_tree:
+ LOG.debug("Update %s", cpv)
+ deleted_cpv_db(context, ebuild_version_db.uuid)
+ else:
+ LOG.info("Adding %s to the database", cpv)
+ ebuild_version_db = create_cpv_db(context, ebuild_version_tree, ebuild_version_checksum_tree, package_uuid)
+ succes = create_cpv_metadata_db(context, myportdb, cpv, ebuild_file, ebuild_version_db, repo_db)
+ ebuild_version_tree_dict_new[cpv] = ebuild_version_db.uuid
+ ebuild_version_tree_list.append(ebuild_version_tree)
+ for ebuild_db in objects.ebuild.EbuildList.get_all(context, filters=filters):
+ if not ebuild_db.version in ebuild_version_tree_list:
+ LOG.info("Deleting %s in the database", ebuild_db.version)
+ deleted_cpv_db(context, ebuild_db.uuid)
+ return succes, ebuild_version_tree_dict_new
diff --git a/gosbs/scheduler/email.py b/gosbs/scheduler/email.py
new file mode 100644
index 0000000..581b22a
--- /dev/null
+++ b/gosbs/scheduler/email.py
@@ -0,0 +1,36 @@
+# Copyright 1999-2020 Gentoo Authors
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_log import log as logging
+from gosbs import objects
+import gosbs.conf
+
+CONF = gosbs.conf.CONF
+
+LOG = logging.getLogger(__name__)
+
+def create_email(context, email):
+ email_db = objects.email.Email()
+ email_db.email = email
+ email_db.create(context)
+ return email_db
+
+def check_email_db(context, email):
+ email_db = objects.email.Email.get_by_name(context, email)
+ if email_db is None:
+ email_db = create_email(context, email)
+ else:
+ if email_db.deleted is True:
+ email_db.deleted = False
+ email_db.save(context)
+ return email_db.id
diff --git a/gosbs/scheduler/manager.py b/gosbs/scheduler/manager.py
new file mode 100644
index 0000000..fea9923
--- /dev/null
+++ b/gosbs/scheduler/manager.py
@@ -0,0 +1,141 @@
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Justin Santa Barbara
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/compute/manager.py
+# We have change the code so it will fit what we need.
+# It need more cleaning.
+
+"""Handles all processes relating to instances (guest vms).
+
+The :py:class:`ComputeManager` class is a :py:class:`nova.manager.Manager` that
+handles RPC calls relating to creating instances. It is responsible for
+building a disk image, launching it via the underlying virtualization driver,
+responding to calls to check its state, attaching persistent storage, and
+terminating it.
+
+"""
+#import functools
+#import json
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
+from importlib import import_module
+import pytz
+import sys
+
+from oslo_log import log as logging
+import oslo_messaging as messaging
+from oslo_service import periodic_task
+from oslo_utils import timeutils
+from openstack import connection
+
+from gosbs.scheduler import rpcapi as scheduler_rpcapi
+from gosbs import rpc
+#from gosbs import exception_wrapper
+from gosbs import manager
+from gosbs import objects
+from gosbs.objects import base as obj_base
+from gosbs.objects import fields
+from gosbs.tasks import scheduler as scheduler_tasks
+from gosbs.common.task import run_task
+
+import gosbs.conf
+
+CONF = gosbs.conf.CONF
+
+LOG = logging.getLogger(__name__)
+
+#get_notifier = functools.partial(rpc.get_notifier, service='scheduler')
+#wrap_exception = functools.partial(exception_wrapper.wrap_exception,
+# get_notifier=get_notifier,
+# binary='gobs-scheduler')
+
+class SchedulerManager(manager.Manager):
+ """Manages the running instances from creation to destruction."""
+
+ #target = messaging.Target(version='1.0')
+
+ def __init__(self, *args, **kwargs):
+ """Load configuration options and connect to the hypervisor."""
+ self.scheduler_rpcapi = scheduler_rpcapi.SchedulerAPI()
+
+ super(SchedulerManager, self).__init__(service_name="scheduler",
+ *args, **kwargs)
+
+
+ def init_host(self):
+ context = gosbs.context.get_admin_context()
+
+ def pre_start_hook(self):
+ context = gosbs.context.get_admin_context()
+ self.openstack_conn = connection.Connection(
+ region_name = CONF.keystone.region_name,
+ auth=dict(
+ auth_url = CONF.keystone.auth_url,
+ username = CONF.keystone.username,
+ password = CONF.keystone.password,
+ project_id = CONF.keystone.project_id,
+ user_domain_id = CONF.keystone.user_domain_name),
+ scheduler_api_version = CONF.keystone.auth_version,
+ identity_interface= CONF.keystone.identity_interface)
+ self.service_ref = objects.Service.get_by_host_and_topic(
+ context, self.host, "scheduler")
+ scheduler_tasks.activete_all_tasks(context, self.service_ref.uuid)
+
+ def reset(self):
+ LOG.info('Reloading scheduler RPC API')
+ scheduler_rpcapi.LAST_VERSION = None
+ self.scheduler_rpcapi = scheduler_rpcapi.SchedulerAPI()
+
+ @periodic_task.periodic_task
+ def update_repo_task(self, context):
+ task_name = 'update_repos'
+ LOG.debug("Runing task %s", task_name)
+ filters = { 'status' : 'waiting',
+ 'name' : task_name,
+ 'service_uuid' : self.service_ref.uuid,
+ }
+ run_task(context, filters, self.service_ref)
+
+ @periodic_task.periodic_task
+ def update_git_task(self, context):
+ task_name = 'update_git'
+ LOG.debug("Runing task %s", task_name)
+ filters = { 'status' : 'waiting',
+ 'name' : task_name,
+ 'service_uuid' : self.service_ref.uuid,
+ }
+ run_task(context, filters, self.service_ref)
+
+ @periodic_task.periodic_task
+ def rebuild_db_task(self, context):
+ task_name = 'rebuild_db'
+ LOG.debug("Runing task %s", task_name)
+ filters = { 'status' : 'waiting',
+ 'name' : task_name,
+ 'service_uuid' : self.service_ref.uuid,
+ }
+ run_task(context, filters, self.service_ref)
+
+ @periodic_task.periodic_task
+ def update_db_task(self, context):
+ task_name = 'update_db'
+ LOG.debug("Runing task %s", task_name)
+ filters = { 'status' : 'waiting',
+ 'name' : task_name,
+ 'service_uuid' : self.service_ref.uuid,
+ }
+ run_task(context, filters, self.service_ref)
diff --git a/gosbs/scheduler/package.py b/gosbs/scheduler/package.py
new file mode 100644
index 0000000..07b467c
--- /dev/null
+++ b/gosbs/scheduler/package.py
@@ -0,0 +1,180 @@
+# Copyright 1999-2020 Gentoo Authors
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import os
+import git
+
+import portage
+from portage.xml.metadata import MetaDataXML
+from portage.checksum import perform_checksum
+
+from oslo_log import log as logging
+from oslo_utils import uuidutils
+
+from gosbs import objects
+from gosbs.scheduler.email import check_email_db
+from gosbs.scheduler.ebuild import check_cpv_db, deleted_cpv_db
+import gosbs.conf
+
+CONF = gosbs.conf.CONF
+LOG = logging.getLogger(__name__)
+
+def get_all_cp_from_repo(myportdb, repopath):
+ repo_dir_list = []
+ repo_dir_list.append(repopath)
+ # Get the package list from the repo
+ return myportdb.cp_all(trees=repo_dir_list)
+
+def get_git_changelog_text(repodir, cp):
+ n = '5'
+ git_log_pkg = ''
+ g = git.Git(repodir)
+ git_log_pkg = g.log('-n ' + n, '--grep=' + cp)
+ return git_log_pkg
+
+def get_package_metadataDict(repodir, cp):
+ # Make package_metadataDict
+ package_metadataDict = {}
+ try:
+ package_metadataDict['metadata_xml_checksum'] = perform_checksum(repodir + "/" + cp + "/metadata.xml", "SHA256")[0]
+ except Exception as e:
+ package_metadataDict['metadata_xml_checksum'] = False
+ return package_metadataDict
+ md_email_list = []
+ pkg_md = MetaDataXML(repodir + "/" + cp + "/metadata.xml", None)
+ tmp_descriptions = pkg_md.descriptions()
+ if tmp_descriptions:
+ package_metadataDict['metadata_xml_descriptions'] = tmp_descriptions[0]
+ else:
+ package_metadataDict['metadata_xml_descriptions'] = ''
+ tmp_herds = pkg_md.herds()
+ if tmp_herds:
+ package_metadataDict['metadata_xml_herds'] = tmp_herds[0]
+ md_email_list.append(package_metadataDict['metadata_xml_herds'] + '@gentoo.org')
+ for maint in pkg_md.maintainers():
+ md_email_list.append(maint.email)
+ if md_email_list != []:
+ package_metadataDict['metadata_xml_email'] = md_email_list
+ else:
+ md_email_list.append('maintainer-needed@gentoo.org')
+ package_metadataDict['metadata_xml_email'] = md_email_list
+ #log_msg = "Metadata file %s missing Email. Setting it to maintainer-needed" % (pkgdir + "/metadata.xml")
+ #write_log(self._session, log_msg, "warning", self._config_id, 'packages.get_package_metadataDict')
+ package_metadataDict['git_changlog'] = get_git_changelog_text(repodir, cp)
+ return package_metadataDict
+
+def update_cp_metadata_db(context, cp, repo_name, package_metadata_db):
+ repodir = CONF.repopath + '/' + repo_name + '.git'
+ package_metadataDict = get_package_metadataDict(repodir, cp)
+ if not package_metadataDict['metadata_xml_checksum']:
+ return False
+ package_metadata_db.gitlog = package_metadataDict['git_changlog']
+ package_metadata_db.description = package_metadataDict['metadata_xml_descriptions']
+ package_metadata_db.checksum = package_metadataDict['metadata_xml_checksum']
+ package_metadata_db.save(context)
+ return package_metadataDict['metadata_xml_email']
+
+def create_cp_metadata_db(context, cp, repo_name, package_uuid):
+ repodir = CONF.repopath + '/' + repo_name + '.git'
+ package_metadataDict = get_package_metadataDict(repodir, cp)
+ if not package_metadataDict['metadata_xml_checksum']:
+ return False
+ package_metadata_db = objects.package_metadata.PackageMetadata()
+ package_metadata_db.package_uuid = package_uuid
+ package_metadata_db.gitlog = package_metadataDict['git_changlog']
+ package_metadata_db.description = package_metadataDict['metadata_xml_descriptions']
+ package_metadata_db.checksum = package_metadataDict['metadata_xml_checksum']
+ package_metadata_db.create(context)
+ return package_metadataDict['metadata_xml_email']
+
+def create_cp_email_db(context, email_id, package_uuid):
+ package_email_db = objects.package_email.PackageEmail()
+ package_email_db.package_uuid = package_uuid
+ package_email_db.email_id = email_id
+ package_email_db.create(context)
+
+def check_cp_email_db(context, metadata_xml_email, package_uuid):
+ filters = { 'package_uuid' : package_uuid }
+ for package_email in metadata_xml_email:
+ email_id = check_email_db(context, package_email)
+ package_email_db = objects.package_email.PackageEmail.get_by_email_id(context, email_id, filters=filters)
+ if package_email_db is None:
+ create_cp_email_db(context, email_id, package_uuid)
+ return True
+
+def check_cp_metadata_db(context, cp, repo_name, package_uuid):
+ repodir = CONF.repopath + '/' + repo_name + '.git'
+ succes = True
+ package_metadata_db = objects.package_metadata.PackageMetadata.get_by_uuid(context, package_uuid)
+ if package_metadata_db is None:
+ LOG.debug("Adding %s metadata", cp)
+ metadata_xml_email = create_cp_metadata_db(context, cp, repo_name, package_uuid)
+ succes = check_cp_email_db(context, metadata_xml_email, package_uuid)
+ else:
+ package_metadata_tree_checksum = perform_checksum(repodir + "/" + cp + "/metadata.xml", "SHA256")[0]
+ if package_metadata_tree_checksum != package_metadata_db.checksum:
+ LOG.debug("Update %s metadata", cp)
+ metadata_xml_email = update_cp_metadata_db(context, cp, repo_name, package_metadata_db)
+ succes = check_cp_email_db(context, metadata_xml_email, package_uuid)
+ return succes
+
+def deleted_cp_db(context, package_db):
+ filters = { 'deleted' : False,
+ 'package_uuid' : package_db.uuid,
+ }
+ for ebuild_db in objects.ebuild.EbuildList.get_all(context, filters=filters):
+ LOG.info("Deleting %s in the database", ebuild_db.version)
+ deleted_cpv_db(context, ebuild_db.uuid)
+ package_db.deleted = True
+ package_db.status = 'completed'
+ package_db.save(context)
+
+def create_cp_db(context, package, repo_db, category_db):
+ package_db = objects.package.Package()
+ package_db.uuid = uuidutils.generate_uuid()
+ package_db.name = package
+ package_db.status = 'in-progress'
+ package_db.category_uuid = category_db.uuid
+ package_db.repo_uuid = repo_db.uuid
+ package_db.create(context)
+ return package_db
+
+def check_cp_db(context, myportdb, cp, repo_db, category_db):
+ package = cp.split('/')[1]
+ cp_path = CONF.repopath + '/' + repo_db.name + '.git/' + cp
+ filters = { 'repo_uuid' : repo_db.uuid,
+ 'category_uuid' : category_db.uuid,
+ 'deleted' : False,
+ }
+ package_db = objects.package.Package.get_by_name(context, package, filters=filters)
+ if not os.path.isdir(cp_path) and package_db is None:
+ LOG.error("Path %s is not found for %s", cp_path, cp)
+ return False, {}
+ elif not os.path.isdir(cp_path) and package_db is not None:
+ LOG.info("Deleting %s in the database", cp)
+ deleted_cp_db(context, package_db)
+ return True, {}
+ elif os.path.isdir(cp_path) and package_db is None:
+ LOG.info("Adding %s to the database", cp)
+ package_db = create_cp_db(context, package, repo_db, category_db)
+ package_db.status = 'in-progress'
+ package_db.save(context)
+ succes1 = check_cp_metadata_db(context, cp, repo_db.name, package_db.uuid)
+ succes2, ebuild_version_tree_dict_new = check_cpv_db(context, myportdb, cp, repo_db, package_db.uuid)
+ if not succes1 or not succes2:
+ package_db.status = 'faild'
+ package_db.save(context)
+ return False, ebuild_version_tree_dict_new
+ package_db.status = 'completed'
+ package_db.save(context)
+ return True, ebuild_version_tree_dict_new
diff --git a/gosbs/scheduler/project.py b/gosbs/scheduler/project.py
new file mode 100644
index 0000000..5c49be6
--- /dev/null
+++ b/gosbs/scheduler/project.py
@@ -0,0 +1,35 @@
+# Copyright 1999-2020 Gentoo Authors
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_log import log as logging
+
+from gosbs import objects
+import gosbs.conf
+
+CONF = gosbs.conf.CONF
+LOG = logging.getLogger(__name__)
+
+def get_project(context, service_repo_db):
+ project_db = objects.project.Project.get_by_name(context, CONF.scheduler.db_project_repo)
+ project_metadata_db = objects.project_metadata.ProjectMetadata.get_by_uuid(context, project_db.uuid)
+ filters = { 'project_uuid' : project_db.uuid,
+ 'repo_uuid' : service_repo_db.repo_uuid,
+ }
+ project_repo_db = objects.project_repo.ProjectRepo.get_by_filters(context, filters=filters)
+ if project_repo_db is None:
+ project_repo_db = objects.project_repo.ProjectRepo()
+ project_repo_db.project_uuid = project_db.uuid
+ project_repo_db.repo_uuid = service_repo_db.repo_uuid
+ project_repo_db.build = False
+ project_repo_db.create(context)
+ return project_db, project_metadata_db
diff --git a/gosbs/scheduler/rpcapi.py b/gosbs/scheduler/rpcapi.py
new file mode 100644
index 0000000..3af4067
--- /dev/null
+++ b/gosbs/scheduler/rpcapi.py
@@ -0,0 +1,127 @@
+# Copyright 2013 Red Hat, Inc.
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/compute/rpcapi.py
+# We have change the code so it will fit what we need.
+# It need more cleaning and work.
+
+"""
+Client side of the scheduler RPC API.
+"""
+
+from oslo_log import log as logging
+import oslo_messaging as messaging
+from oslo_serialization import jsonutils
+
+import gosbs.conf
+from gosbs import context
+from gosbs import exception
+from gosbs.i18n import _
+from gosbs import objects
+from gosbs.objects import base as objects_base
+from gosbs.objects import service as service_obj
+from gosbs import profiler
+from gosbs import rpc
+
+CONF = gosbs.conf.CONF
+RPC_TOPIC = "scheduler"
+
+LOG = logging.getLogger(__name__)
+LAST_VERSION = None
+
+@profiler.trace_cls("rpc")
+class SchedulerAPI(object):
+ '''Client side of the compute rpc API.
+
+ * 5.0 - Remove 4.x compatibility
+ '''
+
+ VERSION_ALIASES = {
+ 'rocky': '1.0',
+ }
+
+ def __init__(self):
+ super(SchedulerAPI, self).__init__()
+ target = messaging.Target(topic=RPC_TOPIC, version='1.0')
+ upgrade_level = CONF.upgrade_levels.scheduler
+ if upgrade_level == 'auto':
+ version_cap = self._determine_version_cap(target)
+ else:
+ version_cap = self.VERSION_ALIASES.get(upgrade_level,
+ upgrade_level)
+ serializer = objects_base.NovaObjectSerializer()
+
+ # NOTE(danms): We need to poke this path to register CONF options
+ # that we use in self.get_client()
+ rpc.get_client(target, version_cap, serializer)
+
+ default_client = self.get_client(target, version_cap, serializer)
+ self.router = rpc.ClientRouter(default_client)
+
+ def _determine_version_cap(self, target):
+ global LAST_VERSION
+ if LAST_VERSION:
+ return LAST_VERSION
+ service_version = objects.Service.get_minimum_version(
+ context.get_admin_context(), 'gosbs-scheduler')
+
+ history = service_obj.SERVICE_VERSION_HISTORY
+
+ # NOTE(johngarbutt) when there are no nova-compute services running we
+ # get service_version == 0. In that case we do not want to cache
+ # this result, because we will get a better answer next time.
+ # As a sane default, return the current version.
+ if service_version == 0:
+ LOG.debug("Not caching compute RPC version_cap, because min "
+ "service_version is 0. Please ensure a nova-compute "
+ "service has been started. Defaulting to current "
+ "version.")
+ return history[service_obj.SERVICE_VERSION]['scheduler_rpc']
+
+ try:
+ version_cap = history[service_version]['scheduler_rpc']
+ except IndexError:
+ LOG.error('Failed to extract compute RPC version from '
+ 'service history because I am too '
+ 'old (minimum version is now %(version)i)',
+ {'version': service_version})
+ raise exception.ServiceTooOld(thisver=service_obj.SERVICE_VERSION,
+ minver=service_version)
+ except KeyError:
+ LOG.error('Failed to extract compute RPC version from '
+ 'service history for version %(version)i',
+ {'version': service_version})
+ return target.version
+ LAST_VERSION = version_cap
+ LOG.info('Automatically selected compute RPC version %(rpc)s '
+ 'from minimum service version %(service)i',
+ {'rpc': version_cap,
+ 'service': service_version})
+ return version_cap
+
+ # Cells overrides this
+ def get_client(self, target, version_cap, serializer):
+ if CONF.rpc_response_timeout > rpc.HEARTBEAT_THRESHOLD:
+ # NOTE(danms): If the operator has overridden RPC timeout
+ # to be longer than rpc.HEARTBEAT_THRESHOLD then configure
+ # the call monitor timeout to be the threshold to keep the
+ # failure timing characteristics that our code likely
+ # expects (from history) while allowing healthy calls
+ # to run longer.
+ cmt = rpc.HEARTBEAT_THRESHOLD
+ else:
+ cmt = None
+ return rpc.get_client(target,
+ version_cap=version_cap,
+ serializer=serializer,
+ call_monitor_timeout=cmt)
diff --git a/gosbs/service.py b/gosbs/service.py
new file mode 100644
index 0000000..ea67f81
--- /dev/null
+++ b/gosbs/service.py
@@ -0,0 +1,331 @@
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Justin Santa Barbara
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/service.py
+# we removed class WSGIService
+
+"""Generic Node base class for all workers that run on hosts."""
+
+import os
+import random
+import sys
+
+#from oslo_concurrency import processutils
+from oslo_log import log as logging
+import oslo_messaging as messaging
+from oslo_service import service
+from oslo_utils import importutils
+
+#from nova.api import wsgi as api_wsgi
+from gosbs import baserpc
+#from gosbs import conductor
+import gosbs.conf
+from gosbs import context
+from gosbs import debugger
+from gosbs import exception
+from gosbs.i18n import _, _LE, _LI, _LW
+from gosbs import objects
+from gosbs.objects import base as objects_base
+from gosbs.objects import service as service_obj
+from gosbs import rpc
+#from gosbs import servicegroup
+from gosbs import utils
+from gosbs import version
+#from gosbs import wsgi
+
+osprofiler = importutils.try_import("osprofiler")
+osprofiler_initializer = importutils.try_import("osprofiler.initializer")
+
+
+LOG = logging.getLogger(__name__)
+
+CONF = gosbs.conf.CONF
+
+SERVICE_MANAGERS = {
+ 'gosbs-gitmirror': 'gosbs.gitmirror.manager.GitMirrorManager',
+ 'gosbs-scheduler': 'gosbs.scheduler.manager.SchedulerManager',
+}
+
+
+def _create_service_ref(this_service, context):
+ service = objects.Service(context)
+ service.host = this_service.host
+ service.binary = this_service.binary
+ service.topic = this_service.topic
+ service.report_count = 0
+ service.create()
+ return service
+
+
+def _update_service_ref(service):
+ if service.version != service_obj.SERVICE_VERSION:
+ LOG.info(_LI('Updating service version for %(binary)s on '
+ '%(host)s from %(old)i to %(new)i'),
+ {'binary': service.binary,
+ 'host': service.host,
+ 'old': service.version,
+ 'new': service_obj.SERVICE_VERSION})
+ service.version = service_obj.SERVICE_VERSION
+ service.save()
+
+
+def setup_profiler(binary, host):
+ if osprofiler and CONF.profiler.enabled:
+ osprofiler.initializer.init_from_conf(
+ conf=CONF,
+ context=context.get_admin_context().to_dict(),
+ project="gosbs",
+ service=binary,
+ host=host)
+ LOG.info(_LI("OSProfiler is enabled."))
+
+
+def assert_eventlet_uses_monotonic_clock():
+ from eventlet import hubs
+ import monotonic
+
+ hub = hubs.get_hub()
+ if hub.clock is not monotonic.monotonic:
+ raise RuntimeError(
+ 'eventlet hub is not using a monotonic clock - '
+ 'periodic tasks will be affected by drifts of system time.')
+
+
+class Service(service.Service):
+ """Service object for binaries running on hosts.
+
+ A service takes a manager and enables rpc by listening to queues based
+ on topic. It also periodically runs tasks on the manager and reports
+ its state to the database services table.
+ """
+
+ def __init__(self, host, binary, topic, manager, report_interval=None,
+ periodic_enable=None, periodic_fuzzy_delay=None,
+ periodic_interval_max=None, *args, **kwargs):
+ super(Service, self).__init__()
+ self.host = host
+ self.binary = binary
+ self.topic = topic
+ self.manager_class_name = manager
+ #self.servicegroup_api = servicegroup.API()
+ manager_class = importutils.import_class(self.manager_class_name)
+ #if objects_base.NovaObject.indirection_api:
+ # conductor_api = conductor.API()
+ # conductor_api.wait_until_ready(context.get_admin_context())
+ self.manager = manager_class(host=self.host, *args, **kwargs)
+ self.rpcserver = None
+ self.report_interval = report_interval
+ self.periodic_enable = periodic_enable
+ self.periodic_fuzzy_delay = periodic_fuzzy_delay
+ self.periodic_interval_max = periodic_interval_max
+ self.saved_args, self.saved_kwargs = args, kwargs
+ self.backdoor_port = None
+ setup_profiler(binary, self.host)
+
+ def __repr__(self):
+ return "<%(cls_name)s: host=%(host)s, binary=%(binary)s, " \
+ "manager_class_name=%(manager)s>" % {
+ 'cls_name': self.__class__.__name__,
+ 'host': self.host,
+ 'binary': self.binary,
+ 'manager': self.manager_class_name
+ }
+
+ def start(self):
+ """Start the service.
+
+ This includes starting an RPC service, initializing
+ periodic tasks, etc.
+ """
+ assert_eventlet_uses_monotonic_clock()
+
+ verstr = version.version_string_with_package()
+ LOG.info(_LI('Starting %(topic)s server (version %(version)s)'),
+ {'topic': self.topic, 'version': verstr})
+ self.basic_config_check()
+ self.manager.init_host()
+ self.model_disconnected = False
+ ctxt = context.get_admin_context()
+ self.service_ref = objects.Service.get_by_host_and_binary(
+ ctxt, self.host, self.binary)
+ if self.service_ref:
+ _update_service_ref(self.service_ref)
+
+ else:
+ try:
+ self.service_ref = _create_service_ref(self, ctxt)
+ except (exception.ServiceTopicExists,
+ exception.ServiceBinaryExists):
+ # NOTE(danms): If we race to create a record with a sibling
+ # worker, don't fail here.
+ self.service_ref = objects.Service.get_by_host_and_binary(
+ ctxt, self.host, self.binary)
+
+ self.manager.pre_start_hook()
+
+ if self.backdoor_port is not None:
+ self.manager.backdoor_port = self.backdoor_port
+
+ LOG.debug("Creating RPC server for service %s", self.topic)
+
+ target = messaging.Target(topic=self.topic, server=self.host)
+
+ endpoints = [
+ self.manager,
+ baserpc.BaseRPCAPI(self.manager.service_name, self.backdoor_port)
+ ]
+ endpoints.extend(self.manager.additional_endpoints)
+
+ serializer = objects_base.NovaObjectSerializer()
+
+ self.rpcserver = rpc.get_server(target, endpoints, serializer)
+ #self.rpcserver.start()
+
+ self.manager.post_start_hook()
+
+ LOG.debug("Join ServiceGroup membership for this service %s",
+ self.topic)
+ # Add service to the ServiceGroup membership group.
+ #self.servicegroup_api.join(self.host, self.topic, self)
+
+ if self.periodic_enable:
+ if self.periodic_fuzzy_delay:
+ initial_delay = random.randint(0, self.periodic_fuzzy_delay)
+ else:
+ initial_delay = None
+
+ self.tg.add_dynamic_timer(self.periodic_tasks,
+ initial_delay=initial_delay,
+ periodic_interval_max=
+ self.periodic_interval_max)
+
+ def __getattr__(self, key):
+ manager = self.__dict__.get('manager', None)
+ return getattr(manager, key)
+
+ @classmethod
+ def create(cls, host=None, binary=None, topic=None, manager=None,
+ report_interval=None, periodic_enable=None,
+ periodic_fuzzy_delay=None, periodic_interval_max=None):
+ """Instantiates class and passes back application object.
+
+ :param host: defaults to CONF.host
+ :param binary: defaults to basename of executable
+ :param topic: defaults to bin_name - 'nova-' part
+ :param manager: defaults to CONF.<topic>_manager
+ :param report_interval: defaults to CONF.report_interval
+ :param periodic_enable: defaults to CONF.periodic_enable
+ :param periodic_fuzzy_delay: defaults to CONF.periodic_fuzzy_delay
+ :param periodic_interval_max: if set, the max time to wait between runs
+
+ """
+ if not host:
+ host = CONF.host
+ if not binary:
+ binary = os.path.basename(sys.argv[0])
+ if not topic:
+ topic = binary.rpartition('gosbs-')[2]
+ if not manager:
+ manager = SERVICE_MANAGERS.get(binary)
+ if report_interval is None:
+ report_interval = CONF.report_interval
+ if periodic_enable is None:
+ periodic_enable = CONF.periodic_enable
+ if periodic_fuzzy_delay is None:
+ periodic_fuzzy_delay = CONF.periodic_fuzzy_delay
+
+ debugger.init()
+
+ service_obj = cls(host, binary, topic, manager,
+ report_interval=report_interval,
+ periodic_enable=periodic_enable,
+ periodic_fuzzy_delay=periodic_fuzzy_delay,
+ periodic_interval_max=periodic_interval_max)
+
+ return service_obj
+
+ def kill(self):
+ """Destroy the service object in the datastore.
+
+ NOTE: Although this method is not used anywhere else than tests, it is
+ convenient to have it here, so the tests might easily and in clean way
+ stop and remove the service_ref.
+
+ """
+ self.stop()
+ try:
+ self.service_ref.destroy()
+ except exception.NotFound:
+ LOG.warning(_LW('Service killed that has no database entry'))
+
+ def stop(self):
+ """stop the service and clean up."""
+ try:
+ self.rpcserver.stop()
+ self.rpcserver.wait()
+ except Exception:
+ pass
+
+ try:
+ self.manager.cleanup_host()
+ except Exception:
+ LOG.exception(_LE('Service error occurred during cleanup_host'))
+ pass
+
+ super(Service, self).stop()
+
+ def periodic_tasks(self, raise_on_error=False):
+ """Tasks to be run at a periodic interval."""
+ ctxt = context.get_admin_context()
+ return self.manager.periodic_tasks(ctxt, raise_on_error=raise_on_error)
+
+ def basic_config_check(self):
+ """Perform basic config checks before starting processing."""
+ # Make sure the tempdir exists and is writable
+ try:
+ with utils.tempdir():
+ pass
+ except Exception as e:
+ LOG.error(_LE('Temporary directory is invalid: %s'), e)
+ sys.exit(1)
+
+ def reset(self):
+ """reset the service."""
+ self.manager.reset()
+
+
+def process_launcher():
+ return service.ProcessLauncher(CONF, restart_method='mutate')
+
+
+# NOTE(vish): the global launcher is to maintain the existing
+# functionality of calling service.serve +
+# service.wait
+_launcher = None
+
+
+def serve(server, workers=None):
+ global _launcher
+ if _launcher:
+ raise RuntimeError(_('serve() can only be called once'))
+
+ _launcher = service.launch(CONF, server, workers=workers,
+ restart_method='mutate')
+
+
+def wait():
+ _launcher.wait()
diff --git a/gosbs/tasks/__init__.py b/gosbs/tasks/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gosbs/tasks/__init__.py
diff --git a/gosbs/tasks/scheduler/__init__.py b/gosbs/tasks/scheduler/__init__.py
new file mode 100644
index 0000000..cda600c
--- /dev/null
+++ b/gosbs/tasks/scheduler/__init__.py
@@ -0,0 +1,18 @@
+# Copyright 1999-2020 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+from datetime import datetime
+
+from oslo_log import log as logging
+
+from gosbs import objects
+from gosbs.common.task import check_task_db
+
+LOG = logging.getLogger(__name__)
+
+def activete_all_tasks(context, service_uuid):
+ # Tasks
+ check_task_db(context, 'update_repos', datetime(1, 1, 1, 0, 15, 0, 0), True, service_uuid)
+ check_task_db(context, 'update_git', datetime(1, 1, 1, 0, 5, 0, 0), True, service_uuid)
+ check_task_db(context, 'update_db', datetime(1, 1, 1, 0, 5, 0, 0), True, service_uuid)
+ check_task_db(context, 'rebuild_db', datetime(1, 1, 1, 0, 10, 0, 0), True, service_uuid)
diff --git a/gosbs/tasks/scheduler/rebuild_db.py b/gosbs/tasks/scheduler/rebuild_db.py
new file mode 100644
index 0000000..2eee2b8
--- /dev/null
+++ b/gosbs/tasks/scheduler/rebuild_db.py
@@ -0,0 +1,52 @@
+# Copyright 1999-2020 Gentoo Authors
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_log import log as logging
+
+from gosbs.common.portage_settings import get_portage_settings
+from gosbs.scheduler.category import check_c_db
+from gosbs.scheduler.package import check_cp_db, get_all_cp_from_repo
+from gosbs.scheduler.project import get_project
+from gosbs import objects
+import gosbs.conf
+
+CONF = gosbs.conf.CONF
+LOG = logging.getLogger(__name__)
+
+def rebuild_repo_db_thread(context, service_repo_db):
+ project_db, project_metadata_db = get_project(context, service_repo_db)
+ repo_db = objects.repo.Repo.get_by_uuid(context, service_repo_db.repo_uuid)
+ mysettings, myportdb = get_portage_settings(context, project_metadata_db, project_db.name)
+ repo_path = CONF.repopath + '/' + repo_db.name + '.git'
+ LOG.debug("Rebuilding repo %s", repo_db.name)
+ for cp in sorted(get_all_cp_from_repo(myportdb, repo_path)):
+ category = cp.split('/')[0]
+ succesc = check_c_db(context, category, repo_db)
+ category_db = objects.category.Category.get_by_name(context, category)
+ succesp = check_cp_db(context, myportdb, cp, repo_db, category_db)
+ return True
+
+def task(context, service_uuid):
+ filters = {
+ 'service_uuid' : service_uuid,
+ 'status' : 'rebuild_db',
+ }
+ for service_repo_db in objects.service_repo.ServiceRepoList.get_all(context, filters=filters):
+ service_repo_db.status = 'in-progress'
+ service_repo_db.save(context)
+ succes = rebuild_repo_db_thread(context, service_repo_db)
+ if not succes:
+ service_repo_db.status = 'failed'
+ else:
+ service_repo_db.status = 'completed'
+ service_repo_db.save(context)
diff --git a/gosbs/tasks/scheduler/sub/__init__.py b/gosbs/tasks/scheduler/sub/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gosbs/tasks/scheduler/sub/__init__.py
diff --git a/gosbs/tasks/scheduler/sub/build.py b/gosbs/tasks/scheduler/sub/build.py
new file mode 100644
index 0000000..20f981a
--- /dev/null
+++ b/gosbs/tasks/scheduler/sub/build.py
@@ -0,0 +1,73 @@
+# Copyright 1999-2020 Gentoo Authors
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_log import log as logging
+
+from gosbs.common.portage_settings import get_portage_settings, clean_portage_settings
+from gosbs.common.flags import get_all_cpv_use, filter_flags, get_iuse, reduce_flags
+from gosbs import objects
+
+LOG = logging.getLogger(__name__)
+
+def build_sub_task(context, cp, ebuild_version_tree_dict_new, repo_db):
+ user_db = objects.user.User.get_by_name(context, 'scheduler')
+ filters = { 'build' : True,
+ 'repo_uuid' : repo_db.uuid,
+ }
+ for project_repo_db in objects.project_repo.ProjectRepoList.get_all(context, filters=filters):
+ project_db = objects.project.Project.get_by_uuid(context, project_repo_db.project_uuid)
+ if project_db.active and project_db.auto:
+ project_metadata_db = objects.project_metadata.ProjectMetadata.get_by_uuid(context, project_db.uuid)
+ mysettings, myportdb = get_portage_settings(context, project_metadata_db, project_db.name)
+ build_cpv = myportdb.xmatch('bestmatch-visible', cp)
+ if build_cpv != "" and build_cpv in ebuild_version_tree_dict_new:
+ (final_use, use_expand_hidden, usemasked, useforced) = \
+ get_all_cpv_use(build_cpv, myportdb, mysettings)
+ iuse_flags = filter_flags(get_iuse(build_cpv, myportdb), use_expand_hidden,
+ usemasked, useforced, mysettings)
+ final_flags = filter_flags(final_use, use_expand_hidden,
+ usemasked, useforced, mysettings)
+ iuse_flags2 = reduce_flags(iuse_flags)
+ iuse_flags_list = list(set(iuse_flags2))
+ use_disable = list(set(iuse_flags_list).difference(set(final_flags)))
+ # Make a dict with enable and disable use flags for ebuildqueuedwithuses
+ use_flagsDict = {}
+ for x in final_flags:
+ use_flagsDict[x] = True
+ for x in use_disable:
+ use_flagsDict[x] = False
+ enable_test = False
+ if project_repo_db.test and not "test" in usemasked:
+ enable_test = True
+ restrictions_filters = { 'ebuild_uuid' : ebuild_version_tree_dict_new[build_cpv], }
+ restrictions_list = objects.ebuild_restriction.EbuildRestrictionList.get_all(context, filters=restrictions_filters)
+ if not restrictions_list is None:
+ if ("test" in restrictions_list or "fetch" in restrictions_list) and "test" in use_flagsDict:
+ enable_test = False
+ if "test" in use_flagsDict and enable_test:
+ use_flagsDict['test'] = True
+ project_build_db = objects.project_build.ProjectBuild()
+ project_build_db.project_uuid = project_db.uuid
+ project_build_db.ebuild_uuid = ebuild_version_tree_dict_new[build_cpv]
+ project_build_db.status = 'waiting'
+ project_build_db.user_id = user_db.id
+ project_build_db.create(context)
+ for k, v in use_flagsDict.items():
+ iuse_db = objects.use.Use.get_by_name(context, k)
+ build_iuse_db = objects.build_iuse.BuildIUse()
+ build_iuse_db.build_uuid = project_build_db.uuid
+ build_iuse_db.use_id = iuse_db.id
+ build_iuse_db.status = v
+ build_iuse_db.create(context)
+ clean_portage_settings(myportdb)
+ return True
diff --git a/gosbs/tasks/scheduler/update_db.py b/gosbs/tasks/scheduler/update_db.py
new file mode 100644
index 0000000..0800e28
--- /dev/null
+++ b/gosbs/tasks/scheduler/update_db.py
@@ -0,0 +1,60 @@
+# Copyright 1999-2020 Gentoo Authors
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_log import log as logging
+
+from gosbs.common.portage_settings import get_portage_settings, clean_portage_settings
+from gosbs.scheduler.package import check_cp_db
+from gosbs.scheduler.project import get_project
+from gosbs.tasks.scheduler.sub.build import build_sub_task
+from gosbs import objects
+import gosbs.conf
+
+CONF = gosbs.conf.CONF
+LOG = logging.getLogger(__name__)
+
+def update_repo_db_multi_thread(context, myportdb, repo_db, package_db):
+ category_db = objects.category.Category.get_by_uuid(context, package_db.category_uuid)
+ cp = category_db.name + '/' + package_db.name
+ succes, ebuild_version_tree_dict_new = check_cp_db(context, myportdb, cp, repo_db, category_db)
+ # sub tasks
+ succes = build_sub_task(context, cp, ebuild_version_tree_dict_new, repo_db)
+ return True
+
+def update_repo_db_thread(context, service_repo_db):
+ project_db, project_metadata_db = get_project(context, service_repo_db)
+ repo_db = objects.repo.Repo.get_by_uuid(context, service_repo_db.repo_uuid)
+ mysettings, myportdb = get_portage_settings(context, project_metadata_db, project_db.name)
+ succes = True
+ filters = { 'repo_uuid' : repo_db.uuid,
+ 'status' : 'waiting',
+ }
+ for package_db in objects.package.PackageList.get_all(context, filters=filters):
+ succes = update_repo_db_multi_thread(context, myportdb, repo_db, package_db)
+ clean_portage_settings(myportdb)
+ return succes
+
+def task(context, service_uuid):
+ filters = {
+ 'service_uuid' : service_uuid,
+ 'status' : 'update_db',
+ }
+ for service_repo_db in objects.service_repo.ServiceRepoList.get_all(context, filters=filters):
+ service_repo_db.status = 'in-progress'
+ service_repo_db.save(context)
+ succes = update_repo_db_thread(context, service_repo_db)
+ if not succes:
+ service_repo_db.status = 'failed'
+ else:
+ service_repo_db.status = 'completed'
+ service_repo_db.save(context)
diff --git a/gosbs/tasks/scheduler/update_git.py b/gosbs/tasks/scheduler/update_git.py
new file mode 100644
index 0000000..ac63966
--- /dev/null
+++ b/gosbs/tasks/scheduler/update_git.py
@@ -0,0 +1,103 @@
+# Copyright 1999-2020 Gentoo Authors
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import sys
+
+from oslo_log import log as logging
+from gosbs import objects
+from gosbs.common.git import check_git_repo, check_git_repo_db
+from gosbs.scheduler.category import check_c_db
+from gosbs.scheduler.package import create_cp_db
+import gosbs.conf
+
+CONF = gosbs.conf.CONF
+LOG = logging.getLogger(__name__)
+
+def update_repo_git_thread(context, service_uuid, repo_db):
+ repo_dict = { 'repo_uuid' : repo_db.uuid,
+ 'repo_name' : repo_db.name,
+ 'repo_url' : repo_db.src_url,
+ 'repo_type' : repo_db.repo_type,
+ 'repo_path' : CONF.repopath + '/' + repo_db.name + '.git',
+ 'history' : True,
+ }
+ repo_db.status = 'in-progress'
+ repo_db.save(context)
+ filters = {
+ 'repo_uuid' : repo_db.uuid,
+ 'service_uuid' : service_uuid,
+ }
+ service_repo_db = objects.service_repo.ServiceRepo.get_by_filters(context, filters=filters)
+ if service_repo_db is None:
+ service_repo_db = objects.service_repo.ServiceRepo()
+ service_repo_db.repo_uuid = repo_db.uuid
+ service_repo_db.service_uuid = service_uuid
+ service_repo_db.auto = repo_db.auto
+ service_repo_db.status = 'in-progress'
+ service_repo_db.create(context)
+ else:
+ service_repo_db.status = 'in-progress'
+ service_repo_db.save(context)
+ cp_list = []
+ if repo_db.repo_type == 'project':
+ succes = check_git_repo(repo_dict)
+ else:
+ succes, cp_list = check_git_repo_db(repo_dict)
+ if not succes:
+ repo_db.status = 'failed'
+ repo_db.save(context)
+ service_repo_db.status = 'failed'
+ service_repo_db.save(context)
+ return False
+ repo_db.status = 'completed'
+ repo_db.save(context)
+ if cp_list is None:
+ service_repo_db.status = 'rebuild_db'
+ service_repo_db.save(context)
+ return True
+ if cp_list == []:
+ service_repo_db.status = 'completed'
+ service_repo_db.save(context)
+ return True
+ for cp in cp_list:
+ category = cp.split('/')[0]
+ package = cp.split('/')[1]
+ succesc = check_c_db(context, category, repo_db)
+ category_db = objects.category.Category.get_by_name(context, category)
+ filters = { 'repo_uuid' : repo_dict['repo_uuid'],
+ 'category_uuid' : category_db.uuid,
+ }
+ package_db = objects.package.Package.get_by_name(context, package, filters=filters)
+ if package_db is None:
+ LOG.info("Adding %s to the database", package)
+ package_db = create_cp_db(context, package, repo_db, category_db)
+ package_db.status = 'waiting'
+ package_db.save(context)
+ service_repo_db.status = 'update_db'
+ service_repo_db.save(context)
+ return True
+
+def task(context, service_uuid):
+ filters = { 'status' : 'waiting',
+ 'deleted' : False,
+ }
+ for repo_db in objects.repo.RepoList.get_all(context, filters=filters):
+ if repo_db is None:
+ return
+ succes = update_repo_git_thread(context, service_uuid, repo_db)
+ return
+ #with futurist.GreenThreadPoolExecutor(max_workers=1) as executor:
+ # Start the load operations and mark each future with its URL
+ # for cp in cp_list:
+ # future = executor.submit(update_cpv_db_thread, context, cp, repo_uuid, project_uuid)
+ # print(future.result())
diff --git a/gosbs/tasks/scheduler/update_repos.py b/gosbs/tasks/scheduler/update_repos.py
new file mode 100644
index 0000000..a28689a
--- /dev/null
+++ b/gosbs/tasks/scheduler/update_repos.py
@@ -0,0 +1,23 @@
+# Copyright 1999-2020 Gentoo Authors
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_log import log as logging
+from gosbs import objects
+import gosbs.conf
+
+CONF = gosbs.conf.CONF
+LOG = logging.getLogger(__name__)
+
+def task(context, service_uuid):
+ LOG.info('Update Repos')
+ objects.repo.RepoList.update_all(context)
diff --git a/gosbs/utils.py b/gosbs/utils.py
new file mode 100644
index 0000000..11ee94d
--- /dev/null
+++ b/gosbs/utils.py
@@ -0,0 +1,1358 @@
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Justin Santa Barbara
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/utils.py
+
+"""Utilities and helper functions."""
+
+import contextlib
+import copy
+import datetime
+import functools
+import hashlib
+import inspect
+import os
+import random
+import re
+import shutil
+import tempfile
+import time
+
+import eventlet
+from keystoneauth1 import exceptions as ks_exc
+from keystoneauth1 import loading as ks_loading
+import netaddr
+from os_service_types import service_types
+from oslo_concurrency import lockutils
+from oslo_concurrency import processutils
+from oslo_context import context as common_context
+from oslo_log import log as logging
+import oslo_messaging as messaging
+from oslo_utils import encodeutils
+from oslo_utils import excutils
+from oslo_utils import importutils
+from oslo_utils import strutils
+from oslo_utils import timeutils
+from oslo_utils import units
+import six
+from six.moves import range
+from six.moves import reload_module
+
+import gosbs.conf
+from gosbs import debugger
+from gosbs import exception
+from gosbs.i18n import _, _LE, _LI, _LW
+#import gosbs.network
+from gosbs import safe_utils
+
+profiler = importutils.try_import('osprofiler.profiler')
+
+CONF = gosbs.conf.CONF
+
+LOG = logging.getLogger(__name__)
+
+_IS_NEUTRON = None
+
+synchronized = lockutils.synchronized_with_prefix('nova-')
+
+SM_IMAGE_PROP_PREFIX = "image_"
+SM_INHERITABLE_KEYS = (
+ 'min_ram', 'min_disk', 'disk_format', 'container_format',
+)
+# Keys which hold large structured data that won't fit in the
+# size constraints of the system_metadata table, so we avoid
+# storing and/or loading them.
+SM_SKIP_KEYS = (
+ # Legacy names
+ 'mappings', 'block_device_mapping',
+ # Modern names
+ 'img_mappings', 'img_block_device_mapping',
+)
+# Image attributes which Cinder stores in volume image metadata
+# as regular properties
+VIM_IMAGE_ATTRIBUTES = (
+ 'image_id', 'image_name', 'size', 'checksum',
+ 'container_format', 'disk_format', 'min_ram', 'min_disk',
+)
+
+_FILE_CACHE = {}
+
+_SERVICE_TYPES = service_types.ServiceTypes()
+
+
+if hasattr(inspect, 'getfullargspec'):
+ getargspec = inspect.getfullargspec
+else:
+ getargspec = inspect.getargspec
+
+
+def get_root_helper():
+ if CONF.workarounds.disable_rootwrap:
+ cmd = 'sudo'
+ else:
+ cmd = 'sudo nova-rootwrap %s' % CONF.rootwrap_config
+ return cmd
+
+
+class RootwrapProcessHelper(object):
+ def trycmd(self, *cmd, **kwargs):
+ kwargs['root_helper'] = get_root_helper()
+ return processutils.trycmd(*cmd, **kwargs)
+
+ def execute(self, *cmd, **kwargs):
+ kwargs['root_helper'] = get_root_helper()
+ return processutils.execute(*cmd, **kwargs)
+
+
+class RootwrapDaemonHelper(RootwrapProcessHelper):
+ _clients = {}
+
+ @synchronized('daemon-client-lock')
+ def _get_client(cls, rootwrap_config):
+ try:
+ return cls._clients[rootwrap_config]
+ except KeyError:
+ from oslo_rootwrap import client
+ new_client = client.Client([
+ "sudo", "nova-rootwrap-daemon", rootwrap_config])
+ cls._clients[rootwrap_config] = new_client
+ return new_client
+
+ def __init__(self, rootwrap_config):
+ self.client = self._get_client(rootwrap_config)
+
+ def trycmd(self, *args, **kwargs):
+ discard_warnings = kwargs.pop('discard_warnings', False)
+ try:
+ out, err = self.execute(*args, **kwargs)
+ failed = False
+ except processutils.ProcessExecutionError as exn:
+ out, err = '', six.text_type(exn)
+ failed = True
+ if not failed and discard_warnings and err:
+ # Handle commands that output to stderr but otherwise succeed
+ err = ''
+ return out, err
+
+ def execute(self, *cmd, **kwargs):
+ # NOTE(dims): This method is to provide compatibility with the
+ # processutils.execute interface. So that calling daemon or direct
+ # rootwrap to honor the same set of flags in kwargs and to ensure
+ # that we don't regress any current behavior.
+ cmd = [str(c) for c in cmd]
+ loglevel = kwargs.pop('loglevel', logging.DEBUG)
+ log_errors = kwargs.pop('log_errors', None)
+ process_input = kwargs.pop('process_input', None)
+ delay_on_retry = kwargs.pop('delay_on_retry', True)
+ attempts = kwargs.pop('attempts', 1)
+ check_exit_code = kwargs.pop('check_exit_code', [0])
+ ignore_exit_code = False
+ if isinstance(check_exit_code, bool):
+ ignore_exit_code = not check_exit_code
+ check_exit_code = [0]
+ elif isinstance(check_exit_code, int):
+ check_exit_code = [check_exit_code]
+
+ sanitized_cmd = strutils.mask_password(' '.join(cmd))
+ LOG.info(_LI('Executing RootwrapDaemonHelper.execute '
+ 'cmd=[%(cmd)r] kwargs=[%(kwargs)r]'),
+ {'cmd': sanitized_cmd, 'kwargs': kwargs})
+
+ while attempts > 0:
+ attempts -= 1
+ try:
+ start_time = time.time()
+ LOG.log(loglevel, _('Running cmd (subprocess): %s'),
+ sanitized_cmd)
+
+ (returncode, out, err) = self.client.execute(
+ cmd, process_input)
+
+ end_time = time.time() - start_time
+ LOG.log(loglevel,
+ 'CMD "%(sanitized_cmd)s" returned: %(return_code)s '
+ 'in %(end_time)0.3fs',
+ {'sanitized_cmd': sanitized_cmd,
+ 'return_code': returncode,
+ 'end_time': end_time})
+
+ if not ignore_exit_code and returncode not in check_exit_code:
+ out = strutils.mask_password(out)
+ err = strutils.mask_password(err)
+ raise processutils.ProcessExecutionError(
+ exit_code=returncode,
+ stdout=out,
+ stderr=err,
+ cmd=sanitized_cmd)
+ return (out, err)
+
+ except processutils.ProcessExecutionError as err:
+ # if we want to always log the errors or if this is
+ # the final attempt that failed and we want to log that.
+ if log_errors == processutils.LOG_ALL_ERRORS or (
+ log_errors == processutils.LOG_FINAL_ERROR and
+ not attempts):
+ format = _('%(desc)r\ncommand: %(cmd)r\n'
+ 'exit code: %(code)r\nstdout: %(stdout)r\n'
+ 'stderr: %(stderr)r')
+ LOG.log(loglevel, format, {"desc": err.description,
+ "cmd": err.cmd,
+ "code": err.exit_code,
+ "stdout": err.stdout,
+ "stderr": err.stderr})
+ if not attempts:
+ LOG.log(loglevel, _('%r failed. Not Retrying.'),
+ sanitized_cmd)
+ raise
+ else:
+ LOG.log(loglevel, _('%r failed. Retrying.'),
+ sanitized_cmd)
+ if delay_on_retry:
+ time.sleep(random.randint(20, 200) / 100.0)
+
+
+def execute(*cmd, **kwargs):
+ """Convenience wrapper around oslo's execute() method."""
+ if 'run_as_root' in kwargs and kwargs.get('run_as_root'):
+ if CONF.use_rootwrap_daemon:
+ return RootwrapDaemonHelper(CONF.rootwrap_config).execute(
+ *cmd, **kwargs)
+ else:
+ return RootwrapProcessHelper().execute(*cmd, **kwargs)
+ return processutils.execute(*cmd, **kwargs)
+
+
+def ssh_execute(dest, *cmd, **kwargs):
+ """Convenience wrapper to execute ssh command."""
+ ssh_cmd = ['ssh', '-o', 'BatchMode=yes']
+ ssh_cmd.append(dest)
+ ssh_cmd.extend(cmd)
+ return execute(*ssh_cmd, **kwargs)
+
+
+def generate_uid(topic, size=8):
+ characters = '01234567890abcdefghijklmnopqrstuvwxyz'
+ choices = [random.choice(characters) for _x in range(size)]
+ return '%s-%s' % (topic, ''.join(choices))
+
+
+# Default symbols to use for passwords. Avoids visually confusing characters.
+# ~6 bits per symbol
+DEFAULT_PASSWORD_SYMBOLS = ('23456789', # Removed: 0,1
+ 'ABCDEFGHJKLMNPQRSTUVWXYZ', # Removed: I, O
+ 'abcdefghijkmnopqrstuvwxyz') # Removed: l
+
+
+def last_completed_audit_period(unit=None, before=None):
+ """This method gives you the most recently *completed* audit period.
+
+ arguments:
+ units: string, one of 'hour', 'day', 'month', 'year'
+ Periods normally begin at the beginning (UTC) of the
+ period unit (So a 'day' period begins at midnight UTC,
+ a 'month' unit on the 1st, a 'year' on Jan, 1)
+ unit string may be appended with an optional offset
+ like so: 'day@18' This will begin the period at 18:00
+ UTC. 'month@15' starts a monthly period on the 15th,
+ and year@3 begins a yearly one on March 1st.
+ before: Give the audit period most recently completed before
+ <timestamp>. Defaults to now.
+
+
+ returns: 2 tuple of datetimes (begin, end)
+ The begin timestamp of this audit period is the same as the
+ end of the previous.
+ """
+ if not unit:
+ unit = CONF.instance_usage_audit_period
+
+ offset = 0
+ if '@' in unit:
+ unit, offset = unit.split("@", 1)
+ offset = int(offset)
+
+ if before is not None:
+ rightnow = before
+ else:
+ rightnow = timeutils.utcnow()
+ if unit not in ('month', 'day', 'year', 'hour'):
+ raise ValueError(_('Time period must be hour, day, month or year'))
+ if unit == 'month':
+ if offset == 0:
+ offset = 1
+ end = datetime.datetime(day=offset,
+ month=rightnow.month,
+ year=rightnow.year)
+ if end >= rightnow:
+ year = rightnow.year
+ if 1 >= rightnow.month:
+ year -= 1
+ month = 12 + (rightnow.month - 1)
+ else:
+ month = rightnow.month - 1
+ end = datetime.datetime(day=offset,
+ month=month,
+ year=year)
+ year = end.year
+ if 1 >= end.month:
+ year -= 1
+ month = 12 + (end.month - 1)
+ else:
+ month = end.month - 1
+ begin = datetime.datetime(day=offset, month=month, year=year)
+
+ elif unit == 'year':
+ if offset == 0:
+ offset = 1
+ end = datetime.datetime(day=1, month=offset, year=rightnow.year)
+ if end >= rightnow:
+ end = datetime.datetime(day=1,
+ month=offset,
+ year=rightnow.year - 1)
+ begin = datetime.datetime(day=1,
+ month=offset,
+ year=rightnow.year - 2)
+ else:
+ begin = datetime.datetime(day=1,
+ month=offset,
+ year=rightnow.year - 1)
+
+ elif unit == 'day':
+ end = datetime.datetime(hour=offset,
+ day=rightnow.day,
+ month=rightnow.month,
+ year=rightnow.year)
+ if end >= rightnow:
+ end = end - datetime.timedelta(days=1)
+ begin = end - datetime.timedelta(days=1)
+
+ elif unit == 'hour':
+ end = rightnow.replace(minute=offset, second=0, microsecond=0)
+ if end >= rightnow:
+ end = end - datetime.timedelta(hours=1)
+ begin = end - datetime.timedelta(hours=1)
+
+ return (begin, end)
+
+
+def generate_password(length=None, symbolgroups=DEFAULT_PASSWORD_SYMBOLS):
+ """Generate a random password from the supplied symbol groups.
+
+ At least one symbol from each group will be included. Unpredictable
+ results if length is less than the number of symbol groups.
+
+ Believed to be reasonably secure (with a reasonable password length!)
+
+ """
+ if length is None:
+ length = CONF.password_length
+
+ r = random.SystemRandom()
+
+ # NOTE(jerdfelt): Some password policies require at least one character
+ # from each group of symbols, so start off with one random character
+ # from each symbol group
+ password = [r.choice(s) for s in symbolgroups]
+ # If length < len(symbolgroups), the leading characters will only
+ # be from the first length groups. Try our best to not be predictable
+ # by shuffling and then truncating.
+ r.shuffle(password)
+ password = password[:length]
+ length -= len(password)
+
+ # then fill with random characters from all symbol groups
+ symbols = ''.join(symbolgroups)
+ password.extend([r.choice(symbols) for _i in range(length)])
+
+ # finally shuffle to ensure first x characters aren't from a
+ # predictable group
+ r.shuffle(password)
+
+ return ''.join(password)
+
+
+# TODO(sfinucan): Replace this with the equivalent from oslo.utils
+def utf8(value):
+ """Try to turn a string into utf-8 if possible.
+
+ The original code was copied from the utf8 function in
+ http://github.com/facebook/tornado/blob/master/tornado/escape.py
+
+ """
+ if value is None or isinstance(value, six.binary_type):
+ return value
+
+ if not isinstance(value, six.text_type):
+ value = six.text_type(value)
+
+ return value.encode('utf-8')
+
+
+def parse_server_string(server_str):
+ """Parses the given server_string and returns a tuple of host and port.
+ If it's not a combination of host part and port, the port element
+ is an empty string. If the input is invalid expression, return a tuple of
+ two empty strings.
+ """
+ try:
+ # First of all, exclude pure IPv6 address (w/o port).
+ if netaddr.valid_ipv6(server_str):
+ return (server_str, '')
+
+ # Next, check if this is IPv6 address with a port number combination.
+ if server_str.find("]:") != -1:
+ (address, port) = server_str.replace('[', '', 1).split(']:')
+ return (address, port)
+
+ # Third, check if this is a combination of an address and a port
+ if server_str.find(':') == -1:
+ return (server_str, '')
+
+ # This must be a combination of an address and a port
+ (address, port) = server_str.split(':')
+ return (address, port)
+
+ except (ValueError, netaddr.AddrFormatError):
+ LOG.error(_LE('Invalid server_string: %s'), server_str)
+ return ('', '')
+
+
+def get_shortened_ipv6(address):
+ addr = netaddr.IPAddress(address, version=6)
+ return str(addr.ipv6())
+
+
+def get_shortened_ipv6_cidr(address):
+ net = netaddr.IPNetwork(address, version=6)
+ return str(net.cidr)
+
+
+def safe_ip_format(ip):
+ """Transform ip string to "safe" format.
+
+ Will return ipv4 addresses unchanged, but will nest ipv6 addresses
+ inside square brackets.
+ """
+ try:
+ if netaddr.IPAddress(ip).version == 6:
+ return '[%s]' % ip
+ except (TypeError, netaddr.AddrFormatError): # hostname
+ pass
+ # it's IPv4 or hostname
+ return ip
+
+
+def format_remote_path(host, path):
+ """Returns remote path in format acceptable for scp/rsync.
+
+ If host is IPv6 address literal, return '[host]:path', otherwise
+ 'host:path' is returned.
+
+ If host is None, only path is returned.
+ """
+ if host is None:
+ return path
+
+ return "%s:%s" % (safe_ip_format(host), path)
+
+
+def make_dev_path(dev, partition=None, base='/dev'):
+ """Return a path to a particular device.
+
+ >>> make_dev_path('xvdc')
+ /dev/xvdc
+
+ >>> make_dev_path('xvdc', 1)
+ /dev/xvdc1
+ """
+ path = os.path.join(base, dev)
+ if partition:
+ path += str(partition)
+ return path
+
+
+def sanitize_hostname(hostname, default_name=None):
+ """Return a hostname which conforms to RFC-952 and RFC-1123 specs except
+ the length of hostname.
+
+ Window, Linux, and Dnsmasq has different limitation:
+
+ Windows: 255 (net_bios limits to 15, but window will truncate it)
+ Linux: 64
+ Dnsmasq: 63
+
+ Due to nova-network will leverage dnsmasq to set hostname, so we chose
+ 63.
+
+ """
+
+ def truncate_hostname(name):
+ if len(name) > 63:
+ LOG.warning(_LW("Hostname %(hostname)s is longer than 63, "
+ "truncate it to %(truncated_name)s"),
+ {'hostname': name, 'truncated_name': name[:63]})
+ return name[:63]
+
+ if isinstance(hostname, six.text_type):
+ # Remove characters outside the Unicode range U+0000-U+00FF
+ hostname = hostname.encode('latin-1', 'ignore')
+ if six.PY3:
+ hostname = hostname.decode('latin-1')
+
+ hostname = truncate_hostname(hostname)
+ hostname = re.sub('[ _]', '-', hostname)
+ hostname = re.sub('[^\w.-]+', '', hostname)
+ hostname = hostname.lower()
+ hostname = hostname.strip('.-')
+ # NOTE(eliqiao): set hostname to default_display_name to avoid
+ # empty hostname
+ if hostname == "" and default_name is not None:
+ return truncate_hostname(default_name)
+ return hostname
+
+
+@contextlib.contextmanager
+def temporary_mutation(obj, **kwargs):
+ """Temporarily set the attr on a particular object to a given value then
+ revert when finished.
+
+ One use of this is to temporarily set the read_deleted flag on a context
+ object:
+
+ with temporary_mutation(context, read_deleted="yes"):
+ do_something_that_needed_deleted_objects()
+ """
+ def is_dict_like(thing):
+ return hasattr(thing, 'has_key') or isinstance(thing, dict)
+
+ def get(thing, attr, default):
+ if is_dict_like(thing):
+ return thing.get(attr, default)
+ else:
+ return getattr(thing, attr, default)
+
+ def set_value(thing, attr, val):
+ if is_dict_like(thing):
+ thing[attr] = val
+ else:
+ setattr(thing, attr, val)
+
+ def delete(thing, attr):
+ if is_dict_like(thing):
+ del thing[attr]
+ else:
+ delattr(thing, attr)
+
+ NOT_PRESENT = object()
+
+ old_values = {}
+ for attr, new_value in kwargs.items():
+ old_values[attr] = get(obj, attr, NOT_PRESENT)
+ set_value(obj, attr, new_value)
+
+ try:
+ yield
+ finally:
+ for attr, old_value in old_values.items():
+ if old_value is NOT_PRESENT:
+ delete(obj, attr)
+ else:
+ set_value(obj, attr, old_value)
+
+
+def generate_mac_address():
+ """Generate an Ethernet MAC address."""
+ # NOTE(vish): We would prefer to use 0xfe here to ensure that linux
+ # bridge mac addresses don't change, but it appears to
+ # conflict with libvirt, so we use the next highest octet
+ # that has the unicast and locally administered bits set
+ # properly: 0xfa.
+ # Discussion: https://bugs.launchpad.net/nova/+bug/921838
+ mac = [0xfa, 0x16, 0x3e,
+ random.randint(0x00, 0xff),
+ random.randint(0x00, 0xff),
+ random.randint(0x00, 0xff)]
+ return ':'.join(map(lambda x: "%02x" % x, mac))
+
+
+# NOTE(mikal): I really wanted this code to go away, but I can't find a way
+# to implement what the callers of this method want with privsep. Basically,
+# if we could hand off either a file descriptor or a file like object then
+# we could make this go away.
+@contextlib.contextmanager
+def temporary_chown(path, owner_uid=None):
+ """Temporarily chown a path.
+
+ :param owner_uid: UID of temporary owner (defaults to current user)
+ """
+ if owner_uid is None:
+ owner_uid = os.getuid()
+
+ orig_uid = os.stat(path).st_uid
+
+ if orig_uid != owner_uid:
+ nova.privsep.path.chown(path, uid=owner_uid)
+ try:
+ yield
+ finally:
+ if orig_uid != owner_uid:
+ nova.privsep.path.chown(path, uid=orig_uid)
+
+
+@contextlib.contextmanager
+def tempdir(**kwargs):
+ argdict = kwargs.copy()
+ if 'dir' not in argdict:
+ argdict['dir'] = CONF.tempdir
+ tmpdir = tempfile.mkdtemp(**argdict)
+ try:
+ yield tmpdir
+ finally:
+ try:
+ shutil.rmtree(tmpdir)
+ except OSError as e:
+ LOG.error(_LE('Could not remove tmpdir: %s'), e)
+
+
+class UndoManager(object):
+ """Provides a mechanism to facilitate rolling back a series of actions
+ when an exception is raised.
+ """
+ def __init__(self):
+ self.undo_stack = []
+
+ def undo_with(self, undo_func):
+ self.undo_stack.append(undo_func)
+
+ def _rollback(self):
+ for undo_func in reversed(self.undo_stack):
+ undo_func()
+
+ def rollback_and_reraise(self, msg=None, **kwargs):
+ """Rollback a series of actions then re-raise the exception.
+
+ .. note:: (sirp) This should only be called within an
+ exception handler.
+ """
+ with excutils.save_and_reraise_exception():
+ if msg:
+ LOG.exception(msg, **kwargs)
+
+ self._rollback()
+
+
+def metadata_to_dict(metadata, include_deleted=False):
+ result = {}
+ for item in metadata:
+ if not include_deleted and item.get('deleted'):
+ continue
+ result[item['key']] = item['value']
+ return result
+
+
+def dict_to_metadata(metadata):
+ result = []
+ for key, value in metadata.items():
+ result.append(dict(key=key, value=value))
+ return result
+
+
+def instance_meta(instance):
+ if isinstance(instance['metadata'], dict):
+ return instance['metadata']
+ else:
+ return metadata_to_dict(instance['metadata'])
+
+
+def instance_sys_meta(instance):
+ if not instance.get('system_metadata'):
+ return {}
+ if isinstance(instance['system_metadata'], dict):
+ return instance['system_metadata']
+ else:
+ return metadata_to_dict(instance['system_metadata'],
+ include_deleted=True)
+
+
+def expects_func_args(*args):
+ def _decorator_checker(dec):
+ @functools.wraps(dec)
+ def _decorator(f):
+ base_f = safe_utils.get_wrapped_function(f)
+ argspec = getargspec(base_f)
+ if argspec[1] or argspec[2] or set(args) <= set(argspec[0]):
+ # NOTE (ndipanov): We can't really tell if correct stuff will
+ # be passed if it's a function with *args or **kwargs so
+ # we still carry on and hope for the best
+ return dec(f)
+ else:
+ raise TypeError("Decorated function %(f_name)s does not "
+ "have the arguments expected by the "
+ "decorator %(d_name)s" %
+ {'f_name': base_f.__name__,
+ 'd_name': dec.__name__})
+ return _decorator
+ return _decorator_checker
+
+
+class ExceptionHelper(object):
+ """Class to wrap another and translate the ClientExceptions raised by its
+ function calls to the actual ones.
+ """
+
+ def __init__(self, target):
+ self._target = target
+
+ def __getattr__(self, name):
+ func = getattr(self._target, name)
+
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ try:
+ return func(*args, **kwargs)
+ except messaging.ExpectedException as e:
+ six.reraise(*e.exc_info)
+ return wrapper
+
+
+def check_string_length(value, name=None, min_length=0, max_length=None):
+ """Check the length of specified string
+ :param value: the value of the string
+ :param name: the name of the string
+ :param min_length: the min_length of the string
+ :param max_length: the max_length of the string
+ """
+ try:
+ strutils.check_string_length(value, name=name,
+ min_length=min_length,
+ max_length=max_length)
+ except (ValueError, TypeError) as exc:
+ raise exception.InvalidInput(message=exc.args[0])
+
+
+def validate_integer(value, name, min_value=None, max_value=None):
+ """Make sure that value is a valid integer, potentially within range.
+
+ :param value: value of the integer
+ :param name: name of the integer
+ :param min_value: min_value of the integer
+ :param max_value: max_value of the integer
+ :returns: integer
+ :raise: InvalidInput If value is not a valid integer
+ """
+ try:
+ return strutils.validate_integer(value, name, min_value, max_value)
+ except ValueError as e:
+ raise exception.InvalidInput(reason=six.text_type(e))
+
+
+def _serialize_profile_info():
+ if not profiler:
+ return None
+ prof = profiler.get()
+ trace_info = None
+ if prof:
+ # FIXME(DinaBelova): we'll add profiler.get_info() method
+ # to extract this info -> we'll need to update these lines
+ trace_info = {
+ "hmac_key": prof.hmac_key,
+ "base_id": prof.get_base_id(),
+ "parent_id": prof.get_id()
+ }
+ return trace_info
+
+
+def spawn(func, *args, **kwargs):
+ """Passthrough method for eventlet.spawn.
+
+ This utility exists so that it can be stubbed for testing without
+ interfering with the service spawns.
+
+ It will also grab the context from the threadlocal store and add it to
+ the store on the new thread. This allows for continuity in logging the
+ context when using this method to spawn a new thread.
+ """
+ _context = common_context.get_current()
+ profiler_info = _serialize_profile_info()
+
+ @functools.wraps(func)
+ def context_wrapper(*args, **kwargs):
+ # NOTE: If update_store is not called after spawn it won't be
+ # available for the logger to pull from threadlocal storage.
+ if _context is not None:
+ _context.update_store()
+ if profiler_info and profiler:
+ profiler.init(**profiler_info)
+ return func(*args, **kwargs)
+
+ return eventlet.spawn(context_wrapper, *args, **kwargs)
+
+
+def spawn_n(func, *args, **kwargs):
+ """Passthrough method for eventlet.spawn_n.
+
+ This utility exists so that it can be stubbed for testing without
+ interfering with the service spawns.
+
+ It will also grab the context from the threadlocal store and add it to
+ the store on the new thread. This allows for continuity in logging the
+ context when using this method to spawn a new thread.
+ """
+ _context = common_context.get_current()
+ profiler_info = _serialize_profile_info()
+
+ @functools.wraps(func)
+ def context_wrapper(*args, **kwargs):
+ # NOTE: If update_store is not called after spawn_n it won't be
+ # available for the logger to pull from threadlocal storage.
+ if _context is not None:
+ _context.update_store()
+ if profiler_info and profiler:
+ profiler.init(**profiler_info)
+ func(*args, **kwargs)
+
+ eventlet.spawn_n(context_wrapper, *args, **kwargs)
+
+
+def is_none_string(val):
+ """Check if a string represents a None value.
+ """
+ if not isinstance(val, six.string_types):
+ return False
+
+ return val.lower() == 'none'
+
+
+def is_neutron():
+ global _IS_NEUTRON
+
+ if _IS_NEUTRON is not None:
+ return _IS_NEUTRON
+
+ _IS_NEUTRON = nova.network.is_neutron()
+ return _IS_NEUTRON
+
+
+def is_auto_disk_config_disabled(auto_disk_config_raw):
+ auto_disk_config_disabled = False
+ if auto_disk_config_raw is not None:
+ adc_lowered = auto_disk_config_raw.strip().lower()
+ if adc_lowered == "disabled":
+ auto_disk_config_disabled = True
+ return auto_disk_config_disabled
+
+
+def get_auto_disk_config_from_instance(instance=None, sys_meta=None):
+ if sys_meta is None:
+ sys_meta = instance_sys_meta(instance)
+ return sys_meta.get("image_auto_disk_config")
+
+
+def get_auto_disk_config_from_image_props(image_properties):
+ return image_properties.get("auto_disk_config")
+
+
+def get_system_metadata_from_image(image_meta, flavor=None):
+ system_meta = {}
+ prefix_format = SM_IMAGE_PROP_PREFIX + '%s'
+
+ for key, value in image_meta.get('properties', {}).items():
+ if key in SM_SKIP_KEYS:
+ continue
+
+ new_value = safe_truncate(six.text_type(value), 255)
+ system_meta[prefix_format % key] = new_value
+
+ for key in SM_INHERITABLE_KEYS:
+ value = image_meta.get(key)
+
+ if key == 'min_disk' and flavor:
+ if image_meta.get('disk_format') == 'vhd':
+ value = flavor['root_gb']
+ else:
+ value = max(value or 0, flavor['root_gb'])
+
+ if value is None:
+ continue
+
+ system_meta[prefix_format % key] = value
+
+ return system_meta
+
+
+def get_image_from_system_metadata(system_meta):
+ image_meta = {}
+ properties = {}
+
+ if not isinstance(system_meta, dict):
+ system_meta = metadata_to_dict(system_meta, include_deleted=True)
+
+ for key, value in system_meta.items():
+ if value is None:
+ continue
+
+ # NOTE(xqueralt): Not sure this has to inherit all the properties or
+ # just the ones we need. Leaving it for now to keep the old behaviour.
+ if key.startswith(SM_IMAGE_PROP_PREFIX):
+ key = key[len(SM_IMAGE_PROP_PREFIX):]
+
+ if key in SM_SKIP_KEYS:
+ continue
+
+ if key in SM_INHERITABLE_KEYS:
+ image_meta[key] = value
+ else:
+ properties[key] = value
+
+ image_meta['properties'] = properties
+
+ return image_meta
+
+
+def get_image_metadata_from_volume(volume):
+ properties = copy.copy(volume.get('volume_image_metadata', {}))
+ image_meta = {'properties': properties}
+ # Volume size is no longer related to the original image size,
+ # so we take it from the volume directly. Cinder creates
+ # volumes in Gb increments, and stores size in Gb, whereas
+ # glance reports size in bytes. As we're returning glance
+ # metadata here, we need to convert it.
+ image_meta['size'] = volume.get('size', 0) * units.Gi
+ # NOTE(yjiang5): restore the basic attributes
+ # NOTE(mdbooth): These values come from volume_glance_metadata
+ # in cinder. This is a simple key/value table, and all values
+ # are strings. We need to convert them to ints to avoid
+ # unexpected type errors.
+ for attr in VIM_IMAGE_ATTRIBUTES:
+ val = properties.pop(attr, None)
+ if attr in ('min_ram', 'min_disk'):
+ image_meta[attr] = int(val or 0)
+ # NOTE(mriedem): Set the status to 'active' as a really old hack
+ # from when this method was in the compute API class and is
+ # needed for _check_requested_image which makes sure the image
+ # is 'active'. For volume-backed servers, if the volume is not
+ # available because the image backing the volume is not active,
+ # then the compute API trying to reserve the volume should fail.
+ image_meta['status'] = 'active'
+ return image_meta
+
+
+def get_hash_str(base_str):
+ """Returns string that represents MD5 hash of base_str (in hex format).
+
+ If base_str is a Unicode string, encode it to UTF-8.
+ """
+ if isinstance(base_str, six.text_type):
+ base_str = base_str.encode('utf-8')
+ return hashlib.md5(base_str).hexdigest()
+
+
+def get_sha256_str(base_str):
+ """Returns string that represents sha256 hash of base_str (in hex format).
+
+ sha1 and md5 are known to be breakable, so sha256 is a better option
+ when the hash is being used for security purposes. If hashing passwords
+ or anything else that needs to be retained for a long period a salted
+ hash is better.
+ """
+ if isinstance(base_str, six.text_type):
+ base_str = base_str.encode('utf-8')
+ return hashlib.sha256(base_str).hexdigest()
+
+
+def get_obj_repr_unicode(obj):
+ """Returns a string representation of an object converted to unicode.
+
+ In the case of python 3, this just returns the repr() of the object,
+ else it converts the repr() to unicode.
+ """
+ obj_repr = repr(obj)
+ if not six.PY3:
+ obj_repr = six.text_type(obj_repr, 'utf-8')
+ return obj_repr
+
+
+def filter_and_format_resource_metadata(resource_type, resource_list,
+ search_filts, metadata_type=None):
+ """Get all metadata for a list of resources after filtering.
+
+ Search_filts is a list of dictionaries, where the values in the dictionary
+ can be string or regex string, or a list of strings/regex strings.
+
+ Let's call a dict a 'filter block' and an item in the dict
+ a 'filter'. A tag is returned if it matches ALL the filters in
+ a filter block. If more than one values are specified for a
+ filter, a tag is returned if it matches ATLEAST ONE value of the filter. If
+ more than one filter blocks are specified, the tag should match ALL the
+ filter blocks.
+
+ For example:
+
+ search_filts = [{'key': ['key1', 'key2'], 'value': 'val1'},
+ {'value': 'val2'}]
+
+ The filter translates to 'match any tag for which':
+ ((key=key1 AND value=val1) OR (key=key2 AND value=val1)) AND
+ (value=val2)
+
+ This example filter will never match a tag.
+
+ :param resource_type: The resource type as a string, e.g. 'instance'
+ :param resource_list: List of resource objects
+ :param search_filts: Filters to filter metadata to be returned. Can be
+ dict (e.g. {'key': 'env', 'value': 'prod'}, or a list of dicts
+ (e.g. [{'key': 'env'}, {'value': 'beta'}]. Note that the values
+ of the dict can be regular expressions.
+ :param metadata_type: Provided to search for a specific metadata type
+ (e.g. 'system_metadata')
+
+ :returns: List of dicts where each dict is of the form {'key':
+ 'somekey', 'value': 'somevalue', 'instance_id':
+ 'some-instance-uuid-aaa'} if resource_type is 'instance'.
+ """
+
+ if isinstance(search_filts, dict):
+ search_filts = [search_filts]
+
+ def _get_id(resource):
+ if resource_type == 'instance':
+ return resource.get('uuid')
+
+ def _match_any(pattern_list, string):
+ if isinstance(pattern_list, six.string_types):
+ pattern_list = [pattern_list]
+ return any([re.match(pattern, string)
+ for pattern in pattern_list])
+
+ def _filter_metadata(resource, search_filt, input_metadata):
+ ids = search_filt.get('resource_id', [])
+ keys_filter = search_filt.get('key', [])
+ values_filter = search_filt.get('value', [])
+ output_metadata = {}
+
+ if ids and _get_id(resource) not in ids:
+ return {}
+
+ for k, v in input_metadata.items():
+ # Both keys and value defined -- AND
+ if (keys_filter and values_filter and
+ not _match_any(keys_filter, k) and
+ not _match_any(values_filter, v)):
+ continue
+ # Only keys or value is defined
+ elif ((keys_filter and not _match_any(keys_filter, k)) or
+ (values_filter and not _match_any(values_filter, v))):
+ continue
+
+ output_metadata[k] = v
+ return output_metadata
+
+ formatted_metadata_list = []
+ for res in resource_list:
+
+ if resource_type == 'instance':
+ # NOTE(rushiagr): metadata_type should be 'metadata' or
+ # 'system_metadata' if resource_type is instance. Defaulting to
+ # 'metadata' if not specified.
+ if metadata_type is None:
+ metadata_type = 'metadata'
+ metadata = res.get(metadata_type, {})
+
+ for filt in search_filts:
+ # By chaining the input to the output, the filters are
+ # ANDed together
+ metadata = _filter_metadata(res, filt, metadata)
+
+ for (k, v) in metadata.items():
+ formatted_metadata_list.append({'key': k, 'value': v,
+ '%s_id' % resource_type: _get_id(res)})
+
+ return formatted_metadata_list
+
+
+def safe_truncate(value, length):
+ """Safely truncates unicode strings such that their encoded length is
+ no greater than the length provided.
+ """
+ b_value = encodeutils.safe_encode(value)[:length]
+
+ # NOTE(chaochin) UTF-8 character byte size varies from 1 to 6. If
+ # truncating a long byte string to 255, the last character may be
+ # cut in the middle, so that UnicodeDecodeError will occur when
+ # converting it back to unicode.
+ decode_ok = False
+ while not decode_ok:
+ try:
+ u_value = encodeutils.safe_decode(b_value)
+ decode_ok = True
+ except UnicodeDecodeError:
+ b_value = b_value[:-1]
+ return u_value
+
+
+def read_cached_file(filename, force_reload=False):
+ """Read from a file if it has been modified.
+
+ :param force_reload: Whether to reload the file.
+ :returns: A tuple with a boolean specifying if the data is fresh
+ or not.
+ """
+ global _FILE_CACHE
+
+ if force_reload:
+ delete_cached_file(filename)
+
+ reloaded = False
+ mtime = os.path.getmtime(filename)
+ cache_info = _FILE_CACHE.setdefault(filename, {})
+
+ if not cache_info or mtime > cache_info.get('mtime', 0):
+ LOG.debug("Reloading cached file %s", filename)
+ with open(filename) as fap:
+ cache_info['data'] = fap.read()
+ cache_info['mtime'] = mtime
+ reloaded = True
+ return (reloaded, cache_info['data'])
+
+
+def delete_cached_file(filename):
+ """Delete cached file if present.
+
+ :param filename: filename to delete
+ """
+ global _FILE_CACHE
+
+ if filename in _FILE_CACHE:
+ del _FILE_CACHE[filename]
+
+
+def isotime(at=None):
+ """Current time as ISO string,
+ as timeutils.isotime() is deprecated
+
+ :returns: Current time in ISO format
+ """
+ if not at:
+ at = timeutils.utcnow()
+ date_string = at.strftime("%Y-%m-%dT%H:%M:%S")
+ tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC'
+ date_string += ('Z' if tz in ['UTC', 'UTC+00:00'] else tz)
+ return date_string
+
+
+def strtime(at):
+ return at.strftime("%Y-%m-%dT%H:%M:%S.%f")
+
+
+def get_ksa_adapter(service_type, ksa_auth=None, ksa_session=None,
+ min_version=None, max_version=None):
+ """Construct a keystoneauth1 Adapter for a given service type.
+
+ We expect to find a conf group whose name corresponds to the service_type's
+ project according to the service-types-authority. That conf group must
+ provide at least ksa adapter options. Depending how the result is to be
+ used, ksa auth and/or session options may also be required, or the relevant
+ parameter supplied.
+
+ A raise_exc=False adapter is returned, meaning responses >=400 return the
+ Response object rather than raising an exception. This behavior can be
+ overridden on a per-request basis by setting raise_exc=True.
+
+ :param service_type: String name of the service type for which the Adapter
+ is to be constructed.
+ :param ksa_auth: A keystoneauth1 auth plugin. If not specified, we attempt
+ to find one in ksa_session. Failing that, we attempt to
+ load one from the conf.
+ :param ksa_session: A keystoneauth1 Session. If not specified, we attempt
+ to load one from the conf.
+ :param min_version: The minimum major version of the adapter's endpoint,
+ intended to be used as the lower bound of a range with
+ max_version.
+ If min_version is given with no max_version it is as
+ if max version is 'latest'.
+ :param max_version: The maximum major version of the adapter's endpoint,
+ intended to be used as the upper bound of a range with
+ min_version.
+ :return: A keystoneauth1 Adapter object for the specified service_type.
+ :raise: ConfGroupForServiceTypeNotFound If no conf group name could be
+ found for the specified service_type.
+ """
+ # Get the conf group corresponding to the service type.
+ confgrp = _SERVICE_TYPES.get_project_name(service_type)
+ if not confgrp or not hasattr(CONF, confgrp):
+ # Try the service type as the conf group. This is necessary for e.g.
+ # placement, while it's still part of the nova project.
+ # Note that this might become the first thing we try if/as we move to
+ # using service types for conf group names in general.
+ confgrp = service_type
+ if not confgrp or not hasattr(CONF, confgrp):
+ raise exception.ConfGroupForServiceTypeNotFound(stype=service_type)
+
+ # Ensure we have an auth.
+ # NOTE(efried): This could be None, and that could be okay - e.g. if the
+ # result is being used for get_endpoint() and the conf only contains
+ # endpoint_override.
+ if not ksa_auth:
+ if ksa_session and ksa_session.auth:
+ ksa_auth = ksa_session.auth
+ else:
+ ksa_auth = ks_loading.load_auth_from_conf_options(CONF, confgrp)
+
+ if not ksa_session:
+ ksa_session = ks_loading.load_session_from_conf_options(
+ CONF, confgrp, auth=ksa_auth)
+
+ return ks_loading.load_adapter_from_conf_options(
+ CONF, confgrp, session=ksa_session, auth=ksa_auth,
+ min_version=min_version, max_version=max_version, raise_exc=False)
+
+
+def get_endpoint(ksa_adapter):
+ """Get the endpoint URL represented by a keystoneauth1 Adapter.
+
+ This method is equivalent to what
+
+ ksa_adapter.get_endpoint()
+
+ should do, if it weren't for a panoply of bugs.
+
+ :param ksa_adapter: keystoneauth1.adapter.Adapter, appropriately set up
+ with an endpoint_override; or service_type, interface
+ (list) and auth/service_catalog.
+ :return: String endpoint URL.
+ :raise EndpointNotFound: If endpoint discovery fails.
+ """
+ # TODO(efried): This will be unnecessary once bug #1707993 is fixed.
+ # (At least for the non-image case, until 1707995 is fixed.)
+ if ksa_adapter.endpoint_override:
+ return ksa_adapter.endpoint_override
+ # TODO(efried): Remove this once bug #1707995 is fixed.
+ if ksa_adapter.service_type == 'image':
+ try:
+ return ksa_adapter.get_endpoint_data().catalog_url
+ except AttributeError:
+ # ksa_adapter.auth is a _ContextAuthPlugin, which doesn't have
+ # get_endpoint_data. Fall through to using get_endpoint().
+ pass
+ # TODO(efried): The remainder of this method reduces to
+ # TODO(efried): return ksa_adapter.get_endpoint()
+ # TODO(efried): once bug #1709118 is fixed.
+ # NOTE(efried): Id9bd19cca68206fc64d23b0eaa95aa3e5b01b676 may also do the
+ # trick, once it's in a ksa release.
+ # The EndpointNotFound exception happens when _ContextAuthPlugin is in play
+ # because its get_endpoint() method isn't yet set up to handle interface as
+ # a list. (It could also happen with a real auth if the endpoint isn't
+ # there; but that's covered below.)
+ try:
+ return ksa_adapter.get_endpoint()
+ except ks_exc.EndpointNotFound:
+ pass
+
+ interfaces = list(ksa_adapter.interface)
+ for interface in interfaces:
+ ksa_adapter.interface = interface
+ try:
+ return ksa_adapter.get_endpoint()
+ except ks_exc.EndpointNotFound:
+ pass
+ raise ks_exc.EndpointNotFound(
+ "Could not find requested endpoint for any of the following "
+ "interfaces: %s" % interfaces)
+
+
+def generate_hostid(host, project_id):
+ """Generate an obfuscated host id representing the host.
+
+ This is a hashed value so will not actually look like a hostname, and is
+ hashed with data from the project_id.
+
+ :param host: The name of the compute host.
+ :param project_id: The UUID of the project.
+ :return: An obfuscated hashed host id string, return "" if host is empty
+ """
+ if host:
+ data = (project_id + host).encode('utf-8')
+ sha_hash = hashlib.sha224(data)
+ return sha_hash.hexdigest()
+ return ""
+
+
+def monkey_patch():
+ if debugger.enabled():
+ # turn off thread patching to enable the remote debugger
+ eventlet.monkey_patch(os=False, thread=False)
+ else:
+ eventlet.monkey_patch(os=False)
+
+ # NOTE(rgerganov): oslo.context is storing a global thread-local variable
+ # which keeps the request context for the current thread. If oslo.context
+ # is imported before calling monkey_patch(), then this thread-local won't
+ # be green. To workaround this, reload the module after calling
+ # monkey_patch()
+ reload_module(importutils.import_module('oslo_context.context'))
+
+
+if six.PY2:
+ nested_contexts = contextlib.nested
+else:
+ @contextlib.contextmanager
+ def nested_contexts(*contexts):
+ with contextlib.ExitStack() as stack:
+ yield [stack.enter_context(c) for c in contexts]
+
+
+def run_once(message, logger, cleanup=None):
+ """This is a utility function decorator to ensure a function
+ is run once and only once in an interpreter instance.
+ The decorated function object can be reset by calling its
+ reset function. All exceptions raised by the wrapped function,
+ logger and cleanup function will be propagated to the caller.
+ """
+ def outer_wrapper(func):
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ if not wrapper.called:
+ # Note(sean-k-mooney): the called state is always
+ # updated even if the wrapped function completes
+ # by raising an exception. If the caller catches
+ # the exception it is their responsibility to call
+ # reset if they want to re-execute the wrapped function.
+ try:
+ return func(*args, **kwargs)
+ finally:
+ wrapper.called = True
+ else:
+ logger(message)
+
+ wrapper.called = False
+
+ def reset(wrapper, *args, **kwargs):
+ # Note(sean-k-mooney): we conditionally call the
+ # cleanup function if one is provided only when the
+ # wrapped function has been called previously. We catch
+ # and reraise any exception that may be raised and update
+ # the called state in a finally block to ensure its
+ # always updated if reset is called.
+ try:
+ if cleanup and wrapper.called:
+ return cleanup(*args, **kwargs)
+ finally:
+ wrapper.called = False
+
+ wrapper.reset = functools.partial(reset, wrapper)
+ return wrapper
+ return outer_wrapper
diff --git a/gosbs/version.py b/gosbs/version.py
new file mode 100644
index 0000000..268086c
--- /dev/null
+++ b/gosbs/version.py
@@ -0,0 +1,90 @@
+# Copyright 2011 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Origin https://github.com/openstack/nova/blob/master/nova/version.py
+
+import pbr.version
+
+from gosbs.i18n import _LE
+
+NOVA_VENDOR = "Gentoo Foundation Inc"
+NOVA_PRODUCT = "OpenStack Gosbs"
+NOVA_PACKAGE = None # OS distro package version suffix
+
+loaded = False
+version_info = pbr.version.VersionInfo('gosbs')
+version_string = version_info.version_string
+
+
+def _load_config():
+ # Don't load in global context, since we can't assume
+ # these modules are accessible when distutils uses
+ # this module
+ from six.moves import configparser
+
+ from oslo_config import cfg
+
+ from oslo_log import log as logging
+
+ global loaded, NOVA_VENDOR, NOVA_PRODUCT, NOVA_PACKAGE
+ if loaded:
+ return
+
+ loaded = True
+
+ cfgfile = cfg.CONF.find_file("release")
+ if cfgfile is None:
+ return
+
+ try:
+ cfg = configparser.RawConfigParser()
+ cfg.read(cfgfile)
+
+ if cfg.has_option("Gosbs", "vendor"):
+ NOVA_VENDOR = cfg.get("Gosbs", "vendor")
+
+ if cfg.has_option("Gobs", "product"):
+ NOVA_PRODUCT = cfg.get("Gosbs", "product")
+
+ if cfg.has_option("Gosbs", "package"):
+ NOVA_PACKAGE = cfg.get("Gosbs", "package")
+ except Exception as ex:
+ LOG = logging.getLogger(__name__)
+ LOG.error(_LE("Failed to load %(cfgfile)s: %(ex)s"),
+ {'cfgfile': cfgfile, 'ex': ex})
+
+
+def vendor_string():
+ _load_config()
+
+ return NOVA_VENDOR
+
+
+def product_string():
+ _load_config()
+
+ return NOVA_PRODUCT
+
+
+def package_string():
+ _load_config()
+
+ return NOVA_PACKAGE
+
+
+def version_string_with_package():
+ if package_string() is None:
+ return version_info.version_string()
+ else:
+ return "%s-%s" % (version_info.version_string(), package_string())
diff --git a/licenses/Apache-2.0 b/licenses/Apache-2.0
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/licenses/Apache-2.0
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/licenses/GPL-2 b/licenses/GPL-2
new file mode 100644
index 0000000..0e845b5
--- /dev/null
+++ b/licenses/GPL-2
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/patches/portage.patch b/patches/portage.patch
deleted file mode 100644
index 45fa880..0000000
--- a/patches/portage.patch
+++ /dev/null
@@ -1,292 +0,0 @@
-2016-11-20 Magnus Granberg <zorry@gentoo.org>
-
- * tbc/pym/actions.py
- Use the patched Scheduler and add build_dict so it can be ust.
- We use or own mydepgraph (build_mydepgraph) that call backtrack_depgraph.
- Return the output_buffer for emerge info.
- And pass unresolvable in action_depclean so we can use it later.
- * tbc/pym/main.py
- Use or own patched actions.
- We pass build_dict and session to some functions.
- * tbc/pym/Scheduler.py
- We copy Scheduler.py from portage and patch it.
- Fix so we can use add_buildlog_main()
- We use add_buildlog_main() for loging.
-
---- a/pym/tbc/actions.py 2013-03-22 17:57:23.000000000 +0100
-+++ b/pym/tbc/actions.py 2013-03-22 19:00:43.265582143 +0100
-@@ -72,7 +72,7 @@ from _emerge.MetadataRegen import Metada
- from _emerge.Package import Package
- from _emerge.ProgressHandler import ProgressHandler
- from _emerge.RootConfig import RootConfig
--from _emerge.Scheduler import Scheduler
-+from tbc.Scheduler import Scheduler
- from _emerge.search import search
- from _emerge.SetArg import SetArg
- from _emerge.show_invalid_depstring_notice import show_invalid_depstring_notice
-@@ -83,6 +83,8 @@ from _emerge.UnmergeDepPriority import U
- from _emerge.UseFlagDisplay import pkg_use_display
- from _emerge.userquery import userquery
-
-+from tbc.build_depgraph import build_mydepgraph
-+
- if sys.hexversion >= 0x3000000:
- long = int
- _unicode = str
-@@ -96,7 +96,7 @@ if sys.hexversion >= 0x3000000:
- else:
- _unicode = unicode
-
--def action_build(emerge_config, trees=DeprecationWarning,
-+def action_build(emerge_config, build_dict, session, trees=DeprecationWarning,
- mtimedb=DeprecationWarning, myopts=DeprecationWarning,
- myaction=DeprecationWarning, myfiles=DeprecationWarning, spinner=None):
-
-@@ -333,13 +334,8 @@ def action_build(emerge_config, trees=DeprecationWarning,
- print(darkgreen("emerge: It seems we have nothing to resume..."))
- return os.EX_OK
-
-- try:
-- success, mydepgraph, favorites = backtrack_depgraph(
-- settings, trees, myopts, myparams, myaction, myfiles, spinner)
-- except portage.exception.PackageSetNotFound as e:
-- root_config = trees[settings['EROOT']]['root_config']
-- display_missing_pkg_set(root_config, e.value)
-- return 1
-+ success, settings, trees, mtimedb, mydepgraph = build_mydepgraph(settings,
-+ trees, mtimedb, myopts, myparams, myaction, myfiles, spinner, build_dict, session)
-
- if success and mydepgraph.need_config_reload():
- load_emerge_config(emerge_config=emerge_config)
-@@ -351,7 +347,6 @@ def action_build(emerge_config, trees=DeprecationWarning,
- return 0
-
- if not success:
-- mydepgraph.display_problems()
- return 1
-
- mergecount = None
-@@ -613,7 +609,7 @@ def action_depclean(settings, trees, ldp
- # The calculation is done in a separate function so that depgraph
- # references go out of scope and the corresponding memory
- # is freed before we call unmerge().
-- rval, cleanlist, ordered, req_pkg_count = \
-+ rval, cleanlist, ordered, req_pkg_count, unresolvable = \
- calc_depclean(settings, trees, ldpath_mtimes,
- myopts, action, args_set, spinner)
-
-@@ -816,7 +812,7 @@ def calc_depclean(settings, trees, ldpat
- resolver.display_problems()
-
- if not success:
-- return 1, [], False, 0
-+ return 1, [], False, 0, []
-
- def unresolved_deps():
-
-@@ -827,7 +823,7 @@ def calc_depclean(settings, trees, ldpat
- unresolvable.add((dep.atom, dep.parent.cpv))
-
- if not unresolvable:
-- return False
-+ return None
-
- if unresolvable and not allow_missing_deps:
-
-@@ -877,11 +873,12 @@ def calc_depclean(settings, trees, ldpat
- "dependencies then use %s." % good("--nodeps"))
- writemsg_level("".join("%s%s\n" % (prefix, line) for line in msg),
- level=logging.ERROR, noiselevel=-1)
-- return True
-- return False
-+ return unresolvable
-+ return None
-
-- if unresolved_deps():
-- return 1, [], False, 0
-+ unresolvable = unresolved_deps()
-+ if not unresolvable is None:
-+ return 1, [], False, 0, unresolvable
-
- graph = resolver._dynamic_config.digraph.copy()
- required_pkgs_total = 0
-@@ -1160,7 +1157,7 @@ def calc_depclean(settings, trees, ldpat
- priority=UnmergeDepPriority(runtime=True),
- root=pkg.root)):
- resolver.display_problems()
-- return 1, [], False, 0
-+ return 1, [], False, 0, []
-
- writemsg_level("\nCalculating dependencies ")
- success = resolver._complete_graph(
-@@ -1168,9 +1165,10 @@ def calc_depclean(settings, trees, ldpat
- writemsg_level("\b\b... done!\n")
- resolver.display_problems()
- if not success:
-- return 1, [], False, 0
-- if unresolved_deps():
-- return 1, [], False, 0
-+ return 1, [], False, 0, []
-+ unresolvable = unresolved_deps()
-+ if not unresolvable is None:
-+ return 1, [], False, 0, unresolvable
-
- graph = resolver._dynamic_config.digraph.copy()
- required_pkgs_total = 0
-@@ -1179,7 +1177,7 @@ def calc_depclean(settings, trees, ldpat
- required_pkgs_total += 1
- cleanlist = create_cleanlist()
- if not cleanlist:
-- return 0, [], False, required_pkgs_total
-+ return 0, [], False, required_pkgs_total, []
- clean_set = set(cleanlist)
-
- if clean_set:
-@@ -1289,8 +1287,8 @@ def calc_depclean(settings, trees, ldpat
- graph.remove(node)
- cleanlist.append(node.cpv)
-
-- return 0, cleanlist, ordered, required_pkgs_total
-- return 0, [], False, required_pkgs_total
-+ return 0, cleanlist, ordered, required_pkgs_total, []
-+ return 0, [], False, required_pkgs_total, []
-
- def action_deselect(settings, trees, opts, atoms):
- enter_invalid = '--ask-enter-invalid' in opts
-@@ -1692,11 +1692,8 @@ def action_info(settings, trees, myopts,
- unset_vars.append(k)
- if unset_vars:
- append("Unset: "+", ".join(unset_vars))
-- append("")
-- append("")
-- writemsg_stdout("\n".join(output_buffer),
-- noiselevel=-1)
-- del output_buffer[:]
-+
-+ return False, output_buffer
-
- # If some packages were found...
- if mypkgs:
-@@ -3607,7 +3607,7 @@ def repo_name_duplicate_check(trees):
-
- return bool(ignored_repos)
-
--def run_action(emerge_config):
-+def run_action(emerge_config, build_dict, session):
-
- # skip global updates prior to sync, since it's called after sync
- if emerge_config.action not in ('help', 'info', 'sync', 'version') and \
-@@ -3258,7 +3252,7 @@ def run_action(emerge_config):
- except OSError:
- writemsg("Please install eselect to use this feature.\n",
- noiselevel=-1)
-- retval = action_build(emerge_config, spinner=spinner)
-+ retval = action_build(emerge_config, build_dict, session, spinner=spinner)
- post_emerge(emerge_config.action, emerge_config.opts,
- emerge_config.args, emerge_config.target_config.root,
- emerge_config.trees, emerge_config.target_config.mtimedb, retval)
---- a/pym/tbc/main.py 2013-03-22 17:57:23.000000000 +0100
-+++ b/pym/tbc/main.py 2012-12-06 03:32:56.104889716 +0100
-@@ -11,7 +11,7 @@ portage.proxy.lazyimport.lazyimport(glob
- 'logging',
- 'portage.util:writemsg_level',
- 'textwrap',
-- '_emerge.actions:load_emerge_config,run_action,' + \
-+ 'tbc.actions:load_emerge_config,run_action,' + \
- 'validate_ebuild_environment',
- '_emerge.help:help@emerge_help',
- )
-@@ -968,15 +968,20 @@ def profile_check(trees, myaction):
- return 1
- return os.EX_OK
-
--def emerge_main(args=None):
-+def emerge_main(args=None, build_dict=None, session=None):
- """
- @param args: command arguments (default: sys.argv[1:])
- @type args: list
-+ @param build_dict: info of the build_job
-+ @type build_dict: dict
- """
- if args is None:
- args = sys.argv[1:]
-
- args = portage._decode_argv(args)
-+
-+ if build_dict is None:
-+ build_dict = {}
-
- # Disable color until we're sure that it should be enabled (after
- # EMERGE_DEFAULT_OPTS has been parsed).
-@@ -1028,7 +1028,7 @@ def emerge_main(args=None):
- parse_opts(tmpcmdline)
-
- try:
-- return run_action(emerge_config)
-+ return run_action(emerge_config, build_dict, session)
- finally:
- # Call destructors for our portdbapi instances.
- for x in emerge_config.trees.values():
---- a/pym/tbc/Scheduler.py 2013-03-22 17:57:23.000000000 +0100
-+++ b/pym/tbc/Scheduler.py 2012-12-21 02:09:28.082301168 +0100
-@@ -62,6 +62,8 @@ from _emerge.PackageMerge import Package
- from _emerge.PollScheduler import PollScheduler
- from _emerge.SequentialTaskQueue import SequentialTaskQueue
-
-+from tbc.build_log import add_buildlog_main
-+
- if sys.hexversion >= 0x3000000:
- basestring = str
-
-@@ -1254,8 +1251,9 @@ class Scheduler(PollScheduler):
-
- def _do_merge_exit(self, merge):
- pkg = merge.merge.pkg
-+ settings = merge.merge.settings
-+ trees = self.trees
- if merge.returncode != os.EX_OK:
-- settings = merge.merge.settings
- build_dir = settings.get("PORTAGE_BUILDDIR")
- build_log = settings.get("PORTAGE_LOG_FILE")
-
-@@ -1266,6 +1264,7 @@ class Scheduler(PollScheduler):
- if not self._terminated_tasks:
- self._failed_pkg_msg(self._failed_pkgs[-1], "install", "to")
- self._status_display.failed = len(self._failed_pkgs)
-+ add_buildlog_main(settings, pkg, trees)
- return
-
- self._task_complete(pkg)
-@@ -1284,6 +1283,7 @@ class Scheduler(PollScheduler):
- self._pkg_cache.pop(pkg_to_replace, None)
-
- if pkg.installed:
-+ add_buildlog_main(settings, pkg, trees)
- return
-
- # Call mtimedb.commit() after each merge so that
-@@ -1294,6 +1294,7 @@ class Scheduler(PollScheduler):
- if not mtimedb["resume"]["mergelist"]:
- del mtimedb["resume"]
- mtimedb.commit()
-+ add_buildlog_main(settings, pkg, trees)
-
- def _build_exit(self, build):
- self._running_tasks.pop(id(build), None)
-@@ -1318,6 +1319,8 @@ class Scheduler(PollScheduler):
- self._status_display.merges = len(self._task_queues.merge)
- else:
- settings = build.settings
-+ trees = self.trees
-+ pkg = build.pkg
- build_dir = settings.get("PORTAGE_BUILDDIR")
- build_log = settings.get("PORTAGE_LOG_FILE")
-
-@@ -1329,6 +1332,7 @@ class Scheduler(PollScheduler):
- self._failed_pkg_msg(self._failed_pkgs[-1], "emerge", "for")
- self._status_display.failed = len(self._failed_pkgs)
- self._deallocate_config(build.settings)
-+ add_buildlog_main(settings, pkg, trees)
- self._jobs -= 1
- self._status_display.running = self._jobs
- self._schedule()
diff --git a/patches/repoman.patch b/patches/repoman.patch
deleted file mode 100644
index 64ea894..0000000
--- a/patches/repoman.patch
+++ /dev/null
@@ -1,42 +0,0 @@
-2016-11-20 Magnus Granberg <zorry@gentoo.org>
-
- * tbc/pym/repoman.py
- We add config_root pkdir and remove action and return
- vcs_settings.qatracker and qawarnings.
-
---- a/pym/tbc/repoman.py 2015-09-22 02:20:41.000000000 +0200
-+++ b/pym/tbc/repoman.py 2015-10-04 20:21:57.586494104 +0200
-@@ -45,8 +45,9 @@ bad = create_color_func("BAD")
- os.umask(0o22)
-
-
--def repoman_main(argv):
-- config_root = os.environ.get("PORTAGE_CONFIGROOT")
-+def repoman_main(argv, config_root=None, pkgdir=None):
-+ if config_root is None:
-+ config_root = os.environ.get("PORTAGE_CONFIGROOT")
- repoman_settings = portage.config(config_root=config_root, local_config=False)
-
- if repoman_settings.get("NOCOLOR", "").lower() in ("yes", "true") or \
-@@ -71,6 +72,9 @@ def repoman_main(argv):
- # commit (like if Manifest generation fails).
- can_force = True
-
-+ if not pkgdir is None:
-+ os.chdir(pkgdir)
-+
- portdir, portdir_overlay, mydir = utilities.FindPortdir(repoman_settings)
- if portdir is None:
- sys.exit(1)
-@@ -159,10 +163,4 @@ def repoman_main(argv):
- qa_output = qa_output.getvalue()
- qa_output = qa_output.splitlines(True)
-
-- # output the results
-- actions = Actions(repo_settings, options, scanner, vcs_settings)
-- if actions.inform(can_force.get(), result):
-- # perform any other actions
-- actions.perform(qa_output)
--
-- sys.exit(0)
-+ return vcs_settings.qatracker, qawarnings
diff --git a/pym/tbc/ConnectionManager.py b/pym/tbc/ConnectionManager.py
deleted file mode 100644
index 40abfd5..0000000
--- a/pym/tbc/ConnectionManager.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 1998-2015 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-from __future__ import print_function
-import logging
-
-def NewConnection(tbc_settings_dict):
- backend=tbc_settings_dict['sql_backend']
- host=tbc_settings_dict['sql_host']
- user=tbc_settings_dict['sql_user']
- password=tbc_settings_dict['sql_passwd']
- database=tbc_settings_dict['sql_db']
- if backend == 'mysql':
- try:
- from sqlalchemy import create_engine
- except ImportError:
- print("Please install a recent version of dev-python/sqlalchemy for Python")
- sys.exit(1)
- #logging.basicConfig()
- #logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
- mysqldriver = 'mysql+mysqlconnector'
- return create_engine(mysqldriver + '://' + user + ':' + password + '@' + host + '/' + database, pool_recycle=120)
diff --git a/pym/tbc/build_depgraph.py b/pym/tbc/build_depgraph.py
deleted file mode 100644
index 5d8b93e..0000000
--- a/pym/tbc/build_depgraph.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# Copyright 1998-2015 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-from __future__ import print_function
-from _emerge.create_depgraph_params import create_depgraph_params
-from _emerge.depgraph import backtrack_depgraph
-import portage
-portage.proxy.lazyimport.lazyimport(globals(),
- 'tbc.actions:load_emerge_config',
-)
-from portage.exception import PackageSetNotFound
-
-from tbc.build_log import log_fail_queru
-
-def build_mydepgraph(settings, trees, mtimedb, myopts, myparams, myaction, myfiles, spinner, build_dict, session):
- try:
- success, mydepgraph, favorites = backtrack_depgraph(
- settings, trees, myopts, myparams, myaction, myfiles, spinner)
- except portage.exception.PackageSetNotFound as e:
- root_config = trees[settings["ROOT"]]["root_config"]
- display_missing_pkg_set(root_config, e.value)
- build_dict['type_fail'] = "depgraph fail\n"
- build_dict['check_fail'] = True
- else:
- if not success:
- repeat = True
- repeat_times = 0
- while repeat:
- if mydepgraph._dynamic_config._needed_p_mask_changes:
- build_dict['type_fail'] = "Mask package or dep\n"
- build_dict['check_fail'] = True
- elif mydepgraph._dynamic_config._needed_use_config_changes:
- mydepgraph._display_autounmask()
- build_dict['type_fail'] = "Need use change\n"
- build_dict['check_fail'] = True
- elif mydepgraph._dynamic_config._slot_conflict_handler:
- build_dict['type_fail'] = "Slot blocking\n"
- build_dict['check_fail'] = True
- elif mydepgraph._dynamic_config._circular_deps_for_display:
- build_dict['type_fail'] = "Circular Deps\n"
- build_dict['check_fail'] = True
- elif mydepgraph._dynamic_config._unsolvable_blockers:
- build_dict['type_fail'] = "Blocking packages\n"
- build_dict['check_fail'] = True
- else:
- build_dict['type_fail'] = "Dep calc fail\n"
- build_dict['check_fail'] = True
- mydepgraph.display_problems()
- if repeat_times is 2:
- repeat = False
- log_fail_queru(session, build_dict, settings)
- else:
- repeat_times = repeat_times + 1
- settings, trees, mtimedb = load_emerge_config()
- myparams = create_depgraph_params(myopts, myaction)
- try:
- success, mydepgraph, favorites = backtrack_depgraph(
- settings, trees, myopts, myparams, myaction, myfiles, spinner)
- except portage.exception.PackageSetNotFound as e:
- root_config = trees[settings["ROOT"]]["root_config"]
- display_missing_pkg_set(root_config, e.value)
- if success:
- repeat = False
-
- return success, settings, trees, mtimedb, mydepgraph
diff --git a/pym/tbc/build_job.py b/pym/tbc/build_job.py
deleted file mode 100644
index 8d10a68..0000000
--- a/pym/tbc/build_job.py
+++ /dev/null
@@ -1,202 +0,0 @@
-# Copyright 1998-2015 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-from __future__ import print_function
-import portage
-import os
-import re
-import sys
-import signal
-
-from portage import _encodings
-from portage import _unicode_decode
-from portage.versions import cpv_getkey
-from portage.dep import check_required_use
-from portage.checksum import perform_checksum
-from tbc.depclean import do_depclean
-from tbc.flags import tbc_use_flags
-from tbc.qachecks import check_file_in_manifest
-from tbc.main import emerge_main
-from tbc.build_log import log_fail_queru
-from tbc.actions import load_emerge_config
-from tbc.sqlquerys import add_logs, get_packages_to_build, update_buildjobs_status, is_build_job_done, get_ebuild_restrictions
-
-class build_job_action(object):
-
- def __init__(self, config_id, session):
- self._config_id = config_id
- self._session = session
-
- def make_build_list(self, build_dict, settings, portdb):
- cp = build_dict['cp']
- repo = build_dict['repo']
- package = build_dict['package']
- cpv = build_dict['cpv']
- pkgdir = portdb.getRepositoryPath(repo) + "/" + cp
- build_use_flags_list = []
- try:
- ebuild_version_checksum_tree = perform_checksum(pkgdir + "/" + package + "-" + build_dict['ebuild_version'] + ".ebuild", "SHA256")[0]
- except:
- ebuild_version_checksum_tree = None
- if ebuild_version_checksum_tree == build_dict['checksum']:
- manifest_error = check_file_in_manifest(pkgdir, settings, portdb, cpv, build_use_flags_list, repo)
- if manifest_error is None:
- init_flags = tbc_use_flags(settings, portdb, cpv)
- build_use_flags_list = init_flags.comper_useflags(build_dict)
- log_msg = "build_use_flags_list %s" % (build_use_flags_list,)
- add_logs(self._session, log_msg, "info", self._config_id)
- manifest_error = check_file_in_manifest(pkgdir, settings, portdb, cpv, build_use_flags_list, repo)
- if manifest_error is None:
- build_dict['check_fail'] = False
- build_cpv_dict = {}
- build_cpv_dict[cpv] = build_use_flags_list
- log_msg = "build_cpv_dict: %s" % (build_cpv_dict,)
- add_logs(self._session, log_msg, "info", self._config_id)
- return build_cpv_dict
- build_dict['type_fail'] = "Manifest error"
- build_dict['check_fail'] = True
- log_msg = "Manifest error: %s:%s" % (cpv, manifest_error)
- add_logs(self._session, log_msg, "info", self._config_id)
- else:
- build_dict['type_fail'] = "Wrong ebuild checksum"
- build_dict['check_fail'] = True
- if build_dict['check_fail'] is True:
- log_fail_queru(self._session, build_dict, settings)
- return None
-
- def build_procces(self, buildqueru_cpv_dict, build_dict, settings, portdb):
- build_cpv_list = []
- depclean_fail = True
- disable_test_features = False
- enable_test_features = False
- restrictions_test = False
- restrictions_list= get_ebuild_restrictions(self._session, build_dict['ebuild_id'])
- if restrictions_list:
- if "test" in restrictions_list:
- restrictions_test = True
- if restrictions_test and "test" in settings.features:
- disable_test_features = True
- for k, build_use_flags_list in buildqueru_cpv_dict.items():
- build_cpv_list.append("=" + k)
- if not build_use_flags_list == None:
- build_use_flags = ""
- for flags in build_use_flags_list:
- build_use_flags = build_use_flags + flags + " "
- filetext = '=' + k + ' ' + build_use_flags
- log_msg = "filetext: %s" % filetext
- add_logs(self._session, log_msg, "info", self._config_id)
- with open("/etc/portage/package.use/99_autounmask", "a") as f:
- f.write(filetext)
- f.write('\n')
- f.close
-
- if not build_dict['build_useflags'] is None:
- if "test" in build_dict['build_useflags']:
- if build_dict['build_useflags']['test'] is False and "test" in settings.features:
- disable_test_features = True
- if build_dict['build_useflags']['test'] is True and not disable_test_features and "test" not in settings.features:
- enable_test_features = True
- if disable_test_features:
- filetext = '=' + k + ' ' + 'notest.conf'
- log_msg = "filetext: %s" % filetext
- add_logs(self._session, log_msg, "info", self._config_id)
- with open("/etc/portage/package.env/99_env", "a") as f:
- f.write(filetext)
- f.write('\n')
- f.close
- if enable_test_features:
- filetext = '=' + k + ' ' + 'test.conf'
- log_msg = "filetext: %s" % filetext
- add_logs(self._session, log_msg, "info", self._config_id)
- with open("/etc/portage/package.env/99_env", "a") as f:
- f.write(filetext)
- f.write('\n')
- f.close
-
- log_msg = "build_cpv_list: %s" % (build_cpv_list,)
- add_logs(self._session, log_msg, "info", self._config_id)
-
- # We remove the binary package if removebin is true
- if build_dict['removebin']:
- package = build_dict['package']
- pv = package + "-" + build_dict['ebuild_version']
- binfile = settings['PKGDIR'] + "/" + build_dict['category'] + "/" + pv + ".tbz2"
- try:
- os.remove(binfile)
- except:
- log_msg = "Binary file was not removed or found: %s" % (binfile,)
- add_logs(self._session, log_msg, "info", self._config_id)
-
- argscmd = []
- for emerge_option in build_dict['emerge_options']:
- if emerge_option == '--depclean':
- pass
- elif emerge_option == '--nodepclean':
- pass
- elif emerge_option == '--nooneshot':
- pass
- else:
- if not emerge_option in argscmd:
- argscmd.append(emerge_option)
- for build_cpv in build_cpv_list:
- argscmd.append(build_cpv)
- print("Emerge options: %s" % argscmd)
- log_msg = "argscmd: %s" % (argscmd,)
- add_logs(self._session, log_msg, "info", self._config_id)
-
- # Call main_emerge to build the package in build_cpv_list
- print("Build: %s" % build_dict)
- update_buildjobs_status(self._session, build_dict['build_job_id'], 'Building', self._config_id)
- build_fail = emerge_main(argscmd, build_dict, self._session)
- # Run depclean
- if '--depclean' in build_dict['emerge_options'] and not '--nodepclean' in build_dict['emerge_options']:
- depclean_fail = do_depclean()
- try:
- os.remove("/etc/portage/package.use/99_autounmask")
- with open("/etc/portage/package.use/99_autounmask", "a") as f:
- f.close
- os.remove("/etc/portage/package.env/99_env")
- with open("/etc/portage/package.env/99_env/", "a") as f:
- f.close
- except:
- pass
-
- if is_build_job_done(self._session, build_dict['build_job_id']):
- update_buildjobs_status(self._session, build_dict['build_job_id'], 'Looked', self._config_id)
- log_msg = "build_job %s was not removed" % (build_dict['build_job_id'],)
- add_logs(self._session, log_msg, "info", self._config_id)
- print("qurery was not removed")
- build_dict['type_fail'] = "Querey was not removed\n"
- build_dict['check_fail'] = True
- log_fail_queru(self._session, build_dict, settings)
- if build_fail is True:
- build_dict['type_fail'] = "Emerge faild\n"
- build_dict['check_fail'] = True
- log_msg = "Emerge faild!"
- add_logs(self._session, log_msg, "info", self._config_id)
- return True
- return False
-
- def procces_build_jobs(self):
- build_dict = {}
- build_dict = get_packages_to_build(self._session, self._config_id)
- if build_dict is None:
- return
- print("build_dict: %s" % (build_dict,))
- log_msg = "build_dict: %s" % (build_dict,)
- add_logs(self._session, log_msg, "info", self._config_id)
- if not build_dict['ebuild_id'] is None and build_dict['checksum'] is not None:
- settings, trees, mtimedb = load_emerge_config()
- portdb = trees[settings["ROOT"]]["porttree"].dbapi
- buildqueru_cpv_dict = self.make_build_list(build_dict, settings, portdb)
- log_msg = "buildqueru_cpv_dict: %s" % (buildqueru_cpv_dict,)
- add_logs(self._session, log_msg, "info", self._config_id)
- if buildqueru_cpv_dict is None:
- return
- fail_build_procces = self.build_procces(buildqueru_cpv_dict, build_dict, settings, portdb)
- return
- if not build_dict['emerge_options'] is [] and build_dict['ebuild_id'] is None:
- return
- if not build_dict['ebuild_id'] is None and build_dict['emerge_options'] is None:
- pass
- # del_old_queue(self._session, build_dict['queue_id'])
diff --git a/pym/tbc/build_log.py b/pym/tbc/build_log.py
deleted file mode 100644
index e9e5dd0..0000000
--- a/pym/tbc/build_log.py
+++ /dev/null
@@ -1,383 +0,0 @@
-# Copyright 1998-2016 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-from __future__ import print_function
-import re
-import os
-import platform
-import hashlib
-import logging
-
-from portage.versions import catpkgsplit, cpv_getversion
-import portage
-from portage.util import writemsg, \
- writemsg_level, writemsg_stdout
-from portage import _encodings
-from portage import _unicode_encode
-from portage.checksum import perform_checksum
-from _emerge.main import parse_opts
-
-portage.proxy.lazyimport.lazyimport(globals(),
- 'tbc.actions:action_info,load_emerge_config',
-)
-from tbc.irk import send_irk
-from tbc.qachecks import check_repoman, repoman_full
-from tbc.text import get_log_text_dict
-from tbc.readconf import read_config_settings
-from tbc.flags import tbc_use_flags
-from tbc.ConnectionManager import NewConnection
-from tbc.sqlquerys import get_config_id, get_ebuild_id_db, add_new_buildlog, \
- get_package_info, get_build_job_id, get_use_id, get_config_info, get_hilight_info, get_error_info_list, \
- add_e_info, get_fail_times, add_fail_times, update_fail_times, del_old_build_jobs, add_old_ebuild, \
- update_buildjobs_status, add_repoman_qa, get_config_id_fqdn, get_setup_info, \
- add_repoman_log, get_tbc_config
-from tbc.log import write_log
-
-from sqlalchemy.orm import sessionmaker
-
-def check_repoman_full(session, pkgdir, package_id, config_id, cpv=False):
- # Check cp with repoman repoman full
- write_log(session, 'Repoman Check', "info", config_id, 'build_log.check_repoman_full')
- status = repoman_full(session, pkgdir, config_id)
- repoman_hash = hashlib.sha256()
- if cpv:
- ebuild_version_tree = portage.versions.cpv_getversion(cpv)
- if status:
- repoman_dict = {}
- for k, v in status.items():
- repoman_log2 = []
- for line in v:
- if cpv:
- if re.search(ebuild_version_tree, line):
- repoman_log2.append(line)
- else:
- repoman_log2.append(line)
- if not repoman_log2 == []:
- repoman_dict[k] = repoman_log2
- if not repoman_dict == {}:
- repoman_log = ""
- for k, v in repoman_dict.items():
- repoman_log = repoman_log + k + "\n"
- repoman_hash.update(k.encode('utf-8'))
- for line in v:
- repoman_log = repoman_log + line + "\n"
- repoman_hash.update(line.encode('utf-8'))
- add_repoman_log(session, package_id, repoman_log, repoman_hash.hexdigest())
- write_log(session, 'Repoman Check Fail\n' + repoman_log, "warning", config_id, 'build_log.check_repoman_full')
- return repoman_log
- write_log(session, 'Repoman Check Pass', "info", config_id, 'build_log.check_repoman_full')
- return False
-
-def get_build_dict_db(session, config_id, settings, tbc_settings_dict, pkg):
- myportdb = portage.portdbapi(mysettings=settings)
- cpvr_list = catpkgsplit(pkg.cpv, silent=1)
- categories = cpvr_list[0]
- package = cpvr_list[1]
- repo = pkg.repo
- ebuild_version = cpv_getversion(pkg.cpv)
- log_msg = "Setting up logging for %s:%s" % (pkg.cpv, repo,)
- write_log(session, log_msg, "info", config_id, 'build_log.get_build_dict_db')
- PackageInfo = get_package_info(session, categories, package, repo)
- build_dict = {}
- build_dict['ebuild_version'] = ebuild_version
- build_dict['package_id'] = PackageInfo.PackageId
- build_dict['cpv'] = pkg.cpv
- build_dict['categories'] = categories
- build_dict['package'] = package
- build_dict['repo'] = repo
- build_dict['config_id'] = config_id
- init_useflags = tbc_use_flags(settings, myportdb, pkg.cpv)
- iuse_flags_list, final_use_list = init_useflags.get_flags_pkg(pkg, settings)
- iuse = []
- for iuse_line in iuse_flags_list:
- iuse.append(init_useflags.reduce_flag(iuse_line))
- iuse_flags_list2 = list(set(iuse))
- use_enable = final_use_list
- use_disable = list(set(iuse_flags_list2).difference(set(use_enable)))
- use_flagsDict = {}
- for x in use_enable:
- use_id = get_use_id(session, x)
- use_flagsDict[use_id] = True
- for x in use_disable:
- use_id = get_use_id(session, x)
- use_flagsDict[use_id] = False
- if use_enable == [] and use_disable == []:
- build_dict['build_useflags'] = None
- else:
- build_dict['build_useflags'] = use_flagsDict
- pkgdir = myportdb.getRepositoryPath(repo) + "/" + categories + "/" + package
- ebuild_version_checksum_tree = perform_checksum(pkgdir+ "/" + package + "-" + ebuild_version + ".ebuild", "SHA256")[0]
- build_dict['checksum'] = ebuild_version_checksum_tree
- ebuild_id_list, status = get_ebuild_id_db(session, build_dict['checksum'], build_dict['package_id'], build_dict['ebuild_version'])
- if status:
- if ebuild_id_list is None:
- log_msg = "%s:%s Don't have any ebuild_id!" % (pkg.cpv, repo,)
- write_log(session, log_msg, "error", config_id, 'build_log.get_build_dict_db')
- else:
- old_ebuild_id_list = []
- for ebuild_id in ebuild_id_list:
- log_msg = "%s:%s:%s Dups of checksums" % (pkg.cpv, repo, ebuild_id,)
- write_log(session, log_msg, "error", config_id, 'build_log.get_build_dict_db')
- old_ebuild_id_list.append(ebuild_id)
- add_old_ebuild(session, old_ebuild_id_list)
- return
- build_dict['ebuild_id'] = ebuild_id_list
-
- build_job_id = get_build_job_id(session, build_dict)
- if build_job_id is None:
- build_dict['build_job_id'] = None
- else:
- build_dict['build_job_id'] = build_job_id
- return build_dict
-
-def search_buildlog(session, logfile_text_dict, max_text_lines):
- log_search_list = get_hilight_info(session)
- hilight_list = []
- for index, text_line in logfile_text_dict.items():
- for search_pattern in log_search_list:
- if re.search(search_pattern.HiLightSearch, text_line):
- hilight_tmp = {}
- hilight_tmp['startline'] = index - search_pattern.HiLightStart
- hilight_tmp['hilight'] = search_pattern.HiLightCssId
- if search_pattern.HiLightSearchEnd == "":
- hilight_tmp['endline'] = index + search_pattern.HiLightEnd
- if hilight_tmp['endline'] > max_text_lines:
- hilight_tmp['endline'] = max_text_lines
- elif not search_pattern.HiLightSearchEnd == "" and (index + 1) >= max_text_lines:
- hilight_tmp['endline'] = max_text_lines
- else:
- i = index + 1
- match = True
- while match:
- if i >= max_text_lines:
- match = False
- break
- if re.search(search_pattern.HiLightSearchPattern, logfile_text_dict[i]) and re.search(search_pattern.HiLightSearchPattern, logfile_text_dict[i + 1]):
- for search_pattern2 in log_search_list:
- if re.search(search_pattern2.HiLightSearch, logfile_text_dict[i]):
- match = False
- if match:
- i = i + 1
- elif re.search(search_pattern.HiLightSearchPattern, logfile_text_dict[i]) and re.search(search_pattern.HiLightSearchEnd, logfile_text_dict[i + 1]):
- i = i + 1
- match = False
- else:
- match = False
- if i >= max_text_lines:
- hilight_tmp['endline'] = max_text_lines
- if re.search(search_pattern.HiLightSearchEnd, logfile_text_dict[i]):
- hilight_tmp['endline'] = i
- else:
- hilight_tmp['endline'] = i - 1
- hilight_list.append(hilight_tmp)
-
- new_hilight_dict = {}
- for hilight_tmp in hilight_list:
- add_new_hilight = True
- add_new_hilight_middel = None
- for k, v in sorted(new_hilight_dict.items()):
- if hilight_tmp['startline'] == hilight_tmp['endline']:
- if v['endline'] == hilight_tmp['startline'] or v['startline'] == hilight_tmp['startline']:
- add_new_hilight = False
- if hilight_tmp['startline'] > v['startline'] and hilight_tmp['startline'] < v['endline']:
- add_new_hilight = False
- add_new_hilight_middel = k
- else:
- if v['endline'] == hilight_tmp['startline'] or v['startline'] == hilight_tmp['startline']:
- add_new_hilight = False
- if hilight_tmp['startline'] > v['startline'] and hilight_tmp['startline'] < v['endline']:
- add_new_hilight = False
- if add_new_hilight is True:
- adict = {}
- adict['startline'] = hilight_tmp['startline']
- adict['hilight_css_id'] = hilight_tmp['hilight']
- adict['endline'] = hilight_tmp['endline']
- new_hilight_dict[hilight_tmp['startline']] = adict
- if not add_new_hilight_middel is None:
- adict1 = {}
- adict2 = {}
- adict3 = {}
- adict1['startline'] = new_hilight_dict[add_new_hilight_middel]['startline']
- adict1['endline'] = hilight_tmp['startline'] -1
- adict1['hilight_css_id'] = new_hilight_dict[add_new_hilight_middel]['hilight']
- adict2['startline'] = hilight_tmp['startline']
- adict2['hilight_css_id'] = hilight_tmp['hilight']
- adict2['endline'] = hilight_tmp['endline']
- adict3['startline'] = hilight_tmp['endline'] + 1
- adict3['hilight_css_id'] = new_hilight_dict[add_new_hilight_middel]['hilight']
- adict3['endline'] = new_hilight_dict[add_new_hilight_middel]['endline']
- del new_hilight_dict[add_new_hilight_middel]
- new_hilight_dict[adict1['startline']] = adict1
- new_hilight_dict[adict2['startline']] = adict2
- new_hilight_dict[adict3['startline']] = adict3
- return new_hilight_dict
-
-def get_buildlog_info(session, settings, pkg, build_dict, config_id):
- myportdb = portage.portdbapi(mysettings=settings)
- logfile_text_dict, max_text_lines = get_log_text_dict(settings.get("PORTAGE_LOG_FILE"))
- hilight_dict = search_buildlog(session, logfile_text_dict, max_text_lines)
- error_log_list = []
- qa_error_list = []
- repoman_error_list = []
- sum_build_log_list = []
- error_info_list = get_error_info_list(session)
- for k, v in sorted(hilight_dict.items()):
- if v['startline'] == v['endline']:
- error_log_list.append(logfile_text_dict[k])
- if v['hilight_css_id'] == 3: # qa = 3
- qa_error_list.append(logfile_text_dict[k])
- else:
- i = k
- while i != (v['endline'] + 1):
- error_log_list.append(logfile_text_dict[i])
- if v['hilight_css_id'] == 3: # qa = 3
- qa_error_list.append(logfile_text_dict[i])
- i = i +1
-
- # Run repoman full
- element = portage.versions.cpv_getkey(build_dict['cpv']).split('/')
- categories = element[0]
- package = element[1]
- pkgdir = myportdb.getRepositoryPath(build_dict['repo']) + "/" + categories + "/" + package
- repoman_error_list = check_repoman_full(session, pkgdir, build_dict['package_id'], config_id, build_dict['cpv'])
- build_log_dict = {}
- error_search_line = "^ \\* ERROR: "
- build_log_dict['fail'] = False
- if repoman_error_list:
- sum_build_log_list.append(1) # repoman = 1
- build_log_dict['fail'] = True
- if qa_error_list != []:
- sum_build_log_list.append(2) # qa = 2
- build_log_dict['fail'] = True
- else:
- qa_error_list = False
- for error_log_line in error_log_list:
- if re.search(error_search_line, error_log_line):
- build_log_dict['fail'] = True
- for error_info in error_info_list:
- if re.search(error_info.ErrorSearch, error_log_line):
- sum_build_log_list.append(error_info.ErrorId)
- build_log_dict['repoman_error_list'] = repoman_error_list
- build_log_dict['qa_error_list'] = qa_error_list
- build_log_dict['error_log_list'] = error_log_list
- build_log_dict['summary_error_list'] = sum_build_log_list
- build_log_dict['hilight_dict'] = hilight_dict
- return build_log_dict
-
-def get_emerge_info_id(settings, trees, session, config_id):
- args = []
- args.append("--info")
- myaction, myopts, myfiles = parse_opts(args, silent=True)
- status, emerge_info_list = action_info(settings, trees, myopts, myfiles)
- emerge_info = ""
- return "\n".join(emerge_info_list)
-
-def add_buildlog_main(settings, pkg, trees):
- tbc_settings = read_config_settings()
- Session = sessionmaker(bind=NewConnection(tbc_settings))
- session = Session()
- config_id = get_config_id_fqdn(session, tbc_settings['hostname'])
- ConfigInfo = get_config_info(session, config_id)
- SetupInfo = get_setup_info(session, ConfigInfo.SetupId)
- host_config = ConfigInfo.Hostname +"/" + SetupInfo.Setup
- if pkg.type_name == "binary":
- build_dict = None
- else:
- build_dict = get_build_dict_db(session, config_id, settings, tbc_settings, pkg)
- if build_dict is None:
- log_msg = "Package %s:%s is NOT logged." % (pkg.cpv, pkg.repo,)
- write_log(session, log_msg, "info", config_id, 'build_log.add_buildlog_main')
- session.close
- return
- build_log_dict = {}
- build_log_dict = get_buildlog_info(session, settings, pkg, build_dict, config_id)
- error_log_list = build_log_dict['error_log_list']
- build_log_dict['logfilename'] = settings.get("PORTAGE_LOG_FILE").split(host_config)[1]
- build_error = ""
- log_hash = hashlib.sha256()
- build_error = ""
- if error_log_list != []:
- for log_line in error_log_list:
- if not re.search(build_log_dict['logfilename'], log_line):
- build_error = build_error + log_line
- log_hash.update(build_error.encode('utf-8'))
- build_log_dict['build_error'] = build_error
- build_log_dict['log_hash'] = log_hash.hexdigest()
- log_msg = "Logfile name: %s" % (settings.get("PORTAGE_LOG_FILE"),)
- write_log(session, log_msg, "info", config_id, 'build_log.add_buildlog_main')
- build_log_dict['emerge_info'] = get_emerge_info_id(settings, trees, session, config_id)
- log_id = add_new_buildlog(session, build_dict, build_log_dict)
-
- if log_id is None:
- log_msg = "Package %s:%s is NOT logged." % (pkg.cpv, pkg.repo,)
- write_log(session, log_msg, "info", config_id, 'build_log.add_buildlog_main')
- else:
- add_repoman_qa(session, build_log_dict, log_id)
- os.chmod(settings.get("PORTAGE_LOG_FILE"), 0o664)
- log_msg = "Package: %s:%s is logged." % (pkg.cpv, pkg.repo,)
- write_log(session, log_msg, "info", config_id, 'build_log.add_buildlog_main')
- build_msg = "BUILD: PASS"
- qa_msg = "QA: PASS"
- repoman_msg = "REPOMAN: PASS"
- if build_log_dict['fail']:
- for error_id in build_log_dict['summary_error_list']:
- if error_id == 1:
- repoman_msg = "REPOMAN: FAILD"
- elif error_id ==2:
- qa_msg = "QA: FAILD"
- else:
- build_msg = "BUILD: FAILD"
- tbc_config = get_tbc_config(session)
- msg = "Package: %s Repo: %s %s %s %s Weblink http://%s/%s\n" % (pkg.cpv, pkg.repo, build_msg, repoman_msg, qa_msg, tbc_config.WebIrker, log_id,)
- write_log(session, msg, "info", config_id, 'build_log.add_buildlog_main')
- send_irk(msg, tbc_config.HostIrker)
- session.close
-
-def log_fail_queru(session, build_dict, settings):
- config_id = build_dict['config_id']
- if get_fail_times(session, build_dict):
- fail_querue_dict = {}
- fail_querue_dict['build_job_id'] = build_dict['build_job_id']
- fail_querue_dict['fail_type'] = build_dict['type_fail']
- fail_querue_dict['fail_times'] = 1
- add_fail_times(session, fail_querue_dict)
- update_buildjobs_status(session, build_dict['build_job_id'], 'Waiting', config_id)
- else:
- build_log_dict = {}
- error_log_list = []
- sum_build_log_list = []
- sum_build_log_list.append(3) # Others errors
- error_log_list.append(build_dict['type_fail'])
- build_log_dict['summary_error_list'] = sum_build_log_list
- if build_dict['type_fail'] == 'merge fail':
- error_log_list = []
- for k, v in build_dict['failed_merge'].items():
- error_log_list.append(v['fail_msg'])
- build_log_dict['error_log_list'] = error_log_list
- build_error = ""
- if error_log_list != []:
- for log_line in error_log_list:
- build_error = build_error + log_line
- build_log_dict['build_error'] = build_error
- build_log_dict['log_hash'] = '0'
- useflagsdict = {}
- if build_dict['build_useflags'] == {}:
- for k, v in build_dict['build_useflags'].items():
- use_id = get_use_id(session, k)
- useflagsdict[use_id] = v
- build_dict['build_useflags'] = useflagsdict
- else:
- build_dict['build_useflags'] = None
- if settings.get("PORTAGE_LOG_FILE") is not None:
- ConfigInfo= get_config_info(session, config_id)
- host_config = ConfigInfo.Hostname +"/" + ConfigInfo.Config
- build_log_dict['logfilename'] = settings.get("PORTAGE_LOG_FILE").split(host_config)[1]
- os.chmod(settings.get("PORTAGE_LOG_FILE"), 0o664)
- else:
- build_log_dict['logfilename'] = ""
- build_log_dict['hilight_dict'] = {}
- settings2, trees, tmp = load_emerge_config()
- build_log_dict['emerge_info'] = get_emerge_info_id(settings2, trees, session, config_id)
- build_log_dict['fail'] = True
- log_id = add_new_buildlog(session, build_dict, build_log_dict)
- del_old_build_jobs(session, build_dict['build_job_id'])
diff --git a/pym/tbc/buildquerydb.py b/pym/tbc/buildquerydb.py
deleted file mode 100644
index 7fe7f82..0000000
--- a/pym/tbc/buildquerydb.py
+++ /dev/null
@@ -1,102 +0,0 @@
-# Copyright 1998-2015 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-import sys
-import os
-
-# Get the options from the config file set in tbc.readconf
-from tbc.readconf import get_conf_settings
-reader=get_conf_settings()
-tbc_settings_dict=reader.read_tbc_settings_all()
-config_profile = tbc_settings_dict['tbc_config']
-
-from tbc.check_setup import check_make_conf
-from tbc.sync import git_pull
-from tbc.package import tbc_package
-import portage
-import multiprocessing
-
-def add_cpv_query_pool(mysettings, myportdb, config_id, cp, repo):
- conn =0
- init_package = tbc_package(mysettings, myportdb)
- # FIXME: remove the check for tbc when in tree
- if cp != "dev-python/tbc":
- build_dict = {}
- packageDict = {}
- ebuild_id_list = []
- # split the cp to categories and package
- element = cp.split('/')
- categories = element[0]
- package = element[1]
- log_msg = "C %s:%s" % (cp, repo,)
- add_tbc_logs(conn, log_msg, "info", config_id)
- pkgdir = self._myportdb.getRepositoryPath(repo) + "/" + cp
- config_id_list = []
- config_id_list.append(config_id)
- config_cpv_listDict = init_package.config_match_ebuild(cp, config_id_list)
- if config_cpv_listDict != {}:
- cpv = config_cpv_listDict[config_id]['cpv']
- packageDict[cpv] = init_package.get_packageDict(pkgdir, cpv, repo)
- build_dict['checksum'] = packageDict[cpv]['ebuild_version_checksum_tree']
- build_dict['package_id'] = get_package_id(conn, categories, package, repo)
- build_dict['ebuild_version'] = packageDict[cpv]['ebuild_version_tree']
- ebuild_id = get_ebuild_id_db_checksum(conn, build_dict)
- if ebuild_id is not None:
- ebuild_id_list.append(ebuild_id)
- init_package.add_new_ebuild_buildquery_db(ebuild_id_list, packageDict, config_cpv_listDict)
- log_msg = "C %s:%s ... Done." % (cp, repo,)
- add_tbc_logs(conn, log_msg, "info", config_id)
- return
-
-def add_buildquery_main(config_id):
- conn = 0
- config_setup = get_config(conn, config_id)
- log_msg = "Adding build jobs for: %s" % (config_setup,)
- add_tbc_logs(conn, log_msg, "info", config_id)
- check_make_conf()
- log_msg = "Check configs done"
- add_tbc_logs(conn, log_msg, "info", config_profile)
- # Get default config from the configs table and default_config=1
- default_config_root = "/var/cache/tbc/" + tbc_settings_dict['tbc_gitreponame'] + "/" + config_setup + "/"
- # Set config_root (PORTAGE_CONFIGROOT) to default_config_root
- mysettings = portage.config(config_root = default_config_root)
- myportdb = portage.portdbapi(mysettings=mysettings)
- init_package = tbc_package(mysettings, myportdb)
- log_msg = "Setting default config to: %s" % (config_setup)
- add_tbc_logs(conn, log_msg, "info", config_is)
- # Use all exept 2 cores when multiprocessing
- pool_cores= multiprocessing.cpu_count()
- if pool_cores >= 3:
- use_pool_cores = pool_cores - 2
- else:
- use_pool_cores = 1
- pool = multiprocessing.Pool(processes=use_pool_cores)
-
- repo_trees_list = myportdb.porttrees
- for repo_dir in repo_trees_list:
- repo = myportdb.getRepositoryName(repo_dir)
- repo_dir_list = []
- repo_dir_list.append(repo_dir)
-
- # Get the package list from the repo
- package_list_tree = myportdb.cp_all(trees=repo_dir_list)
- for cp in sorted(package_list_tree):
- pool.apply_async(add_cpv_query_pool, (mysettings, myportdb, config_id, cp, repo,))
- pool.close()
- pool.join()
- log_msg = "Adding build jobs for: %s ... Done." % (config_setup,)
- add_tbc_logs(conn, log_msg, "info", config_profile)
- return True
-
-def del_buildquery_main(config_id):
- conn=0
- config_setup = get_config(conn, config_id)
- log_msg = "Removeing build jobs for: %s" % (config_setup,)
- add_tbc_logs(conn, log_msg, "info", config_id)
- build_job_id_list = get_build_jobs_id_list_config(conn, config_id)
- if build_job_id_list is not None:
- for build_job_id in build_job_id_list:
- del_old_build_jobs(conn, build_job_id)
- log_msg = "Removeing build jobs for: %s ... Done." % (config_setup,)
- add_tbc_logs(conn, log_msg, "info", config_id)
- return True
diff --git a/pym/tbc/check_setup.py b/pym/tbc/check_setup.py
deleted file mode 100644
index dcd7a0c..0000000
--- a/pym/tbc/check_setup.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# Copyright 1998-2015 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-from __future__ import print_function
-import portage
-import os
-import errno
-
-from portage.exception import DigestException, FileNotFound, ParseError, PermissionDenied
-from portage.checksum import perform_checksum
-from tbc.text import get_file_text
-from tbc.sqlquerys import get_config_all_info, add_logs, get_configmetadata_info, get_setup_info
-from tbc.sync import git_pull
-
-def check_make_conf(session, config_id):
- log_msg = "Checking configs for changes and errors"
- add_logs(session, log_msg, "info", config_id)
- HostConfigsMetaDataInfo = get_configmetadata_info(session, config_id)
- git_repo = HostConfigsMetaDataInfo.RepoPath + "/"
- git_pull(session, git_repo, config_id)
- configsDict = {}
- for ConfigInfo in get_config_all_info(session):
- attDict={}
- # Set the config dir
- SetupInfo = get_setup_info(session, ConfigInfo.ConfigId)
- check_config_dir = git_repo + ConfigInfo.Hostname +"/" + SetupInfo.Setup + "/"
- make_conf_file = check_config_dir + "etc/portage/make.conf"
- ConfigsMetaDataInfo = get_configmetadata_info(session, ConfigInfo.ConfigId)
- # Check if we can take a checksum on it.
- # Check if we have some error in the file. (portage.util.getconfig)
- # Check if we envorment error with the config. (settings.validate)
- try:
- make_conf_checksum_tree = perform_checksum(make_conf_file, "SHA256")[0]
- portage.util.getconfig(make_conf_file, tolerant=0, allow_sourcing=True, expand=True)
- mysettings = portage.config(config_root = check_config_dir)
- mysettings.validate()
- # With errors we update the db on the config and disable the config
- except ParseError as e:
- ConfigsMetaDataInfo.ConfigErrorText = str(e)
- ConfigsMetaDataInfo.Active = False
- log_msg = "%s FAIL!" % (ConfigInfo.Hostname,)
- add_logs(session, log_msg, "info", config_id)
- session.commit()
- else:
- ConfigsMetaDataInfo.Active = True
- log_msg = "%s PASS" % (ConfigInfo.Hostname,)
- add_logs(session, log_msg, "info", config_id)
- session.commit()
- if make_conf_checksum_tree != ConfigsMetaDataInfo.Checksum:
- ConfigsMetaDataInfo.MakeConfText = get_file_text(make_conf_file)
- ConfigsMetaDataInfo.Checksum = make_conf_checksum_tree
- session.commit()
- log_msg = "Checking configs for changes and errors ... Done"
- add_logs(session, log_msg, "info", config_id)
-
-def check_configure_guest(session, config_id):
- GuestConfigsMetaDataInfo = get_configmetadata_info(session, config_id)
- git_repo = GuestConfigsMetaDataInfo.RepoPath + "/"
- git_pull(session, git_repo, config_id)
- make_conf_file = "/etc/portage/make.conf"
- # Check if we can open the file and close it
- # Check if we have some error in the file (portage.util.getconfig)
- # Check if we envorment error with the config (settings.validate)
- try:
- make_conf_checksum_tree = perform_checksum(make_conf_file, "SHA256")[0]
- portage.util.getconfig(make_conf_file, tolerant=0, allow_sourcing=True, expand=True)
- mysettings = portage.config(config_root = "/")
- mysettings.validate()
- # With errors we return false
- except Exception as e:
- return False
- if make_conf_checksum_tree != GuestConfigsMetaDataInfo.Checksum:
- return False
- return True
diff --git a/pym/tbc/db_mapping.py b/pym/tbc/db_mapping.py
deleted file mode 100644
index f48ef56..0000000
--- a/pym/tbc/db_mapping.py
+++ /dev/null
@@ -1,306 +0,0 @@
-# Copyright 1998-2015 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-import datetime
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy import Column, Integer, String, Boolean, DateTime, Enum, Text, ForeignKey
-from sqlalchemy.orm import relationship, backref
-
-Base = declarative_base()
-
-class Keywords(Base):
- KeywordId = Column('keyword_id', Integer, primary_key=True)
- Keyword = Column('keyword', String)
- __tablename__ = 'keywords'
-
-class Setups(Base):
- SetupId = Column('setup_id', Integer, primary_key=True)
- Setup = Column('setup', String(100))
- Profile = Column('profile', String(150))
- Test = Column('test', Boolean, default=False)
- Repoman = Column('repoman', Boolean, default=False)
- __tablename__ = 'setups'
-
-class Configs(Base):
- ConfigId = Column('config_id', Integer, primary_key=True)
- Hostname = Column('hostname', String(150))
- SetupId = Column('setup_id', Integer, ForeignKey('setups.setup_id'))
- Host = Column('default_config', Boolean, default=False)
- __tablename__ = 'configs'
-
-class Logs(Base):
- LogId = Column('log_id', Integer, primary_key=True)
- ConfigId = Column('config_id', Integer, ForeignKey('configs.config_id'))
- LogType = Column('log_type', Enum('info','error','debug','qa','repoman'))
- Msg = Column('msg', Text)
- TimeStamp = Column('time_stamp', DateTime, nullable=False, default=datetime.datetime.utcnow)
- __tablename__ = 'logs'
-
-class Jobs(Base):
- JobId = Column('job_id', Integer, primary_key=True)
- JobType = Column('job_type', Enum('updatedb', 'esync', 'removeold_cpv'))
- Status = Column('status', Enum('Runing', 'Done', 'Waiting', 'Waiting_on_guest'))
- User = Column('user', String(50))
- ConfigId = Column('config_id', Integer, ForeignKey('configs.config_id'))
- RunConfigId = Column('run_config_id', Integer, ForeignKey('configs.config_id'))
- TimeStamp = Column('time_stamp', DateTime, nullable=False, default=datetime.datetime.utcnow)
- __tablename__ = 'jobs'
-
-class ConfigsMetaData(Base):
- Id = Column('id', Integer, primary_key=True)
- ConfigId = Column('config_id', Integer, ForeignKey('configs.config_id'))
- KeywordId = Column('keyword_id', Integer, ForeignKey('keywords.keyword_id'))
- MakeConfText = Column('make_conf_text', Text)
- Checksum = Column('checksum', String(100))
- ConfigSync = Column('configsync', Boolean, default=False)
- Active = Column('active', Boolean, default=False)
- ConfigErrorText = Column('config_error_text', Text)
- Updateing = Column('updateing', Boolean, default=False)
- Status = Column('status', Enum('Stopped', 'Runing', 'Waiting'))
- Auto = Column('auto', Boolean, default=False)
- RepoPath = Column('repo_path', String(200))
- TimeStamp = Column('time_stamp', DateTime, nullable=False, default=datetime.datetime.utcnow)
- __tablename__ = 'configs_metadata'
-
-class Categories(Base):
- CategoryId = Column('category_id', Integer, primary_key=True)
- Category = Column('category', String(150))
- Active = Column('active', Boolean, default=True)
- TimeStamp = Column('time_stamp', DateTime, nullable=False, default=datetime.datetime.utcnow)
- __tablename__ = 'categories'
-
-class CategoriesMetadata(Base):
- Id = Column('id', Integer, primary_key=True)
- CategoryId = Column('category_id', Integer, ForeignKey('categories.category_id'))
- Checksum = Column('checksum', String(100))
- Descriptions = Column('descriptions', Text)
- __tablename__ = 'categories_metadata'
-
-class Repos(Base):
- RepoId = Column('repo_id', Integer, primary_key=True)
- Repo = Column('repo', String(100))
- __tablename__ = 'repos'
-
-class Packages(Base):
- PackageId = Column('package_id', Integer, primary_key=True)
- CategoryId = Column('category_id', Integer, ForeignKey('categories.category_id'))
- Package = Column('package',String(150))
- RepoId = Column('repo_id', Integer, ForeignKey('repos.repo_id'))
- Active = Column('active', Boolean, default=False)
- TimeStamp = Column('time_stamp', DateTime, nullable=False, default=datetime.datetime.utcnow)
- __tablename__ = 'packages'
-
-class Emails(Base):
- EmailId = Column('email_id', Integer, primary_key=True)
- Email = Column('email', String(150))
- __tablename__ = 'emails'
-
-class PackagesEmails(Base):
- Id = Column('id', Integer, primary_key=True)
- PackageId = Column('package_id', Integer, ForeignKey('packages.package_id'))
- EmailId = Column('email_id', Integer, ForeignKey('emails.email_id'))
- __tablename__ = 'packages_emails'
-
-class PackagesMetadata(Base):
- Id = Column('id', Integer, primary_key=True)
- PackageId = Column('package_id', Integer, ForeignKey('packages.package_id'))
- Gitlog = Column('gitlog', Text)
- Descriptions = Column('descriptions', Text)
- New = Column('new', Boolean, default=False)
- __tablename__ = 'packages_metadata'
-
-class Ebuilds(Base):
- EbuildId = Column('ebuild_id', Integer, primary_key=True)
- PackageId = Column('package_id', Integer, ForeignKey('packages.package_id'))
- Version = Column('version', String(150))
- Checksum = Column('checksum', String(100))
- Active = Column('active', Boolean, default=False)
- TimeStamp = Column('time_stamp', DateTime, nullable=False, default=datetime.datetime.utcnow)
- __tablename__ = 'ebuilds'
-
-class EmergeOptions(Base):
- EmergeOptionId = Column('eoption_id', Integer, primary_key=True)
- EOption = Column('eoption', String(45))
- __tablename__ = 'emerge_options'
-
-class ConfigsEmergeOptions(Base):
- ConfigId = Column('config_id', Integer, ForeignKey('configs.config_id'), primary_key=True)
- EOptionId = Column('eoption_id', Integer, ForeignKey('emerge_options.eoption_id'))
- __tablename__ = 'configs_emerge_options'
-
-class BuildJobs(Base):
- BuildJobId = Column('build_job_id', Integer, primary_key=True)
- EbuildId = Column('ebuild_id', Integer, ForeignKey('ebuilds.ebuild_id'))
- SetupId = Column('setup_id', Integer, ForeignKey('setups.setup_id'))
- ConfigId = Column('config_id', Integer, ForeignKey('configs.config_id'))
- Status = Column('status', Enum('Waiting','Building','Looked',))
- BuildNow = Column('build_now', Boolean, default=False)
- RemoveBin = Column('removebin', Boolean ,default=False)
- New = Column('new', Boolean, default=False)
- TimeStamp = Column('time_stamp', DateTime, nullable=False, default=datetime.datetime.utcnow)
- __tablename__ = 'build_jobs'
-
-class BuildJobsEmergeOptions(Base):
- Id = Column('id', Integer, primary_key=True)
- BuildJobId = Column('build_job_id', Integer, ForeignKey('build_jobs.build_job_id'))
- EOption = Column('eoption_id', Integer, ForeignKey('emerge_options.eoption_id'))
- __tablename__ = 'build_jobs_emerge_options'
-
-class BuildJobsRedo(Base):
- Id = Column('id', Integer, primary_key=True)
- BuildJobId = Column('build_job_id', Integer, ForeignKey('build_jobs.build_job_id'))
- FailTimes = Column('fail_times', Integer)
- FailType = Column('fail_type', String(50))
- TimeStamp = Column('time_stamp', DateTime, nullable=False, default=datetime.datetime.utcnow)
- __tablename__ = 'build_jobs_redo'
-
-class Uses(Base):
- UseId = Column('use_id', Integer, primary_key=True)
- Flag = Column('flag', String(150))
- __tablename__ = 'uses'
-
-class BuildJobsUse(Base):
- Id = Column('id', Integer, primary_key=True)
- BuildJobId = Column('build_job_id', Integer, ForeignKey('build_jobs.build_job_id'))
- UseId = Column('use_id', Integer, ForeignKey('uses.use_id'))
- Status = Column('status', Boolean, default=False)
- __tablename__ = 'build_jobs_use'
-
-class HiLightCss(Base):
- HiLightCssId = Column('hilight_css_id', Integer, primary_key=True)
- HiLightCssName = Column('hilight_css_name', String(30))
- HiLightCssCollor = Column('hilight_css_collor', String(30))
- __tablename__ = 'hilight_css'
-
-class HiLight(Base):
- HiLightId = Column('hilight_id', Integer, primary_key=True)
- HiLightSearch = Column('hilight_search', String(50))
- HiLightSearchEnd = Column('hilight_search_end', String(50))
- HiLightSearchPattern = Column('hilight_search_pattern', String(50))
- HiLightCssId = Column('hilight_css_id', Integer, ForeignKey('hilight_css.hilight_css_id'))
- HiLightStart = Column('hilight_start', Integer)
- HiLightEnd = Column('hilight_end', Integer)
- __tablename__ = 'hilight'
-
-class BuildLogs(Base):
- BuildLogId = Column('build_log_id', Integer, primary_key=True)
- EbuildId = Column('ebuild_id', Integer, ForeignKey('ebuilds.ebuild_id'))
- Fail = Column('fail', Boolean, default=False)
- SummeryText = Column('summery_text', Text)
- LogHash = Column('log_hash', String(100))
- BugId = Column('bug_id', Integer, default=0)
- New = Column('new', Boolean, default=False)
- TimeStamp = Column('time_stamp', DateTime, nullable=False, default=datetime.datetime.utcnow)
- __tablename__ = 'build_logs'
-
-class EmergeInfo(Base):
- EInfoId = Column('einfo_id', Integer, primary_key=True)
- EmergeInfoText = Column('emerge_info_text', Text)
- __tablename__ = 'emerge_info'
-
-class BuildLogsConfig(Base):
- LogId = Column('log_id', Integer, primary_key=True)
- BuildLogId = Column('build_log_id', Integer, ForeignKey('build_logs.build_log_id'))
- ConfigId = Column('config_id', Integer, ForeignKey('configs.config_id'))
- EInfoId = Column('einfo_id', Integer, ForeignKey('emerge_info.einfo_id'))
- LogName = Column('logname', String(450))
- TimeStamp = Column('time_stamp', DateTime, nullable=False, default=datetime.datetime.utcnow)
- __tablename__ = 'build_logs_config'
-
-class BuildLogsHiLight(Base):
- BuildLogHiLightId = Column('id', Integer, primary_key=True)
- LogId = Column('log_id', Integer, ForeignKey('build_logs_config.log_id'))
- StartLine = Column('start_line', Integer)
- EndLine = Column('end_line', Integer)
- HiLightCssId = Column('hilight_css_id', Integer, ForeignKey('hilight_css.hilight_css_id'))
- __tablename__ = 'build_logs_hilight'
-
-class BuildLogsEmergeOptions(Base):
- Id = Column('id', Integer, primary_key=True)
- BuildLogId = Column('build_log_id', Integer, ForeignKey('Build_logs.Build_log_id'))
- EmergeOptionId = Column('eoption_id', Integer, ForeignKey('emerge_options.eoption_id'))
- __tablename__ = 'build_logs_emerge_options'
-
-class BuildLogsUse(Base):
- Id = Column('id', Integer, primary_key=True)
- BuildLogId = Column('build_log_id', Integer, ForeignKey('build_logs.build_log_id'))
- UseId = Column('use_id', Integer, ForeignKey('uses.use_id'))
- Status = Column('status', Boolean, default=False)
- __tablename__ = 'build_logs_use'
-
-class BuildLogsRepoman(Base):
- Id = Column('id', Integer, primary_key=True)
- BuildLogId = Column('build_log_id', Integer, ForeignKey('build_logs.build_log_id'))
- SummeryText = Column('summery_text', Text)
- __tablename__ = 'build_logs_repoman'
-
-class BuildLogsQa(Base):
- Id = Column('id', Integer, primary_key=True)
- BuildLogId = Column('build_log_id', Integer, ForeignKey('build_logs.build_log_id'))
- SummeryText = Column('summery_text', Text)
- __tablename__ = 'build_logs_qa'
-
-class PackagesRepoman(Base):
- Id = Column('id', Integer, primary_key=True)
- PackageId = Column('package_id', Integer, ForeignKey('packages.package_id'))
- RepomanText = Column('repoman_text', Text)
- RepomanHash = Column('repoman_hash', String(100))
- TimeStamp = Column('time_stamp', DateTime, nullable=False, default=datetime.datetime.utcnow)
- __tablename__ = 'packages_repoman'
-
-class ErrorsInfo(Base):
- ErrorId = Column('error_id', Integer, primary_key=True)
- ErrorName = Column('error_name', String)
- ErrorSearch = Column('error_search', String)
- __tablename__ = 'errors_info'
-
-class BuildLogsErrors(Base):
- BuildLogErrorId = Column('id', Integer, primary_key=True)
- BuildLogId = Column('build_log_id', Integer, ForeignKey('build_logs.build_log_id'))
- ErrorId = Column('error_id', Integer, ForeignKey('errors_info.error_id'))
- __tablename__ = 'build_logs_errors'
-
-class Restrictions(Base):
- RestrictionId = Column('restriction_id', Integer, primary_key=True)
- Restriction = Column('restriction', String(150))
- __tablename__ = 'restrictions'
-
-class EbuildsRestrictions(Base):
- Id = Column('id', Integer, primary_key=True)
- EbuildId = Column('ebuild_id', ForeignKey('ebuilds.ebuild_id'))
- RestrictionId = Column('restriction_id', ForeignKey('restrictions.restriction_id'))
- __tablename__ = 'ebuilds_restrictions'
-
-class EbuildsIUse(Base):
- Id = Column('id', Integer, primary_key=True)
- EbuildId = Column('ebuild_id', ForeignKey('ebuilds.ebuild_id'))
- UseId = Column('use_id', ForeignKey('uses.use_id'))
- Status = Column('status', Boolean, default=False)
- __tablename__= 'ebuilds_iuse'
-
-class EbuildsKeywords(Base):
- Id = Column('id', Integer, primary_key=True)
- EbuildId = Column('ebuild_id', ForeignKey('ebuilds.ebuild_id'))
- KeywordId = Column('keyword_id', ForeignKey('keywords.keyword_id'))
- Status = Column('status', Enum('Stable','Unstable','Negative'))
- __tablename__ = 'ebuilds_keywords'
-
-class EbuildsMetadata(Base):
- Id = Column('id', Integer, primary_key=True)
- EbuildId = Column('ebuild_id', ForeignKey('ebuilds.ebuild_id'))
- Commit = Column('git_commit', String(100))
- CommitMsg = Column('git_commit_msg', String(200))
- New = Column('new', Boolean, default=False)
- Updated = Column('updated', Boolean, default=False)
- Descriptions = Column('descriptions', Text)
- Slot = Column('slot', String(10))
- Homepage = Column('homepage', String(200))
- License = Column('license', String(200))
- __tablename__ = 'ebuilds_metadata'
-
-class TbcConfig(Base):
- Id = Column('id', Integer, primary_key=True)
- WebIrker = Column('webirker', String)
- HostIrker = Column('hostirker', String)
- WebBug = Column('webbug', String)
- __tablename__ = 'tbc_config'
diff --git a/pym/tbc/depclean.py b/pym/tbc/depclean.py
deleted file mode 100644
index 3154ac5..0000000
--- a/pym/tbc/depclean.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright 1998-2015 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-from __future__ import print_function
-import portage
-from portage._sets.base import InternalPackageSet
-from _emerge.main import parse_opts
-from tbc.actions import load_emerge_config, action_depclean, calc_depclean
-
-def do_depclean():
- mysettings, mytrees, mtimedb = load_emerge_config()
- myroot = mysettings["ROOT"]
- root_config = mytrees[myroot]["root_config"]
- psets = root_config.setconfig.psets
- args_set = InternalPackageSet(allow_repo=True)
- spinner=None
- scheduler=None
- tmpcmdline = []
- tmpcmdline.append("--depclean")
- tmpcmdline.append("--pretend")
- print("depclean",tmpcmdline)
- myaction, myopts, myfiles = parse_opts(tmpcmdline, silent=False)
- if myfiles:
- args_set.update(myfiles)
- matched_packages = False
- for x in args_set:
- if vardb.match(x):
- matched_packages = True
- if not matched_packages:
- return 0
-
- rval, cleanlist, ordered, req_pkg_count, unresolvable = calc_depclean(mysettings, mytrees, mtimedb["ldpath"], myopts, myaction, args_set, spinner)
- print('rval, cleanlist, ordered, req_pkg_count, unresolvable', rval, cleanlist, ordered, req_pkg_count, unresolvable)
- if unresolvable != []:
- return True
- if cleanlist != []:
- conflict_package_list = []
- for depclean_cpv in cleanlist:
- if portage.versions.cpv_getkey(depclean_cpv) in list(psets["system"]):
- conflict_package_list.append(depclean_cpv)
- if portage.versions.cpv_getkey(depclean_cpv) in list(psets['selected']):
- conflict_package_list.append(depclean_cpv)
- print('conflict_package_list', conflict_package_list)
- if conflict_package_list == []:
- tmpcmdline = []
- tmpcmdline.append("--depclean")
- myaction, myopts, myfiles = parse_opts(tmpcmdline, silent=False)
- rval = action_depclean(mysettings, mytrees, mtimedb["ldpath"], myopts, myaction, myfiles, spinner, scheduler=None)
- return True
- else:
- print("conflicting packages: %s", conflict_package_list)
- return True
- return True
diff --git a/pym/tbc/flags.py b/pym/tbc/flags.py
deleted file mode 100644
index eb3f782..0000000
--- a/pym/tbc/flags.py
+++ /dev/null
@@ -1,222 +0,0 @@
-# Copyright 1998-2016 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-# Origin flags.py from portage public api repo
-from __future__ import print_function
-import portage
-import os
-
-class tbc_use_flags(object):
-
- def __init__(self, mysettings, myportdb, cpv):
- self._mysettings = mysettings
- self._myportdb = myportdb
- self._cpv = cpv
-
- def get_iuse(self):
- """Gets the current IUSE flags from the tree
- To be used when a gentoolkit package object is not needed
- @type: cpv: string
- @param cpv: cat/pkg-ver
- @rtype list
- @returns [] or the list of IUSE flags
- """
- return self._myportdb.aux_get(self._cpv, ["IUSE"])[0].split()
-
- def reduce_flag(self, flag):
- """Absolute value function for a USE flag
- @type flag: string
- @param flag: the use flag to absolute.
- @rtype: string
- @return absolute USE flag
- """
- if flag[0] in ["+","-"]:
- return flag[1:]
- else:
- return flag
-
- def reduce_flags(self, the_list):
- """Absolute value function for a USE flag list
- @type the_list: list
- @param the_list: the use flags to absolute.
- @rtype: list
- @return absolute USE flags
- """
- r=[]
- for member in the_list:
- r.append(self.reduce_flag(member))
- return r
-
- def filter_flags(self, use, use_expand_hidden, usemasked, useforced):
- """Filter function to remove hidden or otherwise not normally
- visible USE flags from a list.
- @type use: list
- @param use: the USE flag list to be filtered.
- @type use_expand_hidden: list
- @param use_expand_hidden: list of flags hidden.
- @type usemasked: list
- @param usemasked: list of masked USE flags.
- @type useforced: list
- @param useforced: the forced USE flags.
- @rtype: list
- @return the filtered USE flags.
- """
- # clean out some environment flags, since they will most probably
- # be confusing for the user
- for f in use_expand_hidden:
- f=f.lower() + "_"
- for x in use:
- if f in x:
- use.remove(x)
- # clean out any arch's
- archlist = self._mysettings["PORTAGE_ARCHLIST"].split()
- for a in use[:]:
- if a in archlist:
- use.remove(a)
- # clean out any abi_ flag
- for a in use[:]:
- if a.startswith("abi_"):
- use.remove(a)
- # clean out any python_ flag
- for a in use[:]:
- if a.startswith("python_"):
- use.remove(a)
-
- # dbl check if any from usemasked or useforced are still there
- masked = usemasked + useforced
- for a in use[:]:
- if a in masked:
- use.remove(a)
- return use
-
- def get_all_cpv_use(self):
- """Uses portage to determine final USE flags and settings for an emerge
- @type cpv: string
- @param cpv: eg cat/pkg-ver
- @rtype: lists
- @return use, use_expand_hidden, usemask, useforce
- """
- use = None
- self._mysettings.unlock()
- try:
- self._mysettings.setcpv(self._cpv, use_cache=None, mydb=self._myportdb)
- use = self._mysettings['PORTAGE_USE'].split()
- use_expand_hidden = self._mysettings["USE_EXPAND_HIDDEN"].split()
- usemask = list(self._mysettings.usemask)
- useforce = list(self._mysettings.useforce)
- except KeyError:
- self._mysettings.reset()
- self._mysettings.lock()
- return [], [], [], []
- # reset cpv filter
- self._mysettings.reset()
- self._mysettings.lock()
- return use, use_expand_hidden, usemask, useforce
-
- def get_all_cpv_use_looked(self):
- """Uses portage to determine final USE flags and settings for an emerge
- @type cpv: string
- @param cpv: eg cat/pkg-ver
- @rtype: lists
- @return use, use_expand_hidden, usemask, useforce
- """
- # use = self._mysettings['PORTAGE_USE'].split()
- use = os.environ['USE'].split()
- use_expand_hidden = self._mysettings["USE_EXPAND_HIDDEN"].split()
- usemask = list(self._mysettings.usemask)
- useforce = list(self._mysettings.useforce)
- return use, use_expand_hidden, usemask, useforce
-
- def get_all_cpv_use_pkg(self, pkg, settings):
- """Uses portage to determine final USE flags and settings for an emerge
- @type cpv: string
- @param cpv: eg cat/pkg-ver
- @rtype: lists
- @return use, use_expand_hidden, usemask, useforce
- """
- # use = self._mysettings['PORTAGE_USE'].split()
- use_list = list(pkg.use.enabled)
- use_expand_hidden = settings["USE_EXPAND_HIDDEN"].split()
- usemask = list(settings.usemask)
- useforced = list(settings.useforce)
- return use_list, use_expand_hidden, usemask, useforced
-
- def get_flags(self):
- """Retrieves all information needed to filter out hidden, masked, etc.
- USE flags for a given package.
-
- @type cpv: string
- @param cpv: eg. cat/pkg-ver
- @type final_setting: boolean
- @param final_setting: used to also determine the final
- enviroment USE flag settings and return them as well.
- @rtype: list or list, list
- @return IUSE or IUSE, final_flags
- """
- final_use, use_expand_hidden, usemasked, useforced = self.get_all_cpv_use()
- iuse_flags = self.filter_flags(self.get_iuse(), use_expand_hidden, usemasked, useforced)
- #flags = filter_flags(use_flags, use_expand_hidden, usemasked, useforced)
- final_flags = self.filter_flags(final_use, use_expand_hidden, usemasked, useforced)
- return iuse_flags, final_flags, usemasked
-
- def get_flags_looked(self):
- """Retrieves all information needed to filter out hidden, masked, etc.
- USE flags for a given package.
-
- @type cpv: string
- @param cpv: eg. cat/pkg-ver
- @type final_setting: boolean
- @param final_setting: used to also determine the final
- enviroment USE flag settings and return them as well.
- @rtype: list or list, list
- @return IUSE or IUSE, final_flags
- """
- final_use, use_expand_hidden, usemasked, useforced = self.get_all_cpv_use_looked()
- iuse_flags = self.filter_flags(self.get_iuse(), use_expand_hidden, usemasked, useforced)
- #flags = filter_flags(use_flags, use_expand_hidden, usemasked, useforced)
- final_flags = self.filter_flags(final_use, use_expand_hidden, usemasked, useforced)
- return iuse_flags, final_flags
-
- def get_flags_pkg(self, pkg, settings):
- """Retrieves all information needed to filter out hidden, masked, etc.
- USE flags for a given package.
- @type cpv: string
- @param cpv: eg. cat/pkg-ver
- @type final_setting: boolean
- @param final_setting: used to also determine the final
- enviroment USE flag settings and return them as well.
- @rtype: list or list, list
- @return IUSE or IUSE, final_flags
- """
- final_use, use_expand_hidden, usemasked, useforced = self.get_all_cpv_use_pkg(pkg, settings)
- iuse_flags = self.filter_flags(list(pkg.iuse.all), use_expand_hidden, usemasked, useforced)
- #flags = filter_flags(use_flags, use_expand_hidden, usemasked, useforced)
- final_flags = self.filter_flags(final_use, use_expand_hidden, usemasked, useforced)
- return iuse_flags, final_flags
-
- def comper_useflags(self, build_dict):
- iuse_flags, use_enable, usemasked = self.get_flags()
- iuse = []
- build_use_flags_dict = build_dict['build_useflags']
- build_use_flags_list = []
- if use_enable == []:
- if build_use_flags_dict is None:
- return None
- for iuse_line in iuse_flags:
- iuse.append(self.reduce_flag(iuse_line))
- iuse_flags_list = list(set(iuse))
- use_disable = list(set(iuse_flags_list).difference(set(use_enable)))
- use_flagsDict = {}
- for x in use_enable:
- use_flagsDict[x] = True
- for x in use_disable:
- use_flagsDict[x] = False
- for k, v in use_flagsDict.items():
- if build_use_flags_dict[k] != v:
- if build_use_flags_dict[k]:
- build_use_flags_list.append(k)
- else:
- build_use_flags_list.append("-" + k)
- if build_use_flags_list == []:
- build_use_flags_list = None
- return build_use_flags_list
diff --git a/pym/tbc/irk.py b/pym/tbc/irk.py
deleted file mode 100644
index 3b3589b..0000000
--- a/pym/tbc/irk.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from __future__ import unicode_literals
-
-import json
-import socket
-import sys
-
-DEFAULT_SERVER = ("192.168.0.5", 6659)
-
-def connect(server = DEFAULT_SERVER):
- return socket.create_connection(server)
-
-def send(s, target, message):
- data = {"to": target, "privmsg" : message}
- s.sendall(json.dumps(data).encode('ascii'))
-
-def irk(target, message, server = DEFAULT_SERVER):
- s = connect(server)
- if "irc:" not in target and "ircs:" not in target:
- target = "irc://chat.freenode.net/{0}".format(target)
- send(s, target, message)
- s.close()
-
-def send_irk(msg, host):
- target = "tinderbox-cluster"
- try:
- irk(target, msg, server = (host, 6659))
- except socket.error as e:
- sys.stderr.write("irk: write to server failed: %r\n" % e)
diff --git a/pym/tbc/jobs.py b/pym/tbc/jobs.py
deleted file mode 100644
index 800fe87..0000000
--- a/pym/tbc/jobs.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# Copyright 1998-2016 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-from __future__ import print_function
-from tbc.sync import git_sync_main
-#from tbc.buildquerydb import add_buildquery_main, del_buildquery_main
-from tbc.updatedb import update_db_main
-from tbc.old_cpv import remove_old_cpv_main
-from tbc.sqlquerys import get_config_id, get_jobs, update_job_list
-from tbc.log import write_log
-
-def jobs_main(session, config_id):
- JobsInfo = get_jobs(session, config_id)
- if JobsInfo is None:
- return
- for JobInfo in JobsInfo:
- job = JobInfo.JobType
- run_config_id = JobInfo.RunConfigId
- job_id = JobInfo.JobId
- log_msg = "Job: %s Type: %s" % (job_id, job,)
- write_log(session, log_msg, "info", config_id, 'jobs_main')
- if job == "addbuildquery":
- update_job_list(session, "Runing", job_id)
- log_msg = "Job %s is runing." % (job_id,)
- write_log(session, log_msg, "info", config_id, 'jobs_main')
- #result = add_buildquery_main(run_config_id)
- #if result is True:
- # update_job_list(session, "Done", job_id)
- # log_msg = "Job %s is done.." % (job_id,)
- # write_log(session, log_msg, "info", config_id, 'jobs_main')
- #else:
- # update_job_list(session, "Fail", job_id)
- # log_msg = "Job %s did fail." % (job_id,)
- # write_log(session, log_msg, "info", config_id, 'jobs_main')
- elif job == "delbuildquery":
- update_job_list(session, "Runing", job_id)
- log_msg = "Job %s is runing." % (job_id,)
- write_log(session, log_msg, "info", config_id, 'jobs_main')
- #result = del_buildquery_main(config_id)
- #if result is True:
- # update_job_list(session, "Done", job_id)
- # log_msg = "Job %s is done.." % (job_id,)
- # write_log(session, log_msg, "info", config_id, 'jobs_main')
- #else:
- # update_job_list(session, "Fail", job_id)
- # log_msg = "Job %s did fail." % (job_id,)
- # write_log(session, log_msg, "info", config_id, 'jobs_main')
- elif job == "esync":
- update_job_list(session, "Waiting_on_guest", job_id)
- log_msg = "Job %s is runing." % (job_id,)
- write_log(session, log_msg, "info", config_id, 'jobs_main')
- if update_db_main(session, git_sync_main(session), config_id):
- update_job_list(session, "Done", job_id)
- log_msg = "Job %s is done.." % (job_id,)
- write_log(session, log_msg, "info", config_id, 'jobs_main')
- else:
- update_job_list(session, "Fail", job_id)
- log_msg = "Job %s did fail." % (job_id,)
- write_log(session, log_msg, "info", config_id, 'jobs_main')
- elif job == "updatedb":
- update_job_list(session, "Waiting_on_guest", job_id)
- log_msg = "Job %s is runing." % (job_id,)
- write_log(session, log_msg, "info", config_id, 'jobs_main')
- if update_db_main(session, None, config_id):
- update_job_list(session, "Done", job_id)
- log_msg = "Job %s is done.." % (job_id,)
- write_log(session, log_msg, "info", config_id, 'jobs_main')
- else:
- update_job_list(session, "Fail", job_id)
- log_msg = "Job %s did fail." % (job_id,)
- write_log(session, log_msg, "info", config_id, 'jobs_main')
- elif job == "removeold_cpv":
- update_job_list(session, "Runing", job_id)
- log_msg = "Job %s is runing." % (job_id,)
- write_log(session, log_msg, "info", config_id, 'jobs_main')
- remove_old_cpv_main(session, config_id)
- update_job_list(session, "Done", job_id)
- log_msg = "Job %s is done.." % (job_id,)
- write_log(session, log_msg, "info", config_id, 'jobs_main')
- return
diff --git a/pym/tbc/log.py b/pym/tbc/log.py
deleted file mode 100644
index 944d3fa..0000000
--- a/pym/tbc/log.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright 1998-2016 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-from __future__ import print_function
-import logging
-module_logger = logging.getLogger('tbc.log')
-
-def setup_logger( tbc_settings):
- # setupt the logger
- log_level = getattr(logging, tbc_settings['log_level'].upper(), None)
- format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
- if not isinstance(log_level, int):
- raise ValueError('Invalid log level: %s' % tbc_settings['log_level'])
- logging.basicConfig()
- logger = logging.getLogger('tbc')
- logger.setLevel(log_level)
- if tbc_settings['log_file']:
- fh = logging.FileHandler(tbc_settings['log_file'])
- else:
- fh = logging.StreamHandler()
- formatter = logging.Formatter(format)
- fh.setFormatter(formatter)
- logger.addHandler(fh)
- return logger
-
-def write_log(session, msg, level, config_id, function=False):
- if function:
- logger = logging.getLogger('tbc.' + function)
- else:
- logger = logging.getLogger('tbc')
- if level == 'info':
- logger.info(msg)
- if level == 'error':
- logger.error(msg)
- if level == 'debug':
- logger.debug(msg)
- if level == 'warning':
- logger.warning(msg)
diff --git a/pym/tbc/old_cpv.py b/pym/tbc/old_cpv.py
deleted file mode 100644
index e97b121..0000000
--- a/pym/tbc/old_cpv.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 1998-2016 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-from __future__ import print_function
-from datetime import datetime
-from tbc.log import write_log
-from tbc.sqlquerys import get_category_list_info, get_package_list_info, get_ebuild_list_info, \
- get_build_job_all, del_old_build_jobs, del_old_ebuild, del_old_package, add_old_category
-
-def remove_old_ebuilds(session, package_id, config_id, cp, today):
- EbuildsInfo = get_ebuild_list_info(session, package_id)
- for EbuildInfo in EbuildsInfo:
- cpv = cp + '-' + EbuildInfo.Version
- log_msg = "Checking: %s" % (cpv,)
- write_log(session, log_msg, "info", config_id, 'old_cpv.remove_old_ebuilds')
- if not EbuildInfo.Active:
- duration = today - EbuildInfo.TimeStamp
- if duration.days > 30:
- log_msg = "Removing: %s" % (cpv,)
- write_log(session, log_msg, "info", config_id, 'old_cpv.remove_old_ebuilds')
- build_job_id_list = get_build_job_all(session, EbuildInfo.EbuildId)
- if build_job_id_list != []:
- for build_job in build_job_id_list:
- del_old_build_jobs(session, build_job.BuildJobId)
- del_old_ebuild(session, EbuildInfo.EbuildId)
- if get_ebuild_list_info(session, package_id) == []:
- log_msg = "Removing: %s" % (cp,)
- write_log(session, log_msg, "info", config_id, 'old_cpv.remove_old_cpv_ebuilds')
- del_old_package(session, package_id)
-
-def remove_old_cpv_main(session, config_id):
- today = datetime.utcnow()
- CategorysInfo = get_category_list_info(session)
- for CategoryInfo in CategorysInfo:
- log_msg = "Checking: %s" % (CategoryInfo.Category,)
- write_log(session, log_msg, "info", config_id, 'old_cpv.remove_old_cpv_main')
- PackagesInfo = get_package_list_info(session, CategoryInfo.CategoryId)
- for PackageInfo in PackagesInfo:
- cp = CategoryInfo.Category + '/' + PackageInfo.Package
- remove_old_ebuilds(session, PackageInfo.PackageId, config_id, cp, today)
-
- if get_package_list_info(session, CategoryInfo.CategoryId) == []:
- add_old_category(session, CategoryInfo.CategoryId)
diff --git a/pym/tbc/package.py b/pym/tbc/package.py
deleted file mode 100644
index 84e3907..0000000
--- a/pym/tbc/package.py
+++ /dev/null
@@ -1,409 +0,0 @@
-# Copyright 1998-2015 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-from __future__ import print_function
-import re
-import hashlib
-import os
-import git
-import portage
-import datetime
-from portage.xml.metadata import MetaDataXML
-from portage.checksum import perform_checksum
-from tbc.flags import tbc_use_flags
-from tbc.text import get_ebuild_cvs_revision, get_log_text_dict
-from tbc.flags import tbc_use_flags
-from tbc.qachecks import check_repoman
-from tbc.build_log import check_repoman_full
-from tbc.log import write_log
-from tbc.sqlquerys import get_package_info, get_config_info, \
- add_new_build_job, add_new_ebuild_sql, get_ebuild_id_list, add_old_ebuild, \
- get_package_metadata_sql, update_package_metadata, \
- get_package_info_from_package_id, get_config_all_info, add_new_package_sql, \
- get_ebuild_checksums, get_ebuild_id_db, get_configmetadata_info, get_setup_info, \
- get_ebuild_info_ebuild_id, get_ebuild_restrictions, add_old_package
-
-class tbc_package(object):
-
- def __init__(self, session, mysettings, myportdb, config_id):
- self._session = session
- self._mysettings = mysettings
- self._myportdb = myportdb
- self._config_id = config_id
-
- def change_config(self, host_config, repopath):
- # Change config_root config_setup = table config
- my_new_setup = repopath + "/" + host_config + "/"
- mysettings_setup = portage.config(config_root = my_new_setup)
- return mysettings_setup
-
- def config_match_ebuild(self, cp, config_list, repopath):
- config_cpv_dict ={}
- if config_list == []:
- return config_cpv_dict
- for config_id in config_list:
- ConfigInfo = get_config_info(self._session, config_id)
- ConfigsMetaData = get_configmetadata_info(self._session, config_id)
- if ConfigsMetaData.Auto and ConfigsMetaData.Active and ConfigsMetaData.Status != 'Stopped' and not ConfigInfo.SetupId in config_cpv_dict:
- SetupInfo = get_setup_info(self._session, config_id)
- mysettings_setup = self.change_config(ConfigInfo.Hostname + "/" + SetupInfo.Setup, repopath)
- myportdb_setup = portage.portdbapi(mysettings=mysettings_setup)
-
- # Get the latest cpv from portage with the config that we can build
- build_cpv = myportdb_setup.xmatch('bestmatch-visible', cp)
-
- # Check if could get cpv from portage and add it to the config_cpv_listDict.
- if build_cpv != "":
-
- # Get the iuse and use flags for that config/setup and cpv
- init_useflags = tbc_use_flags(mysettings_setup, myportdb_setup, build_cpv)
- iuse_flags_list, final_use_list, usemasked = init_useflags.get_flags()
- iuse_flags_list2 = []
- for iuse_line in iuse_flags_list:
- iuse_flags_list2.append( init_useflags.reduce_flag(iuse_line))
- enable_test = False
- if SetupInfo.Test:
- if not "test" in usemasked:
- enable_test = True
- # Dict the needed info
- attDict = {}
- attDict['cpv'] = build_cpv
- attDict['useflags'] = final_use_list
- attDict['iuse'] = iuse_flags_list2
- attDict['test'] = enable_test
- config_cpv_dict[ConfigInfo.SetupId] = attDict
-
- # Clean some cache
- myportdb_setup.close_caches()
- portage.portdbapi.portdbapi_instances.remove(myportdb_setup)
- return config_cpv_dict
-
- def get_ebuild_metadata(self, cpv, repo):
- # Get the auxdbkeys infos for the ebuild
- try:
- ebuild_auxdb_list = self._myportdb.aux_get(cpv, portage.auxdbkeys, myrepo=repo)
- except:
- ebuild_auxdb_list = False
- else:
- for i in range(len(ebuild_auxdb_list)):
- if ebuild_auxdb_list[i] == '':
- ebuild_auxdb_list[i] = ''
- return ebuild_auxdb_list
-
- def get_git_log_ebuild(self, repodir, ebuild_file):
- git_log_ebuild = ''
- g = git.Git(repodir)
- index = 1
- git_log_dict = {}
- for line in g.log('-n 1', ebuild_file).splitlines():
- git_log_dict[index] = line
- index = index + 1
- git_ebuild_commit = re.sub('commit ', '', git_log_dict[1])
- git_ebuild_commit_msg = re.sub(' ', '', git_log_dict[5])
- return git_ebuild_commit, git_ebuild_commit_msg
-
- def get_packageDict(self, pkgdir, cpv, repo):
-
- #Get categories, package and version from cpv
- ebuild_version_tree = portage.versions.cpv_getversion(cpv)
- element = portage.versions.cpv_getkey(cpv).split('/')
- categories = element[0]
- package = element[1]
- ebuild_file = pkgdir + "/" + package + "-" + ebuild_version_tree + ".ebuild"
- # Make a checksum of the ebuild
- try:
- ebuild_version_checksum_tree = perform_checksum(ebuild_file, "SHA256")[0]
- except:
- ebuild_version_checksum_tree = "0"
- log_msg = "QA: Can't checksum the ebuild file. %s on repo %s" % (cpv, repo,)
- write_log(self._session, log_msg, "warning", self._config_id, 'packages.get_packageDict')
- log_msg = "C %s:%s ... Fail." % (cpv, repo)
- write_log(self._session, log_msg, "info", self._config_id, 'packages.get_packageDict')
- git_commit = '0'
- git_commit_msg = '0'
- else:
- repodir =self._myportdb.getRepositoryPath(repo)
- git_commit, git_commit_msg = self.get_git_log_ebuild(repodir, ebuild_file)
-
- # Get the ebuild metadata
- ebuild_version_metadata_tree = self.get_ebuild_metadata(cpv, repo)
- # if there some error to get the metadata we add rubish to the
- # ebuild_version_metadata_tree and set ebuild_version_checksum_tree to 0
- # so it can be updated next time we update the db
- if not ebuild_version_metadata_tree:
- log_msg = " QA: %s have broken metadata on repo %s" % (cpv, repo)
- write_log(self._session, log_msg, "warning", self._config_id, 'packages.get_packageDict')
- ebuild_version_metadata_tree = ['','','','','','','','','','','','','','','','','','','','','','','','','']
- ebuild_version_checksum_tree = '0'
-
- # add the ebuild info to the dict packages
- PackageInfo = get_package_info(self._session, categories, package, repo)
- attDict = {}
- attDict['package_id'] = PackageInfo.PackageId
- attDict['repo'] = repo
- attDict['ebuild_version'] = ebuild_version_tree
- attDict['checksum']= ebuild_version_checksum_tree
- attDict['ebuild_version_metadata_tree'] = ebuild_version_metadata_tree
- #attDict['ebuild_version_text_tree'] = ebuild_version_text_tree[0]
- attDict['git_commit'] = git_commit
- attDict['git_commit_msg'] = git_commit_msg
- attDict['new'] = False
- attDict['updated'] = False
- attDict['ebuild_version_descriptions_tree'] = ebuild_version_metadata_tree[7]
- return attDict
-
- def add_new_build_job_db(self, ebuild_id_list, packageDict, config_cpv_listDict):
- # Get the needed info from packageDict and config_cpv_listDict and put that in buildqueue
- # Only add it if ebuild_version in packageDict and config_cpv_listDict match
- if config_cpv_listDict is not None:
- # Unpack config_cpv_listDict
- for setup_id, v in config_cpv_listDict.items():
- build_cpv = v['cpv']
- iuse_flags_list = list(set(v['iuse']))
- use_enable= v['useflags']
- use_disable = list(set(iuse_flags_list).difference(set(use_enable)))
- # Make a dict with enable and disable use flags for ebuildqueuedwithuses
- use_flagsDict = {}
- for x in use_enable:
- use_flagsDict[x] = True
- for x in use_disable:
- use_flagsDict[x] = False
- enable_test = v['test']
- # Unpack packageDict
- i = 0
- for k, v in packageDict.items():
- ebuild_id = ebuild_id_list[i]
-
- # Comper and add the cpv to buildqueue
- if build_cpv == k:
- # check if we need to enable or disable test
- if "test" in use_flagsDict and enable_test:
- use_flagsDict['test'] = True
- restrictions_list = get_ebuild_restrictions(self._session, ebuild_id)
- if restrictions_list:
- if "test" in restrictions_list and "test" in use_flagsDict:
- use_flagsDict['test'] = False
- add_new_build_job(self._session, ebuild_id, setup_id, use_flagsDict, self._config_id)
- # B = Build cpv use-flags config
- # FIXME log_msg need a fix to log the use flags corect.
- log_msg = "B %s:%s USE: %s Setup: %s" % (k, v['repo'], use_flagsDict, setup_id,)
- write_log(self._session, log_msg, "info", self._config_id, 'packages.add_new_build_job_db')
- i = i +1
-
- def get_git_changelog_text(self, repodir, cp):
- n = '5'
- git_log_pkg = ''
- g = git.Git(repodir)
- git_log_pkg = g.log('-n ' + n, '--grep=' + cp)
- return git_log_pkg
-
- def get_package_metadataDict(self, pkgdir, repodir, package_id, cp):
- # Make package_metadataDict
- attDict = {}
- package_metadataDict = {}
- md_email_list = []
- herd = None
- try:
- pkg_md = MetaDataXML(pkgdir + "/metadata.xml", herd)
- except:
- log_msg = "Metadata file %s is missing or has errors" % (pkgdir + "/metadata.xml")
- write_log(self._session, log_msg, "warning", self._config_id, 'packages.get_package_metadataDict')
- else:
- tmp_herds = pkg_md.herds()
- if tmp_herds != ():
- attDict['metadata_xml_herds'] = tmp_herds[0]
- md_email_list.append(attDict['metadata_xml_herds'] + '@gentoo.org')
- for maint in pkg_md.maintainers():
- md_email_list.append(maint.email)
- if md_email_list != []:
- attDict['metadata_xml_email'] = md_email_list
- else:
- md_email_list.append('maintainer-needed@gentoo.org')
- attDict['metadata_xml_email'] = md_email_list
- log_msg = "Metadata file %s missing Email. Setting it to maintainer-needed" % (pkgdir + "/metadata.xml")
- write_log(self._session, log_msg, "warning", self._config_id, 'packages.get_package_metadataDict')
- attDict['git_changlog'] = self.get_git_changelog_text(repodir, cp)
- attDict['metadata_xml_descriptions'] = ''
- attDict['new'] = False
- package_metadataDict[package_id] = attDict
- return package_metadataDict
-
- def add_package(self, packageDict, package_metadataDict, package_id, new_ebuild_id_list, old_ebuild_id_list):
- # Use packageDict to update the db
- ebuild_id_list = add_new_ebuild_sql(self._session, packageDict)
-
- # Make old ebuilds unactive
- for ebuild_id in ebuild_id_list:
- new_ebuild_id_list.append(ebuild_id)
- for ebuild_id in get_ebuild_id_list(self._session, package_id):
- if not ebuild_id in new_ebuild_id_list:
- if not ebuild_id in old_ebuild_id_list:
- old_ebuild_id_list.append(ebuild_id)
- if not old_ebuild_id_list == []:
- add_old_ebuild(self._session, old_ebuild_id_list)
-
- # update package metadata
- update_package_metadata(self._session, package_metadataDict)
-
- # Get the best cpv for the configs and add it to config_cpv_listDict
- PackageInfo, CategoryInfo, RepoInfo = get_package_info_from_package_id(self._session, package_id)
- cp = CategoryInfo.Category + '/' + PackageInfo.Package
- config_all_info = get_config_all_info(self._session)
- config_list = []
- for config in get_config_all_info(self._session):
- if config.Host is False:
- config_list.append(config.ConfigId)
- ConfigsMetaData = get_configmetadata_info(self._session, self._config_id)
- config_cpv_listDict = self.config_match_ebuild(cp, config_list, ConfigsMetaData.RepoPath)
-
- # Add the ebuild to the build jobs table if needed
- self.add_new_build_job_db(ebuild_id_list, packageDict, config_cpv_listDict)
-
- def add_new_package_db(self, cp, repo):
- # Add new categories package ebuild to tables package and ebuilds
- # C = Checking
- # N = New Package
- log_msg = "C %s:%s" % (cp, repo)
- write_log(self._session, log_msg, "info", self._config_id, 'packages.add_new_package_db')
- log_msg = "N %s:%s" % (cp, repo)
- write_log(self._session, log_msg, "info", self._config_id, 'packages.add_new_package_db')
- repodir = self._myportdb.getRepositoryPath(repo)
- mytree = []
- mytree.append(repodir)
- pkgdir = repodir + "/" + cp # Get RepoDIR + cp
- package_id = add_new_package_sql(self._session, cp, repo)
-
- # Check cp with repoman full
- status = check_repoman_full(self._session, pkgdir, package_id, self._config_id)
- if status:
- log_msg = "Repoman %s::%s ... Fail." % (cp, repo)
- write_log(self._session, log_msg, "warning", self._config_id, 'packages.add_new_package_db')
-
- package_metadataDict = self.get_package_metadataDict(pkgdir, repodir, package_id, cp)
- # Get the ebuild list for cp
- ebuild_list_tree = self._myportdb.cp_list(cp, use_cache=1, mytree=mytree)
- if ebuild_list_tree == []:
- log_msg = "QA: Can't get the ebuilds list. %s:%s" % (cp, repo,)
- write_log(self._session, log_msg, "error", self._config_id, 'packages.add_new_package_db')
- log_msg = "C %s:%s ... Fail." % (cp, repo)
- write_log(self._session, log_msg, "warning", self._config_id, 'packages.add_new_package_db')
- return None
-
- # Make the needed packageDict with ebuild infos so we can add it later to the db.
- packageDict ={}
- new_ebuild_id_list = []
- old_ebuild_id_list = []
- for cpv in sorted(ebuild_list_tree):
- packageDict[cpv] = self.get_packageDict(pkgdir, cpv, repo)
- packageDict[cpv]['new'] = True
-
- # take package descriptions from the ebuilds
- if package_metadataDict[package_id]['metadata_xml_descriptions'] != packageDict[cpv]['ebuild_version_descriptions_tree']:
- package_metadataDict[package_id]['metadata_xml_descriptions'] = packageDict[cpv]['ebuild_version_descriptions_tree']
- package_metadataDict[package_id]['new'] = True
- self.add_package(packageDict, package_metadataDict, package_id, new_ebuild_id_list, old_ebuild_id_list)
- log_msg = "C %s:%s ... Done." % (cp, repo)
- write_log(self._session, log_msg, "info", self._config_id, 'packages.add_new_package_db')
-
- def update_package_db(self, package_id):
- # Update the categories and package with new info
- # C = Checking
- PackageInfo, CategoryInfo, RepoInfo = get_package_info_from_package_id(self._session, package_id)
- cp = CategoryInfo.Category + '/' + PackageInfo.Package
- repo = RepoInfo.Repo
- log_msg = "C %s:%s" % (cp, repo)
- write_log(self._session, log_msg, "info", self._config_id, 'packages.update_package_db')
- repodir = self._myportdb.getRepositoryPath(repo)
- pkgdir = repodir + "/" + cp # Get RepoDIR + cp
- if not os.path.isdir(pkgdir):
- old_ebuild_id_list = get_ebuild_id_list(self._session, package_id)
- for ebuild_id in old_ebuild_id_list:
- EbuildInfo = get_ebuild_info_ebuild_id(self._session, ebuild_id)
- cpv = cp + "-" + EbuildInfo.Version
- # R = remove ebuild
- log_msg = "R %s:%s" % (cpv, repo,)
- write_log(self._session, log_msg, "info", self._config_id, 'packages.update_package_db')
- add_old_ebuild(self._session, old_ebuild_id_list)
- add_old_package(self._session, package_id)
- log_msg = "C %s:%s ... Done." % (cp, repo)
- write_log(self._session, log_msg, "info", self._config_id, 'packages.update_package_db')
- return None
-
- mytree = []
- mytree.append(repodir)
-
- # Get the ebuild list for cp
- old_ebuild_id_list = []
- ebuild_list_tree = self._myportdb.cp_list(cp, use_cache=1, mytree=mytree)
- if ebuild_list_tree == []:
- log_msg = "QA: Can't get the ebuilds list. %s:%s" % (cp, repo,)
- write_log(self._session, log_msg, "error", self._config_id, 'packages.update_package_db')
- log_msg = "C %s:%s ... Fail." % (cp, repo)
- write_log(self._session, log_msg, "warning", self._config_id, 'packages.update_package_db')
- return None
-
- package_metadataDict = self.get_package_metadataDict(pkgdir, repodir, package_id, cp)
- packageDict ={}
- new_ebuild_id_list = []
- package_updated = False
- for cpv in sorted(ebuild_list_tree):
-
- # split out ebuild version
- ebuild_version_tree = portage.versions.cpv_getversion(cpv)
-
- # Get packageDict for cpv
- packageDict[cpv] = self.get_packageDict(pkgdir, cpv, repo)
-
- # take package descriptions from the ebuilds
- if package_metadataDict[package_id]['metadata_xml_descriptions'] != packageDict[cpv]['ebuild_version_descriptions_tree']:
- package_metadataDict[package_id]['metadata_xml_descriptions'] = packageDict[cpv]['ebuild_version_descriptions_tree']
-
- # Get the checksum of the ebuild in tree and db
- ebuild_version_checksum_tree = packageDict[cpv]['checksum']
- checksums_db, fail = get_ebuild_checksums(self._session, package_id, ebuild_version_tree)
-
- # check if we have dupes of the checksum from db
- if checksums_db is None:
- ebuild_version_manifest_checksum_db = None
- elif fail:
- dupe_ebuild_id_list = []
- for checksum in checksums_db:
- ebuilds_id , status = get_ebuild_id_db(self._session, checksum, package_id, ebuild_version_tree)
- for ebuild_id in ebuilds_id:
- log_msg = "U %s:%s:%s Dups of checksums" % (cpv, repo, ebuild_id,)
- write_log(self._session, log_msg, "warning", self._config_id, 'packages.update_package_db')
- dupe_ebuild_id_list.append(ebuild_id)
- add_old_ebuild(self._session, dupe_ebuild_id_list)
- ebuild_version_manifest_checksum_db = None
- else:
- ebuild_version_manifest_checksum_db = checksums_db
-
- # Check if the checksum have change
- if ebuild_version_manifest_checksum_db is None:
- # N = New ebuild
- log_msg = "N %s:%s" % (cpv, repo,)
- write_log(self._session, log_msg, "info", self._config_id, 'packages.update_package_db')
- packageDict[cpv]['updated'] = True
- package_updated = True
- elif ebuild_version_checksum_tree != ebuild_version_manifest_checksum_db:
- # U = Updated ebuild
- log_msg = "U %s:%s" % (cpv, repo,)
- write_log(self._session, log_msg, "info", self._config_id, 'packages.update_package_db')
- packageDict[cpv]['updated'] = True
- package_updated = True
- else:
- # Remove cpv from packageDict and add ebuild to new ebuils list
- del packageDict[cpv]
- ebuild_id , status = get_ebuild_id_db(self._session, ebuild_version_checksum_tree, package_id, ebuild_version_tree)
- new_ebuild_id_list.append(ebuild_id)
- self.add_package(packageDict, package_metadataDict, package_id, new_ebuild_id_list, old_ebuild_id_list)
-
- if package_updated:
- # Check cp with repoman full
- status = check_repoman_full(self._session, pkgdir, package_id, self._config_id)
- if status:
- log_msg = "Repoman %s::%s ... Fail." % (cp, repo)
- write_log(self._session, log_msg, "warning", self._config_id, 'packages.update_package_db')
-
- log_msg = "C %s:%s ... Done." % (cp, repo)
- write_log(self._session, log_msg, "info", self._config_id, 'packages.update_package_db')
diff --git a/pym/tbc/qachecks.py b/pym/tbc/qachecks.py
deleted file mode 100644
index 976cb2a..0000000
--- a/pym/tbc/qachecks.py
+++ /dev/null
@@ -1,100 +0,0 @@
-# Copyright 1998-2015 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-import os
-import warnings
-import sys
-import codecs
-from portage import os, _encodings, _unicode_decode
-from portage.checksum import _hash_filter
-from portage.exception import DigestException, FileNotFound
-from portage.localization import _
-from portage.manifest import Manifest
-from portage import os, _encodings, _unicode_decode, _unicode_encode
-from portage.exception import DigestException, FileNotFound, ParseError, PermissionDenied
-from _emerge.Package import Package
-from _emerge.RootConfig import RootConfig
-from repoman.modules.scan.ebuild.checks import run_checks
-from tbc.repoman import repoman_main
-from tbc.sqlquerys import get_configmetadata_info, get_config_info, get_setup_info
-import portage
-
-def check_file_in_manifest(pkgdir, mysettings, portdb, cpv, build_use_flags_list, repo):
- myfetchlistdict = portage.FetchlistDict(pkgdir, mysettings, portdb)
- my_manifest = portage.Manifest(pkgdir, mysettings['DISTDIR'], fetchlist_dict=myfetchlistdict, manifest1_compat=False, from_scratch=False)
- tree = portdb.getRepositoryPath(repo)
- cpv_fetchmap = portdb.getFetchMap(cpv, useflags=build_use_flags_list, mytree=tree)
- mysettings.unlock()
- try:
- portage.fetch(cpv_fetchmap, mysettings, listonly=0, fetchonly=0, locks_in_subdir='.locks', use_locks=1, try_mirrors=1)
- except:
- mysettings.lock()
- return "Can't fetch the file.\n"
- finally:
- mysettings.lock()
- try:
- my_manifest.checkCpvHashes(cpv, checkDistfiles=True, onlyDistfiles=True, checkMiscfiles=False)
- except:
- return "Can't fetch the file or the hash failed.\n"
- try:
- portdb.fetch_check(cpv, useflags=build_use_flags_list, mysettings=mysettings, all=False)
- except:
- return "Fetch check failed.\n"
- return
-
-def check_repoman(mysettings, myportdb, cpv, repo):
- # We run repoman run_checks on the ebuild
- ebuild_version_tree = portage.versions.cpv_getversion(cpv)
- element = portage.versions.cpv_getkey(cpv).split('/')
- categories = element[0]
- package = element[1]
- pkgdir = myportdb.getRepositoryPath(repo) + "/" + categories + "/" + package
- full_path = pkgdir + "/" + package + "-" + ebuild_version_tree + ".ebuild"
- root = '/'
- trees = {
- root : {'porttree' : portage.portagetree(root, settings=mysettings)}
- }
- root_config = RootConfig(mysettings, trees[root], None)
- allvars = set(x for x in portage.auxdbkeys if not x.startswith("UNUSED_"))
- allvars.update(Package.metadata_keys)
- allvars = sorted(allvars)
- myaux = dict(zip(allvars, myportdb.aux_get(cpv, allvars, myrepo=repo)))
- pkg = Package(cpv=cpv, metadata=myaux, root_config=root_config, type_name='ebuild')
- fails = []
- try:
- # All ebuilds should have utf_8 encoding.
- f = codecs.open(_unicode_encode(full_path,
- encoding = _encodings['fs'], errors = 'strict'),
- mode = 'r', encoding = _encodings['repo.content'])
- try:
- for check_name, e in run_checks(f, pkg):
- fails.append(check_name + ": " + e + "\n")
- finally:
- f.close()
- except UnicodeDecodeError:
- # A file.UTF8 failure will have already been recorded above.
- pass
- # fails will have a list with repoman errors
- if fails == []:
- return False
- return fails
-
-def repoman_full(session, pkgdir, config_id):
- ConfigsMetaData = get_configmetadata_info(session, config_id)
- ConfigInfo = get_config_info(session, config_id)
- SetupInfo = get_setup_info(session, config_id)
- config_root = ConfigsMetaData.RepoPath + '/' + ConfigInfo.Hostname + "/" + SetupInfo.Setup
- argscmd = []
- argscmd.append('--xmlparse')
- argscmd.append('full')
- qatracker, qawarnings = repoman_main(argscmd, config_root=config_root, pkgdir=pkgdir)
- adict = {}
- for key in qatracker.fails.items():
- alist = []
- for foo in key[1]:
- alist.append(foo)
- adict[key[0]] = alist
- if adict == {}:
- return False
- return adict
-
diff --git a/pym/tbc/readconf.py b/pym/tbc/readconf.py
deleted file mode 100644
index ebb3b71..0000000
--- a/pym/tbc/readconf.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright 1998-2016 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-import os
-import sys
-import re
-from socket import getfqdn
-
-configfile = "/etc/tbc/tbc.conf"
-
-def read_config_settings():
-# It will return a dict with options from the configfile
- log_file= False
- tbc_settings = {}
- try:
- open_conffile = open(configfile, 'r')
- except:
- sys.exit("Fail to open config file:" + configfile)
- textlines = open_conffile.readlines()
- for line in textlines:
- element = line.split('=')
- if element[0] == 'SQLBACKEND': # Databas backend
- get_sql_backend = element[1]
- if element[0] == 'SQLDB': # Database
- get_sql_db = element[1]
- if element[0] == 'SQLHOST': # Host
- get_sql_host = element[1]
- if element[0] == 'SQLUSER': # User
- get_sql_user = element[1]
- if element[0] == 'SQLPASSWD': # Password
- get_sql_passwd = element[1]
- if element[0] == 'LOG': # Log level
- tbc_settings['log_level'] = element[1].rstrip('\n')
- if element[0] == 'LOGFILE': # Log level
- log_file = element[1].rstrip('\n')
- open_conffile.close()
-
- tbc_settings['sql_backend'] = get_sql_backend.rstrip('\n')
- tbc_settings['sql_db'] = get_sql_db.rstrip('\n')
- tbc_settings['sql_host'] = get_sql_host.rstrip('\n')
- tbc_settings['sql_user'] = get_sql_user.rstrip('\n')
- tbc_settings['sql_passwd'] = get_sql_passwd.rstrip('\n')
- tbc_settings['hostname'] = getfqdn()
- tbc_settings['log_file'] = log_file
- return tbc_settings
diff --git a/pym/tbc/sqlquerys.py b/pym/tbc/sqlquerys.py
deleted file mode 100644
index 9c962f4..0000000
--- a/pym/tbc/sqlquerys.py
+++ /dev/null
@@ -1,665 +0,0 @@
-# Copyright 1998-2016 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-from __future__ import print_function
-import datetime
-import sys
-from tbc.db_mapping import Configs, Logs, ConfigsMetaData, Jobs, BuildJobs, Packages, Ebuilds, Repos, Categories, \
- Uses, ConfigsEmergeOptions, EmergeOptions, HiLight, BuildLogs, BuildLogsConfig, BuildJobsUse, BuildJobsRedo, \
- HiLightCss, BuildLogsHiLight, BuildLogsEmergeOptions, BuildLogsErrors, ErrorsInfo, EmergeInfo, BuildLogsUse, \
- BuildJobsEmergeOptions, EbuildsMetadata, EbuildsIUse, Restrictions, EbuildsRestrictions, EbuildsKeywords, \
- Keywords, PackagesMetadata, Emails, PackagesEmails, Setups, BuildLogsRepoman, CategoriesMetadata, \
- PackagesRepoman, BuildLogsQa, TbcConfig
-from tbc.log import write_log
-from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
-from sqlalchemy import and_, or_
-
-# Guest Functions
-def get_tbc_config(session):
- TbcConfigInfo = session.query(TbcConfig).one()
- return TbcConfigInfo
-
-def get_config_id(session, setup, host):
- SetupInfo = session.query(Setups).filter_by(Setup = setup).one()
- ConfigInfo = session.query(Configs).filter_by(SetupId = SetupInfo.SetupId).filter_by(Hostname = host).one()
- return ConfigInfo.ConfigId
-
-def get_config_id_fqdn(session, host):
- ConfigInfo = session.query(Configs).filter_by(Hostname = host).one()
- return ConfigInfo.ConfigId
-
-def add_logs(session, log_msg, log_type, config_id):
- Add_Log = Logs(ConfigId = config_id, LogType = log_type, Msg = log_msg)
- session.add(Add_Log)
- session.commit()
-
-def update_deamon_status(session, status, config_id):
- ConfigInfo = session.query(ConfigsMetaData).filter_by(ConfigId = config_id).one()
- ConfigInfo.Status = status
- session.commit()
-
-def get_jobs(session, config_id):
- JobsInfo = session.query(Jobs).filter_by(Status = 'Waiting').filter_by(ConfigId = config_id).order_by(Jobs.JobId).all()
- if JobsInfo == []:
- return None
- return JobsInfo
-
-def get_job_status_waiting_on_guest(session):
- try:
- JobsInfo = session.query(Jobs).filter_by(Status = 'Waiting_on_guest').one()
- except NoResultFound as e:
- return None
- return JobsInfo.JobId
-
-def update_job_list(session, status, job_id):
- JobInfo = session.query(Jobs).filter_by(JobId = job_id).one()
- JobInfo.Status = status
- if status == 'Done':
- JobInfo.TimeStamp = datetime.datetime.utcnow()
- session.commit()
-
-def get_config_all_info(session):
- return session.query(Configs).all()
-
-def get_config_info(session, config_id):
- ConfigInfo = session.query(Configs).filter_by(ConfigId = config_id).one()
- return ConfigInfo
-
-def get_setup_info(session, config_id):
- ConfigInfo = get_config_info(session, config_id)
- SetupInfo = session.query(Setups).filter_by(SetupId = ConfigInfo.SetupId).one()
- return SetupInfo
-
-def update_buildjobs_status(session, build_job_id, status, config_id):
- BuildJobsInfo = session.query(BuildJobs).filter_by(BuildJobId = build_job_id).one()
- BuildJobsInfo.Status = status
- BuildJobsInfo.ConfigId = config_id
- session.commit()
-
-def get_configmetadata_info(session, config_id):
- return session.query(ConfigsMetaData).filter_by(ConfigId = config_id).one()
-
-def is_build_job_done(session, build_job_id):
- try:
- BuildJobsInfo = session.query(BuildJobs).filter_by(BuildJobId = build_job_id).one()
- except NoResultFound as e:
- return False
- return True
-
-def get_packages_to_build(session, config_id):
- SetupInfo = get_setup_info(session, config_id)
- BuildJobsTmp = session.query(BuildJobs).filter(BuildJobs.SetupId==SetupInfo.SetupId). \
- order_by(BuildJobs.BuildJobId).filter_by(Status = 'Waiting')
- if BuildJobsTmp.all() == []:
- return None
- elif BuildJobsTmp.filter_by(BuildNow = True).all() != []:
- BuildJobsInfo = BuildJobsTmp.filter_by(BuildNow = True).first()
- elif BuildJobsTmp.filter_by(BuildNow = False).all() != []:
- BuildJobsInfo = BuildJobsTmp.filter_by(BuildNow = False).first()
- else:
- return None
- update_buildjobs_status(session, BuildJobsInfo.BuildJobId, 'Looked', config_id)
- EbuildsInfo = session.query(Ebuilds).filter_by(EbuildId = BuildJobsInfo.EbuildId).one()
- PackagesInfo, CategoriesInfo = session.query(Packages, Categories).filter(Packages.PackageId==EbuildsInfo.PackageId).filter(Packages.CategoryId==Categories.CategoryId).one()
- ReposInfo = session.query(Repos).filter_by(RepoId = PackagesInfo.RepoId).one()
- uses={}
- for BuildJobsUseInfo, UsesInfo in session.query(BuildJobsUse, Uses).filter(BuildJobsUse.BuildJobId==BuildJobsInfo.BuildJobId).filter(BuildJobsUse.UseId==Uses.UseId).all():
- uses[UsesInfo.Flag] = BuildJobsUseInfo.Status
- if uses == {}:
- uses = None
- emerge_options_list = []
- for ConfigsEmergeOptionsInfo, EmergeOptionsInfo in session.query(ConfigsEmergeOptions, EmergeOptions). \
- filter(ConfigsEmergeOptions.ConfigId==config_id). \
- filter(ConfigsEmergeOptions.EOptionId==EmergeOptions.EmergeOptionId).all():
- emerge_options_list.append(EmergeOptionsInfo.EOption)
- build_dict={}
- build_dict['config_id'] = config_id
- build_dict['setup_id'] = BuildJobsInfo.SetupId
- build_dict['build_job_id'] = BuildJobsInfo.BuildJobId
- build_dict['ebuild_id']= EbuildsInfo.EbuildId
- build_dict['package_id'] = EbuildsInfo.PackageId
- build_dict['package'] = PackagesInfo.Package
- build_dict['category'] = CategoriesInfo.Category
- build_dict['repo'] = ReposInfo.Repo
- build_dict['removebin'] = BuildJobsInfo.RemoveBin
- build_dict['ebuild_version'] = EbuildsInfo.Version
- build_dict['checksum'] = EbuildsInfo.Checksum
- build_dict['cp'] = CategoriesInfo.Category + '/' + PackagesInfo.Package
- build_dict['cpv'] = build_dict['cp'] + '-' + EbuildsInfo.Version
- build_dict['build_useflags'] = uses
- build_dict['emerge_options'] = emerge_options_list
- return build_dict
-
-def get_category_info(session, category):
- try:
- CategoryInfo = session.query(Categories).filter_by(Category = category).filter_by(Active = True).one()
- except NoResultFound as e:
- return False
- return CategoryInfo
-
-def get_repo_info(session, repo):
- try:
- RepoInfo = session.query(Repos).filter_by(Repo = repo).one()
- except NoResultFound as e:
- return False
- return RepoInfo
-
-def get_package_info(session, category, package, repo):
- CategoryInfo = get_category_info(session, category)
- RepoInfo = get_repo_info(session, repo)
- try:
- PackageInfo = session.query(Packages).filter_by(CategoryId = CategoryInfo.CategoryId). \
- filter_by(Package = package).filter_by(RepoId = RepoInfo.RepoId).filter_by(Active = True).one()
- except NoResultFound as e:
- return False
- return PackageInfo
-
-def get_ebuild_info(session, build_dict):
- EbuildInfo = session.query(Ebuilds).filter_by(Version = build_dict['ebuild_version']).filter_by(Checksum = build_dict['checksum']).\
- filter_by(PackageId = build_dict['package_id']).filter_by(Active = True)
- if EbuildInfo.all() == []:
- return None, True
- try:
- EbuildInfo2 = EbuildInfo.one()
- except (MultipleResultsFound) as e:
- return EbuildInfo.all(), True
- return EbuildInfo2, False
-
-def get_ebuild_info_ebuild_id(session, ebuild_id):
- return session.query(Ebuilds).filter_by(EbuildId = ebuild_id).filter_by(Active = True).one()
-
-def get_build_job_id(session, build_dict):
- BuildJobsIdInfo = session.query(BuildJobs.BuildJobId).filter_by(EbuildId = build_dict['ebuild_id']).filter_by(ConfigId = build_dict['config_id']).all()
- if BuildJobsIdInfo == []:
- return None
- for build_job_id in BuildJobsIdInfo:
- BuildJobsUseInfo = session.query(BuildJobsUse).filter_by(BuildJobId = build_job_id.BuildJobId).all()
- useflagsdict = {}
- if BuildJobsUseInfo == []:
- useflagsdict = None
- else:
- for x in BuildJobsUseInfo:
- useflagsdict[x.UseId] = x.Status
- if useflagsdict == build_dict['build_useflags']:
- return build_job_id.BuildJobId
- return None
-
-def get_use_id(session, use_flag):
- try:
- UseIdInfo = session.query(Uses).filter_by(Flag = use_flag).one()
- except NoResultFound as e:
- return None
- return UseIdInfo.UseId
-
-def get_hilight_info(session):
- return session.query(HiLight).all()
-
-def get_error_info_list(session):
- return session.query(ErrorsInfo).all()
-
-def add_e_info(session, emerge_info):
- AddEmergeInfo = EmergeInfo(EmergeInfoText = emerge_info)
- session.add(AddEmergeInfo)
- session.flush()
- EmergeInfoId = AddEmergeInfo.EInfoId
- session.commit()
- return EmergeInfoId
-
-def del_old_build_jobs(session, build_job_id):
- session.query(BuildJobsUse).filter(BuildJobsUse.BuildJobId == build_job_id).delete()
- session.query(BuildJobsRedo).filter(BuildJobsRedo.BuildJobId == build_job_id).delete()
- session.query(BuildJobsEmergeOptions).filter(BuildJobsEmergeOptions.BuildJobId == build_job_id).delete()
- session.query(BuildJobs).filter(BuildJobs.BuildJobId == build_job_id).delete()
- session.commit()
-
-def add_new_buildlog(session, build_dict, build_log_dict):
- build_log_id_list = session.query(BuildLogs.BuildLogId).filter_by(EbuildId = build_dict['ebuild_id']).all()
-
- def add_new_hilight(log_id, build_log_dict):
- for k, hilight_tmp in sorted(build_log_dict['hilight_dict'].items()):
- NewHiLight = BuildLogsHiLight(LogId = log_id, StartLine = hilight_tmp['startline'], EndLine = hilight_tmp['endline'], HiLightCssId = hilight_tmp['hilight_css_id'])
- session.add(NewHiLight)
- session.commit()
-
- def build_log_id_match(build_log_id_list, build_dict, build_log_dict):
- for build_log_id in build_log_id_list:
- log_hash = session.query(BuildLogs.LogHash).filter_by(BuildLogId = build_log_id[0]).one()
- use_list = session.query(BuildLogsUse).filter_by(BuildLogId = build_log_id[0]).all()
- useflagsdict = {}
- if use_list == []:
- useflagsdict = None
- else:
- for use in use_list:
- useflagsdict[use.UseId] = use.Status
- msg = 'Log_hash: %s Log_hash_sql: %s Build_log_id: %s' % (build_log_dict['log_hash'], log_hash[0], build_log_id,)
- write_log(session, msg, "debug", build_dict['config_id'], 'sqlquerys.add_new_buildlog.build_log_id_match')
- if log_hash[0] == build_log_dict['log_hash'] and build_dict['build_useflags'] == useflagsdict:
- if session.query(BuildLogsConfig).filter(BuildLogsConfig.ConfigId.in_([build_dict['config_id']])).filter_by(BuildLogId = build_log_id[0]):
- return None, True
- e_info_id = add_e_info(session, build_log_dict['emerge_info'])
- NewBuildLogConfig = BuildLogsConfig(BuildLogId = build_log_id[0], ConfigId = build_dict['config_id'], LogName = build_log_dict['logfilename'], EInfoId = e_info_id)
- session.add(NewBuildLogConfig)
- session.commit()
- return build_log_id[0], True
- return None, False
-
- def build_log_id_no_match(build_dict, build_log_dict):
- NewBuildLog = BuildLogs(EbuildId = build_dict['ebuild_id'], Fail = build_log_dict['fail'], SummeryText = build_log_dict['build_error'], LogHash = build_log_dict['log_hash'], New = True)
- session.add(NewBuildLog)
- session.flush()
- build_log_id = NewBuildLog.BuildLogId
- session.commit()
- if build_log_dict['summary_error_list'] != []:
- for error in build_log_dict['summary_error_list']:
- NewError = BuildLogsErrors(BuildLogId = build_log_id, ErrorId = error)
- session.add(NewError)
- session.commit()
- e_info_id = add_e_info(session, build_log_dict['emerge_info'])
- NewBuildLogConfig = BuildLogsConfig(BuildLogId = build_log_id, ConfigId = build_dict['config_id'], LogName = build_log_dict['logfilename'], EInfoId = e_info_id)
- session.add(NewBuildLogConfig)
- session.flush()
- log_id = NewBuildLogConfig.LogId
- session.commit()
- add_new_hilight(log_id, build_log_dict)
- if not build_dict['build_useflags'] is None:
- for use_id, status in build_dict['build_useflags'].items():
- NewBuildLogUse = BuildLogsUse(BuildLogId = build_log_id, UseId = use_id, Status = status)
- session.add(NewBuildLogUse)
- session.flush()
- session.commit()
- return build_log_id
-
- msg = 'build_job_id: %s build_log_id_list: %s' % (build_dict['build_job_id'], build_log_id_list,)
- write_log(session, msg, "debug", build_dict['config_id'], 'sqlquerys.add_new_buildlog')
- if build_dict['build_job_id'] is None and build_log_id_list == []:
- build_log_id = build_log_id_no_match(build_dict, build_log_dict)
- return build_log_id
- elif build_dict['build_job_id'] is None and build_log_id_list != []:
- build_log_id, match = build_log_id_match(build_log_id_list, build_dict, build_log_dict)
- if not match:
- build_log_id = build_log_id_no_match(build_dict, build_log_dict)
- return build_log_id
- elif not build_dict['build_job_id'] is None and build_log_id_list != []:
- build_log_id, match = build_log_id_match(build_log_id_list, build_dict, build_log_dict)
- if not match:
- build_log_id = build_log_id_no_match(build_dict, build_log_dict)
- del_old_build_jobs(session, build_dict['build_job_id'])
- return build_log_id
- elif not build_dict['build_job_id'] is None and build_log_id_list == []:
- build_log_id = build_log_id_no_match(build_dict, build_log_dict)
- del_old_build_jobs(session, build_dict['build_job_id'])
- return build_log_id
-
-def add_repoman_qa(session, build_log_dict, log_id):
- repoman_error = ""
- qa_error = ""
- if build_log_dict['repoman_error_list']:
- for repoman_text in build_log_dict['repoman_error_list']:
- repoman_error = repoman_error + repoman_text
- NewBuildLogRepoman = BuildLogsRepoman(BuildLogId = log_id, SummeryText = repoman_error)
- session.add(NewBuildLogRepoman)
- session.commit()
- if build_log_dict['qa_error_list']:
- for qa_text in build_log_dict['qa_error_list']:
- qa_error = qa_error + qa_text
- NewBuildLogQa = BuildLogsQa(BuildLogId = log_id, SummeryText = qa_error)
- session.add(NewBuildLogQa)
- session.commit()
-
-def update_fail_times(session, FailInfo):
- NewBuildJobs = session.query(BuildJobs).filter_by(BuildJobId = FailInfo.BuildJobId).one()
- NewBuildJobs.TimeStamp = datetime.datetime.utcnow()
- session.commit()
-
-def get_fail_times(session, build_dict):
- try:
- FailInfo = session.query(BuildJobsRedo).filter_by(BuildJobId = build_dict['build_job_id']).filter_by(FailType = build_dict['type_fail']).one()
- except NoResultFound as e:
- return False
- return True
-
-def add_fail_times(session, fail_querue_dict):
- NewBuildJobsRedo = BuildJobsRedo(BuildJobId = fail_querue_dict['build_job_id'], FailType = fail_querue_dict['fail_type'], FailTimes = fail_querue_dict['fail_times'])
- session.add(NewBuildJobsRedo)
- session.commit()
-
-def check_host_updatedb(session):
- jobs = False
- try:
- JobsInfo = session.query(Jobs).filter_by(Status = 'Done').filter_by(JobType = 'esync').one()
- except NoResultFound as e:
- jobs = True
- try:
- JobsInfo = session.query(Jobs).filter_by(Status = 'Done').filter_by(JobType = 'updatedb').one()
- except NoResultFound as e:
- jobs = True
- return jobs
-
-# Host Functions
-def update_repo_db(session, repo_list):
- for repo in repo_list:
- if not get_repo_info(session, repo):
- session.add(Repos(Repo = repo))
- session.commit()
-
-def update_categories_db(session, category, categories_metadataDict):
- CategoryInfo = get_category_info(session, category)
- if not CategoryInfo:
- session.add(Categories(Category = category))
- session.commit()
- CategoryInfo = get_category_info(session, category)
- try:
- CategoriesMetadataInfo = session.query(CategoriesMetadata).filter_by(CategoryId = CategoryInfo.CategoryId).one()
- except NoResultFound as e:
- NewCategoriesMetadata = CategoriesMetadata(CategoryId = CategoryInfo.CategoryId, Checksum = categories_metadataDict['metadata_xml_checksum'], Descriptions = categories_metadataDict['metadata_xml_descriptions'])
- session.add(NewCategoriesMetadata)
- session.commit()
- return
- if CategoriesMetadataInfo.Checksum != categories_metadataDict['metadata_xml_checksum']:
- CategoriesMetadataInfo.Checksum = categories_metadataDict['metadata_xml_checksum']
- CategoriesMetadataInfo.Descriptions = categories_metadataDict['metadata_xml_descriptions']
- session.commit()
-
-def get_keyword_id(session, keyword):
- try:
- KeywordsInfo = session.query(Keywords).filter_by(Keyword = keyword).one()
- except NoResultFound as e:
- return None
- return KeywordsInfo.KeywordId
-
-def add_new_ebuild_metadata_sql(session, ebuild_id, keywords, restrictions, iuse_list):
- for restriction in restrictions:
- if restriction in ["!"]:
- restriction = restriction[1:]
- if restriction in ["?"]:
- restriction = restriction[:1]
- if restriction != '(' or restriction != ')':
- try:
- RestrictionInfo = session.query(Restrictions).filter_by(Restriction = restriction).one()
- except NoResultFound as e:
- session.add(Restrictions(Restriction = restriction))
- session.commit()
- RestrictionInfo = session.query(Restrictions).filter_by(Restriction = restriction).one()
- session.add(EbuildsRestrictions(EbuildId = ebuild_id, RestrictionId = RestrictionInfo.RestrictionId))
- session.commit()
- for iuse in iuse_list:
- status = False
- if iuse[0] in ["+"]:
- iuse = iuse[1:]
- status = True
- elif iuse[0] in ["-"]:
- iuse = iuse[1:]
- use_id = get_use_id(session, iuse)
- if use_id is None:
- session.add(Uses(Flag = iuse))
- session.commit()
- use_id = get_use_id(session, iuse)
- session.add(EbuildsIUse(EbuildId = ebuild_id, UseId = use_id, Status = status))
- session.commit()
- for keyword in keywords:
- status = 'Stable'
- if keyword[0] in ["~"]:
- keyword = keyword[1:]
- status = 'Unstable'
- elif keyword[0] in ["-"]:
- keyword = keyword[1:]
- status = 'Negative'
- keyword_id = get_keyword_id(session, keyword)
- if keyword_id is None:
- session.add(Keywords(Keyword = keyword))
- session.commit()
- keyword_id = get_keyword_id(session, keyword)
- session.add(EbuildsKeywords(EbuildId = ebuild_id, KeywordId = keyword_id, Status = status))
- session.commit()
-
-def add_new_ebuild_sql(session, packageDict):
- ebuild_id_list = []
- for k, v in packageDict.items():
- session.add(Ebuilds(PackageId = v['package_id'], Version = v['ebuild_version'], Checksum = v['checksum'], Active = True))
- session.flush()
- try:
- EbuildInfo = session.query(Ebuilds).filter_by(Version = v['ebuild_version']).filter_by(Checksum = v['checksum']).\
- filter_by(PackageId = v['package_id']).filter_by(Active = True).one()
- except (MultipleResultsFound) as e:
- for x in session.query(Ebuilds).filter_by(Version = v['ebuild_version']).filter_by(Checksum = v['checksum']).\
- filter_by(PackageId = v['package_id']).filter_by(Active = True).all():
- x.Checksum = 0
- x.Active = False
- session.commit()
- try:
- EbuildInfo = session.query(Ebuilds).filter_by(Version = v['ebuild_version']).filter_by(Checksum = v['checksum']).\
- filter_by(PackageId = v['package_id']).filter_by(Active = True).one()
- except (MultipleResultsFound) as e:
- # FIXME
- sys.exit()
- session.add(EbuildsMetadata(EbuildId = EbuildInfo.EbuildId, New = v['new'], Updated = v['updated'], Commit = v['git_commit'], \
- CommitMsg = v['git_commit_msg'], Descriptions = v['ebuild_version_descriptions_tree'], Slot = v['ebuild_version_metadata_tree'][2], \
- Homepage = v['ebuild_version_metadata_tree'][5], License = v['ebuild_version_metadata_tree'][6]))
- session.commit()
- ebuild_id_list.append(EbuildInfo.EbuildId)
- restrictions = []
- keywords = []
- iuse = []
- for i in v['ebuild_version_metadata_tree'][4].split():
- restrictions.append(i)
- for i in v['ebuild_version_metadata_tree'][8].split():
- keywords.append(i)
- for i in v['ebuild_version_metadata_tree'][10].split():
- iuse.append(i)
- add_new_ebuild_metadata_sql(session, EbuildInfo.EbuildId, keywords, restrictions, iuse)
- return ebuild_id_list
-
-def get_ebuild_id_list(session, package_id):
- ebuild_id_list = []
- for EbuildInfo in session.query(Ebuilds).filter_by(PackageId = package_id).filter_by(Active = True).all():
- ebuild_id_list.append(EbuildInfo.EbuildId)
- return ebuild_id_list
-
-def get_build_job_all(session, ebuild_id):
- return session.query(BuildJobs).filter_by(EbuildId = ebuild_id).all()
-
-def add_old_ebuild(session, old_ebuild_list):
- for ebuild_id in old_ebuild_list:
- EbuildInfo = session.query(Ebuilds).filter_by(EbuildId = ebuild_id).one()
- EbuildInfo.Active = False
- session.commit()
- build_job_id_list = get_build_job_all(session, ebuild_id)
- if build_job_id_list != []:
- for build_job in build_job_id_list:
- del_old_build_jobs(session, build_job.BuildJobId)
-
-def add_old_package(session, package_id):
- PackagesInfo = session.query(Packages).filter_by(PackageId = package_id).one()
- PackagesInfo.Active = False
- session.commit()
-
-def add_new_package_sql(session, cp, repo):
- element = cp.split('/')
- categories = element[0]
- package = element[1]
- RepoInfo =get_repo_info(session, repo)
- repo_id = RepoInfo.RepoId
- CategoriesInfo = get_category_info(session, categories)
- category_id = CategoriesInfo.CategoryId
- session.add(Packages(Package = package, CategoryId = category_id, RepoId = repo_id, Active = True))
- session.commit()
- PackageInfo = get_package_info(session, categories, package, repo)
- return PackageInfo.PackageId
-
-def get_package_metadata_sql(session, package_id):
- try:
- PackagesMetadataInfo = session.query(PackagesMetadata).filter_by(PackageId = package_id).one()
- except NoResultFound as e:
- return False
- return PackagesMetadataInfo
-
-def update_email_info(session, email):
- try:
- EmailInfo = session.query(Emails).filter_by(Email = email).one()
- except NoResultFound as e:
- session.add(Emails(Email = email))
- session.commit()
- EmailInfo = session.query(Emails).filter_by(Email = email).one()
- return EmailInfo
-
-def update_package_email_info(session, email_id, package_id):
- try:
- PackagesEmailInfo = session.query(PackagesEmails).filter_by(EmailId = email_id).filter_by(PackageId = package_id).one()
- except NoResultFound as e:
- session.add(PackagesEmails(EmailId = email_id, PackageId = package_id))
- session.commit()
- PackagesEmailInfo = session.query(PackagesEmails).filter_by(EmailId = email_id).filter_by(PackageId = package_id).one()
- return PackagesEmailInfo
-
-def update_package_metadata(session, package_metadataDict):
- for k, v in package_metadataDict.items():
- try:
- PackagesMetadataInfo = session.query(PackagesMetadata).filter_by(PackageId = k).one()
- except NoResultFound as e:
- session.add(PackagesMetadata(PackageId = k, Gitlog = v['git_changlog'], Descriptions = v['metadata_xml_descriptions'], New = v['new']))
- session.commit()
- else:
- PackagesMetadataInfo.Gitlog = v['git_changlog']
- PackagesMetadataInfo.Descriptions = v['metadata_xml_descriptions']
- session.commit()
- if v['metadata_xml_email']:
- for email in v['metadata_xml_email']:
- EmailInfo = update_email_info(session, email)
- PackagesEmailInfo = update_package_email_info(session, EmailInfo.EmailId, k)
-
-def get_package_info_from_package_id(session, package_id):
- PackageInfo = session.query(Packages).filter_by(PackageId = package_id).one()
- CategoryInfo = session.query(Categories).filter_by(CategoryId = PackageInfo.CategoryId).one()
- RepoInfo = session.query(Repos).filter_by(RepoId = PackageInfo.RepoId).one()
- return PackageInfo, CategoryInfo, RepoInfo
-
-def add_new_build_job(session, ebuild_id, setup_id, use_flagsDict, config_id):
- NewBuildJobs = BuildJobs(EbuildId = ebuild_id, SetupId = setup_id, ConfigId = config_id, Status = 'Waiting', BuildNow = False, RemoveBin = True, New = True)
- session.add(NewBuildJobs)
- session.flush()
- build_job_id = NewBuildJobs.BuildJobId
- session.commit()
- for k, v in use_flagsDict.items():
- use_id = get_use_id(session, k)
- session.add(BuildJobsUse(BuildJobId = build_job_id, UseId = use_id, Status = v))
- session.commit()
-
-def get_ebuild_checksums(session, package_id, ebuild_version):
- ebuild_checksum_list = []
- try:
- EbuildInfo = session.query(Ebuilds).filter_by(PackageId = package_id).filter_by(Version = ebuild_version).filter_by(Active = True).one()
- except NoResultFound as e:
- return None, False
- except MultipleResultsFound as e:
- EbuildInfo2 = session.query(Ebuilds).filter_by(PackageId = package_id).filter_by(Version = ebuild_version).filter_by(Active = True).all()
- for Ebuild in EbuildInfo2:
- ebuild_checksum_list.append(Ebuild.Checksum)
- return ebuild_checksum_list, True
- return EbuildInfo.Checksum, False
-
-def get_ebuild_id_db(session, checksum, package_id, ebuild_version):
- try:
- EbuildInfos = session.query(Ebuilds).filter_by(PackageId = package_id).filter_by(Checksum = checksum).filter_by(Version = ebuild_version).filter_by(Active = True).one()
- except NoResultFound as e:
- return None, True
- except MultipleResultsFound as e:
- EbuildInfos = session.query(Ebuilds).filter_by(PackageId = package_id).filter_by(Checksum = checksum).filter_by(Version = ebuild_version).filter_by(Active = True).all()
- ebuilds_id = []
- for EbuildInfo in EbuildInfos:
- ebuilds_id.append(EbuildInfo.EbuildId)
- return ebuilds_id, True
- return EbuildInfos.EbuildId, False
-
-def get_ebuild_restrictions(session, ebuild_id):
- restrictions = []
- EbuildsRestrictionsInfos = session.query(EbuildsRestrictions).filter_by(EbuildId = ebuild_id).all()
- if EbuildsRestrictionsInfos == []:
- return False
- for EbuildsRestrictionsInfo in EbuildsRestrictionsInfos:
- RestrictionsInfo = session.query(Restrictions).filter_by(RestrictionId = EbuildsRestrictionsInfo.RestrictionId).one()
- restrictions.append(RestrictionsInfo.Restriction)
- return restrictions
-
-def add_repoman_log(session, package_id, repoman_log, repoman_hash):
- try:
- PackagesRepomanInfo = session.query(PackagesRepoman).filter_by(PackageId = package_id).one()
- except NoResultFound as e:
- session.add(PackagesRepoman(PackageId = package_id, RepomanText = repoman_log, RepomanHash = repoman_hash))
- session.commit()
- else:
- if PackagesRepomanInfo.RepomanHash != repoman_hash:
- PackagesRepomanInfo.RepomanHash = repoman_hash
- PackagesRepomanInfo.RepomanText = repoman_log
- session.commit()
-
-def get_category_list_info(session):
- return session.query(Categories).all()
-
-def get_package_list_info(session, category_id):
- return session.query(Packages).filter_by(CategoryId = category_id).all()
-
-def get_ebuild_list_info(session, package_id):
- return session.query(Ebuilds).filter_by(PackageId = package_id).all()
-
-def del_old_ebuild(session, ebuild_id):
- session.query(EbuildsRestrictions).filter(EbuildsRestrictions.EbuildId == ebuild_id).delete()
- session.query(EbuildsIUse).filter(EbuildsIUse.EbuildId == ebuild_id).delete()
- session.query(EbuildsKeywords).filter(EbuildsKeywords.EbuildId == ebuild_id).delete()
- session.query(EbuildsMetadata).filter(EbuildsMetadata.EbuildId == ebuild_id).delete()
- session.query(Ebuilds).filter(Ebuilds.EbuildId == ebuild_id).delete()
- session.commit()
-
-def del_old_package(session, package_id):
- session.query(PackagesRepoman).filter(PackagesRepoman.PackageId == package_id).delete()
- session.query(PackagesEmails).filter(PackagesEmails.PackageId== package_id).delete()
- session.query(PackagesMetadata).filter(PackagesMetadata.PackageId == package_id).delete()
- session.query(Packages).filter(Packages.PackageId == package_id).delete()
- session.commit()
-
-def add_old_category(session, category_id):
- CategorysInfo = session.query(Categories).filter_by(CategoryId = category_id).one()
- CategorysInfo.Active = False
- session.commit()
-
-def reset_new_updated(session):
- try:
- PMInfo = session.query(PackagesMetadata).filter(PackagesMetadata.New == True).all()
- except NoResultFound as e:
- pass
- else:
- for x in PMInfo:
- x.New = False
- session.flush()
- try:
- EMInfo = session.query(EbuildsMetadata).filter(EbuildsMetadata.New == True).all()
- except NoResultFound as e:
- pass
- else:
- for x in EMInfo:
- x.New = False
- session.flush()
- try:
- BLInfo = session.query(BuildLogs).filter(BuildLogs.New == True).all()
- except NoResultFound as e:
- pass
- else:
- for x in BLInfo:
- x.New = False
- session.flush()
- try:
- BJInfo = session.query(BuildJobs).filter(BuildJobs.New == True).all()
- except NoResultFound as e:
- pass
- else:
- for x in BJInfo:
- x.New = False
- session.flush()
- try:
- EMInfo = session.query(EbuildsMetadata).filter(EbuildsMetadata.Updated == True).all()
- except NoResultFound as e:
- pass
- else:
- for x in EMInfo:
- x.Updated = False
- session.flush()
- session.commit()
diff --git a/pym/tbc/sync.py b/pym/tbc/sync.py
deleted file mode 100644
index 8488c50..0000000
--- a/pym/tbc/sync.py
+++ /dev/null
@@ -1,122 +0,0 @@
-# Copyright 1998-2015 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-from __future__ import print_function
-import portage
-import os
-import errno
-import sys
-import time
-import re
-import git
-
-from tbc.sqlquerys import get_config_id_fqdn, add_logs, get_config_all_info, \
- get_configmetadata_info, get_config_info, get_setup_info, get_job_status_waiting_on_guest, \
- update_job_list
-from tbc.readconf import read_config_settings
-from tbc.log import write_log
-
-def git_repos_list(myportdb):
- repo_trees_list = myportdb.porttrees
- repo_dir_list = []
- for repo_dir in repo_trees_list:
- repo_dir_list.append(repo_dir)
- return repo_dir_list
-
-def git_fetch(repo):
- repouptodate = True
- remote = git.remote.Remote(repo, 'origin')
- info_list = remote.fetch()
- local_commit = repo.commit()
- remote_commit = info_list[0].commit
- if local_commit.hexsha != remote_commit.hexsha:
- repouptodate = False
- return info_list, repouptodate
-
-def git_merge(repo, info):
- repo.git.merge(info.commit)
-
-def git_sync_main(session):
- tbc_settings = read_config_settings()
- config_id = get_config_id_fqdn(session, tbc_settings['hostname'])
- ConfigsMetaDataInfo = get_configmetadata_info(session, config_id)
- ConfigInfo = get_config_info(session, config_id)
- SetupInfo = get_setup_info(session, ConfigInfo.SetupId)
- host_config = ConfigInfo.Hostname +"/" + SetupInfo.Setup
- default_config_root = ConfigsMetaDataInfo.RepoPath + "/" + host_config + "/"
- mysettings = portage.config(config_root = default_config_root)
- myportdb = portage.portdbapi(mysettings=mysettings)
- GuestBusy = True
- log_msg = "Waiting for Guest to be idel"
- write_log(session, log_msg, "info", config_id, 'sync.git_sync_main')
- guestid_list = []
- # check if the guests is idel
- for config in get_config_all_info(session):
- if not config.Host:
- guestid_list.append(config.ConfigId)
- while GuestBusy:
- Status_list = []
- for guest_id in guestid_list:
- ConfigMetadataGuest = get_configmetadata_info(session, guest_id)
- Status_list.append(ConfigMetadataGuest.Status)
- write_log(session, 'Guset status: %s' % (Status_list,), "debug", config_id, 'sync.git_sync_main')
- if not 'Runing' in Status_list:
- GuestBusy = False
- else:
- time.sleep(60)
-
- job_id = get_job_status_waiting_on_guest(session)
- if not job_id is None:
- update_job_list(session, 'Runing', job_id)
-
- # check git diffs witch get updated and pass that to a dict
- # fetch and merge the repo
- repo_cp_dict = {}
- search_list = [ '^metadata', '^eclass', '^licenses', '^profiles', '^scripts', '^skel.', '^header.txt']
- for repo_dir in git_repos_list(myportdb):
- reponame = myportdb.getRepositoryName(repo_dir)
- repo = git.Repo(repo_dir)
- log_msg = "Checking repo %s" % (reponame)
- write_log(session, log_msg, "info", config_id, 'sync.git_sync_main')
- info_list, repouptodate = git_fetch(repo)
- if not repouptodate:
- cp_list = []
- attr = {}
- # We check for dir changes and add the package to a list
- repo_diff = repo.git.diff('origin', '--name-only')
- write_log(session, 'Git dir diff:\n%s' % (repo_diff,), "debug", config_id, 'sync.git_sync_main')
- for diff_line in repo_diff.splitlines():
- find_search = True
- for search_line in search_list:
- if re.search(search_line, diff_line):
- find_search = False
- if find_search:
- splited_diff_line = re.split('/', diff_line)
- cp = splited_diff_line[0] + '/' + splited_diff_line[1]
- if not cp in cp_list:
- cp_list.append(cp)
- attr['cp_list'] = cp_list
- write_log(session, 'Git CP Diff: %s' % (cp_list,), "debug", config_id, 'sync.git_sync_main')
- repo_cp_dict[reponame] = attr
- git_merge(repo, info_list[0])
- else:
- log_msg = "Repo %s is up to date" % (reponame)
- write_log(session, log_msg, "info", config_id, 'sync.git_sync_main')
- log_msg = "Checking repo %s Done" % (reponame)
- write_log(session, log_msg, "info", config_id, 'sync.git_sync_main')
-
- log_msg = "Repo sync ... Done."
- write_log(session, log_msg, "info", config_id, 'sync.git_sync_main')
- write_log(session, 'Updated Packages: %s' % (repo_cp_dict,), "debug", config_id, 'sync.git_sync_main')
- return repo_cp_dict
-
-def git_pull(session, repo_dir, config_id):
- log_msg = "Git pull"
- write_log(session, log_msg, "info", config_id, 'sync.git_pull')
- repo = git.Repo(repo_dir)
- info_list, repouptodate = git_fetch(repo)
- if not repouptodate:
- git_merge(repo, info_list[0])
- log_msg = "Git pull ... Done"
- write_log(session, log_msg, "info", config_id, 'sync.git_pull')
- return True
diff --git a/pym/tbc/text.py b/pym/tbc/text.py
deleted file mode 100644
index c78c432..0000000
--- a/pym/tbc/text.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright 1998-2015 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-from __future__ import print_function
-import sys
-import re
-import os
-import errno
-from portage.util import grablines
-
-def get_file_text(filename):
- # Return the filename contents
- try:
- textfile = open(filename, encoding='utf-8')
- except:
- return "No file", filename
- text = ""
- for line in textfile:
- text += line
- textfile.close()
- return text
-
-def get_ebuild_cvs_revision(filename):
- """Return the ebuild contents"""
- try:
- ebuildfile = open(filename, encoding='utf-8')
- except:
- return "No Ebuild file there"
- text = ""
- dataLines = ebuildfile.readlines()
- for i in dataLines:
- text = text + i + " "
- line2 = dataLines[2]
- field = line2.split(" ")
- ebuildfile.close()
- try:
- cvs_revision = field[3]
- except:
- cvs_revision = ''
- return cvs_revision
-
-def get_log_text_dict(filename):
- """Return the log contents as a dict"""
- logfile_dict = {}
- index = 1
- for text_line in grablines(filename):
- logfile_dict[index] = text_line
- index = index + 1
- return logfile_dict, index - 1
diff --git a/pym/tbc/updatedb.py b/pym/tbc/updatedb.py
deleted file mode 100644
index d32f0ac..0000000
--- a/pym/tbc/updatedb.py
+++ /dev/null
@@ -1,173 +0,0 @@
-# Copyright 1998-2015 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-from __future__ import print_function
-import sys
-import os
-import multiprocessing
-import time
-import re
-import portage
-from portage.xml.metadata import MetaDataXML
-from portage.checksum import perform_checksum
-from sqlalchemy.orm import scoped_session, sessionmaker
-from tbc.ConnectionManager import NewConnection
-from tbc.sqlquerys import get_package_info, update_repo_db, \
- update_categories_db, get_configmetadata_info, get_config_all_info, add_new_build_job, \
- get_config_info, get_setup_info, get_job_status_waiting_on_guest, update_job_list, \
- reset_new_updated
-from tbc.check_setup import check_make_conf
-from tbc.package import tbc_package
-# Get the options from the config file tbc.conf
-from tbc.readconf import read_config_settings
-from tbc.log import write_log
-
-def init_portage_settings(session, config_id):
- # check config setup
- check_make_conf(session, config_id)
- log_msg = "Check configs done"
- write_log(session, log_msg, "info", config_id, 'updatedb.init_portage_settings')
-
- # setup default root
- ConfigsMetaDataInfo = get_configmetadata_info(session, config_id)
- ConfigInfo = get_config_info(session, config_id)
- SetupInfo = get_setup_info(session, ConfigInfo.SetupId)
- host_config = ConfigInfo.Hostname +"/" + SetupInfo.Setup
- default_config_root = ConfigsMetaDataInfo.RepoPath + "/" + host_config + "/"
-
- # Set config_root (PORTAGE_CONFIGROOT) to default_config_root
- mysettings = portage.config(config_root = default_config_root)
- log_msg = "Setting default config to: %s" % (host_config,)
- write_log(session, log_msg, "info", config_id, 'updatedb.init_portage_settings')
- return mysettings
-
-def get_categories_metadataDict(pkgdir):
- # Make categories_metadataDict
- categories_metadataDict = {}
- pkg_md = MetaDataXML(pkgdir + "/metadata.xml", None)
- metadata_xml_descriptions_tree = re.sub('\t', '', pkg_md.descriptions()[0])
- categories_metadataDict['metadata_xml_descriptions'] = re.sub('\n', '', metadata_xml_descriptions_tree)
- categories_metadataDict['metadata_xml_checksum'] = perform_checksum(pkgdir + "/metadata.xml", "SHA256")[0]
- return categories_metadataDict
-
-def update_cpv_db_pool(mysettings, myportdb, cp, repo, tbc_settings, config_id):
- session_factory = sessionmaker(bind=NewConnection(tbc_settings))
- Session = scoped_session(session_factory)
- session2 = Session()
- init_package = tbc_package(session2, mysettings, myportdb, config_id)
-
- # split the cp to categories and package
- element = cp.split('/')
- categories = element[0]
- package = element[1]
-
- # update the categories table
- repodir = myportdb.getRepositoryPath('gentoo')
- pkgdir = repodir + "/" + categories
- categories_metadataDict = get_categories_metadataDict(pkgdir)
- update_categories_db(session2, categories, categories_metadataDict)
-
- # Check if we have the cp in the package table
- PackagesInfo = get_package_info(session2, categories, package, repo)
- if PackagesInfo:
- # Update the packages with ebuilds
- init_package.update_package_db(PackagesInfo.PackageId)
- else:
- # Add new package with ebuilds
- init_package.add_new_package_db(cp, repo)
- session2.close
- Session.remove()
-
-def update_cpv_db(session, repo_cp_dict, config_id, tbc_settings):
- GuestBusy = True
- log_msg = "Waiting for Guest to be idel"
- write_log(session, log_msg, "info", config_id, 'updatedb.update_cpv_db')
- guestid_list = []
- for config in get_config_all_info(session):
- if not config.Host:
- guestid_list.append(config.ConfigId)
- while GuestBusy:
- Status_list = []
- for guest_id in guestid_list:
- ConfigMetadata = get_configmetadata_info(session, guest_id)
- Status_list.append(ConfigMetadata.Status)
- if not 'Runing' in Status_list:
- break
- time.sleep(30)
-
- job_id = get_job_status_waiting_on_guest(session)
- if not job_id is None:
- update_job_list(session, 'Runing', job_id)
-
- log_msg = "Checking categories, package, ebuilds"
- write_log(session, log_msg, "info", config_id, 'updatedb.update_cpv_db')
- new_build_jobs_list = []
-
- # Setup settings, portdb and pool
- mysettings = init_portage_settings(session, config_id)
- myportdb = portage.portdbapi(mysettings=mysettings)
-
- # Use all cores when multiprocessing
- #pool_cores = multiprocessing.cpu_count()
- #pool = multiprocessing.Pool(processes = pool_cores)
-
- # Get packages and repo
- if repo_cp_dict is None:
- repo_list = []
- repos_trees_list = []
-
- # Get the repos and update the repos db
- repo_list = myportdb.getRepositories()
- update_repo_db(session, repo_list)
-
- # Get the rootdirs for the repos
- repo_trees_list = myportdb.porttrees
- for repo_dir in repo_trees_list:
- repo = myportdb.getRepositoryName(repo_dir)
- repo_dir_list = []
- repo_dir_list.append(repo_dir)
-
- # Get the package list from the repo
- package_list_tree = myportdb.cp_all(trees=repo_dir_list)
-
- # Run the update package for all package in the list and in a multiprocessing pool
- for cp in sorted(package_list_tree):
- # pool.apply_async(update_cpv_db_pool, (mysettings, myportdb, cp, repo, tbc_settings, config_id,))
- # use this when debuging
- update_cpv_db_pool(mysettings, myportdb, cp, repo, tbc_settings, config_id)
- else:
- # Update needed repos and packages in the dict
- for repo, v in repo_cp_dict.items():
- # Get the repos and update the repos db
- repo_list = []
- repo_list.append(repo)
- update_repo_db(session, repo_list)
-
- # Run the update package for all package in the list and in a multiprocessing pool
- for cp in v['cp_list']:
- # pool.apply_async(update_cpv_db_pool, (mysettings, myportdb, cp, repo, tbc_settings, config_id,))
- # use this when debuging
- update_cpv_db_pool(mysettings, myportdb, cp, repo, tbc_settings, config_id)
-
-
- #close and join the multiprocessing pools
- # pool.close()
- # pool.join()
- log_msg = "Checking categories, package and ebuilds ... done"
- write_log(session, log_msg, "info", config_id, 'updatedb.update_cpv_db')
-
-def update_db_main(session, repo_cp_dict, config_id):
- # Main
- reset_new_updated(session)
- if repo_cp_dict == {}:
- return True
- # Logging
- tbc_settings = read_config_settings()
- log_msg = "Update db started."
- write_log(session, log_msg, "info", config_id, 'updatedb.update_db_main')
-
- # Update the cpv db
- update_cpv_db(session, repo_cp_dict, config_id, tbc_settings)
- log_msg = "Update db ... Done."
- write_log(session, log_msg, "info", config_id, 'updatedb.update_db_main')
- return True
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..34d697a
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,18 @@
+pbr!=2.1.0,>=2.0.0 # Apache-2.0
+SQLAlchemy>=1.2.19 # MIT
+keystonemiddleware>=4.20.0 # Apache-2.0
+greenlet>=0.4.10,!=0.4.14 # MIT
+keystoneauth1>=3.16.0 # Apache-2.0
+oslo.config>=6.1.0 # Apache-2.0
+oslo.context>=2.21.0 # Apache-2.0
+oslo.log>=3.36.0 # Apache-2.0
+oslo.serialization!=2.19.1,>=2.21.1 # Apache-2.0
+oslo.utils>=3.40.2 # Apache-2.0
+oslo.db>=4.44.0 # Apache-2.0
+oslo.messaging>=10.3.0 # Apache-2.0
+oslo.policy>=2.3.0 # Apache-2.0
+oslo.i18n>=3.15.3 # Apache-2.0
+oslo.service>=1.40.1 # Apache-2.0
+oslo.middleware>=3.31.0 # Apache-2.0
+oslo.versionedobjects>=1.35.0 # Apache-2.0
+openstacksdk>=0.35.0 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..54a9a02
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,35 @@
+[metadata]
+name = gosbs
+summary = Gentoo OpenStack Build System
+description-file =
+ README.rst
+author = Gentoo tinderbox clyster project
+author-email = tinderbox-cluster@gentoo.org
+home-page = https://wiki.gentoo.org/wiki/Project:Tinderbox-cluster
+python-requires = >=3.6
+classifier =
+ Environment :: OpenStack
+ Intended Audience :: Information Technology
+ Intended Audience :: System Administrators
+ License :: OSI Approved :: Apache Software License
+ License :: OSI Approved :: GNU General Public License v2 (GPLv2)
+ Operating System :: POSIX :: Linux
+ Programming Language :: Python
+ Programming Language :: Python :: 3
+ Programming Language :: Python :: 3.6
+ Programming Language :: Python :: 3.7
+ Programming Language :: Python :: 3.8
+ Programming Language :: Python :: 3 :: Only
+ Programming Language :: Python :: Implementation :: CPython
+
+[extras]
+osprofiler =
+ osprofiler>=1.4.0 # Apache-2.0
+
+[files]
+packages =
+ gosbs
+
+[entry_points]
+console_scripts =
+ gosbs-scheduler = gosbs.cmd.scheduler:main
diff --git a/setup.py b/setup.py
index 6a96128..566d844 100644
--- a/setup.py
+++ b/setup.py
@@ -1,27 +1,29 @@
-import os
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
+import setuptools
+
+# In python < 2.7.4, a lazy loading of package `pbr` will break
+# setuptools if some other modules registered functions in `atexit`.
+# solution from: http://bugs.python.org/issue15881#msg170215
try:
- from setuptools import setup
+ import multiprocessing # noqa
except ImportError:
- raise
- from ez_setup import use_setuptools
- use_setuptools()
- from setuptools import setup
-
-def find_packages():
- for dirpath, dirnames, filenames in os.walk('pym'):
- if '__init__.py' in filenames:
- yield os.path.relpath(dirpath, 'pym')
+ pass
-setup(
- version = os.path.split(os.path.abspath(__file__))[-2].split('-')[-1],
- packages = list(find_packages()),
- package_dir = {'': 'pym'},
- name="tbc",
- author='Zorry',
- author_email='tinderbox-cluster@gentoo.org',
- url='https://anongit.gentoo.org/git/proj/tinderbox-cluster.git',
- description='Tinderbox cluster',
- platforms=["any"],
- license="GPL2",
-)
+setuptools.setup(
+ setup_requires=['pbr>=2.0.0'],
+ pbr=True)
diff --git a/sql/data_dump.sql b/sql/data_dump.sql
deleted file mode 100644
index 46102b5..0000000
--- a/sql/data_dump.sql
+++ /dev/null
@@ -1,193 +0,0 @@
--- phpMyAdmin SQL Dump
--- version 4.2.13
--- http://www.phpmyadmin.net
---
--- Host: localhost
--- Generation Time: Feb 13, 2016 at 02:37 PM
--- Server version: 10.0.22-MariaDB-log
--- PHP Version: 5.6.16-pl0-gentoo
-
-SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
-SET time_zone = "+00:00";
-
-
-/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
-/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
-/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
-/*!40101 SET NAMES utf8 */;
-
---
--- Database: `tbc`
---
-
---
--- Dumping data for table `configs`
---
-
-INSERT INTO `configs` (`config_id`, `hostname`, `setup_id`, `default_config`) VALUES
-(1, 'sandra.ume.nu', 1, 1),
-(2, 'virtual1.ume.nu', 2, 0),
-(3, 'virtual2.ume.nu', 3, 0),
-(4, 'virtual3.ume.nu', 3, 0),
-(5, 'virtual4.ume.nu', 3, 0),
-(6, 'virtual5.ume.nu', 3, 0);
-
---
--- Dumping data for table `configs_emerge_options`
---
-
-INSERT INTO `configs_emerge_options` (`id`, `config_id`, `eoption_id`) VALUES
-(1, 2, 1),
-(2, 2, 2),
-(3, 2, 5),
-(4, 2, 6),
-(5, 3, 1),
-(6, 3, 2),
-(7, 3, 5),
-(8, 3, 6),
-(9, 4, 1),
-(10, 4, 2),
-(11, 4, 5),
-(12, 4, 6),
-(13, 5, 1),
-(14, 5, 2),
-(15, 5, 5),
-(16, 5, 6),
-(17, 6, 1),
-(18, 6, 2),
-(19, 6, 5),
-(20, 6, 6);
-
---
--- Dumping data for table `configs_metadata`
---
-
-INSERT INTO `configs_metadata` (`id`, `config_id`, `keyword_id`, `make_conf_text`, `checksum`, `configsync`, `active`, `config_error_text`, `updateing`, `status`, `auto`, `repo_path`, `time_stamp`) VALUES
-(1, 1, 1, '# This is for the base config\nCHOST="x86_64-pc-linux-gnu"\nACCEPT_KEYWORDS=""\nARCH="amd64"\nFEATURES="-metadata-transfer -news distlocks"\nACCEPT_LICENSE="*"\nPORTAGE_TMPDIR=/var/tmp\nDISTDIR=/var/cache/portage/distfiles\nPORT_LOGDIR="/var/log/portage"\nGENTOO_MIRRORS="ftp://ftp.sunet.se/pub/Linux/distributions/gentoo http://distfiles.gentoo.org http://www.ibiblio.org/pub/Linux/distributions/gentoo"\nPORTAGE_TMPFS="/dev/shm"\nPORTAGE_ELOG_CLASSES=""\nPORTAGE_ELOG_SYSTEM=""\nPORTDIR_OVERLAY=""\nsource /var/cache/layman/make.conf\n', 'd29a21ae48f047f5036cb17c278ad96a8c7818bbcda29b2e766a8cc6a34a358f', 0, 1, '', 0, 'Waiting', 1, '/var/cache/gobs/tinderboxs_configs', '2015-07-17 22:54:09'),
-(2, 2, 1, 'CFLAGS="-O2 -pipe -march=native"\nCXXFLAGS="-O2 -pipe -march=native"\nCHOST="x86_64-pc-linux-gnu"\nUSE="qt3support xattr semantic-desktop"\nACCEPT_KEYWORDS="~amd64 amd64"\nACCEPT_LICENSE="*"\nPORTAGE_TMPDIR=/var/tmp\nDISTDIR="/var/cache/portage/distfiles"\nPORT_LOGDIR="/var/cache/portage/logs/virtual1.ume.nu/amd64_hardened_unstable"\nPKGDIR="/var/cache/portage/packages/virtual1.ume.nu/amd64_hardened_unstable"\nGENTOO_MIRRORS="ftp://mirror.mdfnet.se/gentoo http://distfiles.gentoo.org"\nEMERGE_DEFAULT_OPTS="-v --binpkg-respect-use=y --rebuild-if-new-rev=y --rebuilt-binaries=y --autounmask=y --autounmask-write=y --jobs=3 --load-average=5.0"\nMAKEOPTS="-j6"\nAUTOCLEAN="yes"\nNOCOLOR="true"\nPORTAGE_TMPFS="/dev/shm"\nFEATURES="sandbox distlocks parallel-fetch strict -news test test-fail-continue"\nPORTAGE_ELOG_CLASSES=""\nPORTAGE_ELOG_SYSTEM="save"\nPORTDIR_OVERLAY="/usr/local/portage"\nLINGUAS="en"\nINPUT_DEVICES="keyboard mouse synaptics evdev"\nVIDEO_CARDS="radeon"\nALSA_CARDS="hda-intel intel8x0
-intel8x0m"\nALSA_PCM_PLUGINS="adpcm alaw asym copy dmix dshare dsnoop empty extplug file hooks iec958 ioplug ladspa lfloat linear meter mmap_emul mulaw multi null plug rate route share shm softvol"\nCONFIG_PROTECT_MASK="/etc/portage/package.use/99_autounmask"\n\n# for layman stuff\nsource /var/cache/layman/make.conf\n', '91820213d60929995132060b1d917740ff3b23af2a0745fde592df26ef58b5d7', 0, 1, '', 0, 'Stoped', 0, '', '2015-03-23 22:21:39'),
-(3, 3, 1, 'CFLAGS="-O2 -pipe -march=native -fno-diagnostics-color"\nCXXFLAGS="-O2 -pipe -march=native -fno-diagnostics-color"\nCHOST="x86_64-pc-linux-gnu"\nUSE="X qt3support"\nACCEPT_KEYWORDS="~amd64 amd64"\nABI_X86="32 64"\nCPU_FLAGS_X86=""\nACCEPT_LICENSE="*"\nPYTHON_TARGETS="python2_7 python3_4 python3_5"\nRUBY_TARGETS="ruby20 ruby21 ruby22"\nPORTAGE_TMPDIR=/var/tmp\nDISTDIR="/var/cache/portage/distfiles"\nGENTOO_MIRRORS="ftp://mirror.mdfnet.se/gentoo ftp://trumpetti.atm.tut.fi/gentoo/ http://distfiles.gentoo.org"\nEMERGE_DEFAULT_OPTS="-v --binpkg-respect-use=y --rebuild-if-new-rev=y --rebuilt-binaries=y --binpkg-changed-deps=y --autounmask=y --autounmask-write=y --jobs=3 --load-average=5.0"\nMAKEOPTS="-j6"\nAUTOCLEAN="yes"\nNOCOLOR="true"\nPORTAGE_TMPFS="/dev/shm"\nFEATURES="sandbox distlocks parallel-fetch strict -news test-fail-continue fail-clean"\nPORTAGE_ELOG_CLASSES=""\nPORTAGE_ELOG_SYSTEM="save"\nPORTDIR_OVERLAY="/usr/local/portage"\nLINGUAS="en"\nINPUT_DEVICES="keyboard mouse synaptics evdev"\
-nVIDEO_CARDS="radeon"\nALSA_CARDS="hda-intel intel8x0 intel8x0m"\nALSA_PCM_PLUGINS="adpcm alaw asym copy dmix dshare dsnoop empty extplug file hooks iec958 ioplug ladspa lfloat linear meter mmap_emul mulaw multi null plug rate route share shm softvol"\nCONFIG_PROTECT_MASK="/etc/portage/package.use/99_autounmask"\nGRUB_PLATFORMS="pc"\n\n# is in the host.conf\n#PORT_LOGDIR="/var/cache/portage/logs/host/setup"\n#PKGDIR="/var/cache/portage/packages/host/setup"\nsource host.conf\n\n# for layman stuff\n#source /var/cache/layman/make.conf\n', '35c38a4f7f2b4873cf6b6df9b21ca390602c1146b61cd514ca5b146a9d3000d0', 1, 1, '', 0, 'Runing', 1, '/var/cache/zobcs/tinderboxs_configs', '2016-02-13 14:36:20'),
-(4, 4, 1, 'CFLAGS="-O2 -pipe -march=native -fno-diagnostics-color"\nCXXFLAGS="-O2 -pipe -march=native -fno-diagnostics-color"\nCHOST="x86_64-pc-linux-gnu"\nUSE="X qt3support"\nACCEPT_KEYWORDS="~amd64 amd64"\nABI_X86="32 64"\nCPU_FLAGS_X86=""\nACCEPT_LICENSE="*"\nPYTHON_TARGETS="python2_7 python3_4 python3_5"\nRUBY_TARGETS="ruby20 ruby21 ruby22"\nPORTAGE_TMPDIR=/var/tmp\nDISTDIR="/var/cache/portage/distfiles"\nGENTOO_MIRRORS="ftp://mirror.mdfnet.se/gentoo ftp://trumpetti.atm.tut.fi/gentoo/ http://distfiles.gentoo.org"\nEMERGE_DEFAULT_OPTS="-v --binpkg-respect-use=y --rebuild-if-new-rev=y --rebuilt-binaries=y --binpkg-changed-deps=y --autounmask=y --autounmask-write=y --jobs=3 --load-average=5.0"\nMAKEOPTS="-j6"\nAUTOCLEAN="yes"\nNOCOLOR="true"\nPORTAGE_TMPFS="/dev/shm"\nFEATURES="sandbox distlocks parallel-fetch strict -news test-fail-continue fail-clean"\nPORTAGE_ELOG_CLASSES=""\nPORTAGE_ELOG_SYSTEM="save"\nPORTDIR_OVERLAY="/usr/local/portage"\nLINGUAS="en"\nINPUT_DEVICES="keyboard mouse synaptics evdev"\
-nVIDEO_CARDS="radeon"\nALSA_CARDS="hda-intel intel8x0 intel8x0m"\nALSA_PCM_PLUGINS="adpcm alaw asym copy dmix dshare dsnoop empty extplug file hooks iec958 ioplug ladspa lfloat linear meter mmap_emul mulaw multi null plug rate route share shm softvol"\nCONFIG_PROTECT_MASK="/etc/portage/package.use/99_autounmask"\nGRUB_PLATFORMS="pc"\n\n# is in the host.conf\n#PORT_LOGDIR="/var/cache/portage/logs/host/setup"\n#PKGDIR="/var/cache/portage/packages/host/setup"\nsource host.conf\n\n# for layman stuff\n#source /var/cache/layman/make.conf\n', '35c38a4f7f2b4873cf6b6df9b21ca390602c1146b61cd514ca5b146a9d3000d0', 1, 1, '', 0, 'Runing', 1, '/var/cache/zobcs/tinderboxs_configs', '2016-02-13 14:36:20'),
-(5, 5, 1, 'CFLAGS="-O2 -pipe -march=native -fno-diagnostics-color"\nCXXFLAGS="-O2 -pipe -march=native -fno-diagnostics-color"\nCHOST="x86_64-pc-linux-gnu"\nUSE="X qt3support"\nACCEPT_KEYWORDS="~amd64 amd64"\nABI_X86="32 64"\nCPU_FLAGS_X86=""\nACCEPT_LICENSE="*"\nPYTHON_TARGETS="python2_7 python3_4 python3_5"\nRUBY_TARGETS="ruby20 ruby21 ruby22"\nPORTAGE_TMPDIR=/var/tmp\nDISTDIR="/var/cache/portage/distfiles"\nGENTOO_MIRRORS="ftp://mirror.mdfnet.se/gentoo ftp://trumpetti.atm.tut.fi/gentoo/ http://distfiles.gentoo.org"\nEMERGE_DEFAULT_OPTS="-v --binpkg-respect-use=y --rebuild-if-new-rev=y --rebuilt-binaries=y --binpkg-changed-deps=y --autounmask=y --autounmask-write=y --jobs=3 --load-average=5.0"\nMAKEOPTS="-j6"\nAUTOCLEAN="yes"\nNOCOLOR="true"\nPORTAGE_TMPFS="/dev/shm"\nFEATURES="sandbox distlocks parallel-fetch strict -news test-fail-continue fail-clean"\nPORTAGE_ELOG_CLASSES=""\nPORTAGE_ELOG_SYSTEM="save"\nPORTDIR_OVERLAY="/usr/local/portage"\nLINGUAS="en"\nINPUT_DEVICES="keyboard mouse synaptics evdev"\
-nVIDEO_CARDS="radeon"\nALSA_CARDS="hda-intel intel8x0 intel8x0m"\nALSA_PCM_PLUGINS="adpcm alaw asym copy dmix dshare dsnoop empty extplug file hooks iec958 ioplug ladspa lfloat linear meter mmap_emul mulaw multi null plug rate route share shm softvol"\nCONFIG_PROTECT_MASK="/etc/portage/package.use/99_autounmask"\nGRUB_PLATFORMS="pc"\n\n# is in the host.conf\n#PORT_LOGDIR="/var/cache/portage/logs/host/setup"\n#PKGDIR="/var/cache/portage/packages/host/setup"\nsource host.conf\n\n# for layman stuff\n#source /var/cache/layman/make.conf\n', '35c38a4f7f2b4873cf6b6df9b21ca390602c1146b61cd514ca5b146a9d3000d0', 1, 1, '', 0, 'Runing', 1, '/var/cache/zobcs/tinderboxs_configs', '2016-02-13 14:36:25'),
-(6, 6, 1, 'CFLAGS="-O2 -pipe -march=native -fno-diagnostics-color"\nCXXFLAGS="-O2 -pipe -march=native -fno-diagnostics-color"\nCHOST="x86_64-pc-linux-gnu"\nUSE="X qt3support"\nACCEPT_KEYWORDS="~amd64 amd64"\nABI_X86="32 64"\nCPU_FLAGS_X86=""\nACCEPT_LICENSE="*"\nPYTHON_TARGETS="python2_7 python3_4 python3_5"\nRUBY_TARGETS="ruby20 ruby21 ruby22"\nPORTAGE_TMPDIR=/var/tmp\nDISTDIR="/var/cache/portage/distfiles"\nGENTOO_MIRRORS="ftp://mirror.mdfnet.se/gentoo ftp://trumpetti.atm.tut.fi/gentoo/ http://distfiles.gentoo.org"\nEMERGE_DEFAULT_OPTS="-v --binpkg-respect-use=y --rebuild-if-new-rev=y --rebuilt-binaries=y --binpkg-changed-deps=y --autounmask=y --autounmask-write=y --jobs=3 --load-average=5.0"\nMAKEOPTS="-j6"\nAUTOCLEAN="yes"\nNOCOLOR="true"\nPORTAGE_TMPFS="/dev/shm"\nFEATURES="sandbox distlocks parallel-fetch strict -news test-fail-continue fail-clean"\nPORTAGE_ELOG_CLASSES=""\nPORTAGE_ELOG_SYSTEM="save"\nPORTDIR_OVERLAY="/usr/local/portage"\nLINGUAS="en"\nINPUT_DEVICES="keyboard mouse synaptics evdev"\
-nVIDEO_CARDS="radeon"\nALSA_CARDS="hda-intel intel8x0 intel8x0m"\nALSA_PCM_PLUGINS="adpcm alaw asym copy dmix dshare dsnoop empty extplug file hooks iec958 ioplug ladspa lfloat linear meter mmap_emul mulaw multi null plug rate route share shm softvol"\nCONFIG_PROTECT_MASK="/etc/portage/package.use/99_autounmask"\nGRUB_PLATFORMS="pc"\n\n# is in the host.conf\n#PORT_LOGDIR="/var/cache/portage/logs/host/setup"\n#PKGDIR="/var/cache/portage/packages/host/setup"\nsource host.conf\n\n# for layman stuff\n#source /var/cache/layman/make.conf\n', '35c38a4f7f2b4873cf6b6df9b21ca390602c1146b61cd514ca5b146a9d3000d0', 1, 1, '', 0, 'Runing', 1, '/var/cache/zobcs/tinderboxs_configs', '2016-02-13 14:36:02');
-
---
--- Dumping data for table `emerge_options`
---
-
-INSERT INTO `emerge_options` (`eoption_id`, `eoption`) VALUES
-(1, '--oneshot'),
-(2, '--depclean'),
-(3, '--nodepclean'),
-(4, '--nooneshot'),
-(5, '--buildpkg'),
-(6, '--usepkg');
-
---
--- Dumping data for table `errors_info`
---
-
-INSERT INTO `errors_info` (`error_id`, `error_name`, `error_search`) VALUES
-(1, 'repoman', 'repoman'),
-(2, 'qa', 'qa'),
-(3, 'others', 'others'),
-(4, 'configure', 'configure phase'),
-(5, 'test', 'test phase'),
-(6, 'install', 'install phase'),
-(7, 'prepare', 'prepare phase'),
-(8, 'compile', 'compile phase'),
-(9, 'setup', 'setup phase');
-
---
--- Dumping data for table `hilight`
---
-
-INSERT INTO `hilight` (`hilight_id`, `hilight_search`, `hilight_search_end`, `hilight_search_pattern`, `hilight_css_id`, `hilight_start`, `hilight_end`) VALUES
-(3, '^ \\* QA Notice:', '^ \\* ', '^ \\* ', 3, 0, 0),
-(4, '^ \\* Package:', '', '', 2, 0, 4),
-(5, '>>> Unpacking', '', '', 1, 0, 0),
-(6, '\\[ ok ]', '', '', 2, 0, 0),
-(7, '\\[ !! ]', '', '', 5, 0, 0),
-(8, '>>> Source', '', '', 1, 0, 0),
-(9, '>>> Preparing', '', '', 1, 0, 0),
-(10, '^ \\* Applying', '', '', 2, 0, 0),
-(11, '>>> Configuring', '', '', 1, 0, 0),
-(12, '^ \\* econf', '', '', 1, 0, 0),
-(13, '>>> Compiling', '', '', 1, 0, 0),
-(14, '>>> Done.', '', '', 1, 0, 0),
-(15, '>>> Merging', '', '', 1, 0, 0),
-(16, '>>> Safely', '', '', 1, 0, 0),
-(17, '>>> Original', '', '', 1, 0, 0),
-(18, 'merged.$', '', '', 1, 0, 0),
-(19, '>>> Extracting info', '', '', 1, 0, 0),
-(20, '>>> Extracting (?!info)', '', '', 1, 0, 0),
-(21, '>>> Regenerating', '', '', 1, 0, 0),
-(22, '>>> Installing', '', '', 1, 0, 0),
-(23, '>>> Test phase', '', '', 1, 0, 0),
-(24, '^ \\* Running', '', '', 1, 0, 0),
-(25, '>>> Install', '', '', 1, 0, 0),
-(26, '>>> Completed installing', '', '', 1, 0, 0),
-(27, '^ \\* ERROR:', '^ \\* S:', '^ \\* (?!S:)', 5, 0, 0),
-(28, ' Error 1', '', '', 5, 2, 1),
-(29, 'undefined reference to', '', '', 5, 0, 0),
-(30, '^ \\* Generating', '', '', 1, 0, 0),
-(31, ': fatal error:', '', '', 5, 0, 0),
-(32, '^ \\* Done', '', '', 2, 0, 0),
-(33, '.patch ...$', '', '', 2, 0, 0),
-(35, '^ \\* Disabling', '', '', 2, 0, 0),
-(37, '^ \\* abi_x86_', '', '', 2, 0, 0),
-(38, '^ \\* >>> SetUID:', '', '', 1, 0, 0),
-(39, '^ \\* >>> SetGID:', '', '', 1, 0, 0),
-(40, 'CMake Error', '', '', 5, 0, 1),
-(41, 'No such file or directory$', '', '', 5, 0, 0),
-(43, '^ \\* Updating', '', '', 1, 0, 0),
-(44, '^strip:', '', '', 1, 0, 0),
-(45, '^ \\* checking', '', '', 1, 0, 0),
-(46, 'files checked ...$', '', '', 1, 0, 0),
-(49, '^ \\* Installing', '', '', 1, 0, 0),
-(48, '^SyntaxError: invalid syntax', '', '', 5, 3, 0),
-(50, '^ \\* Skipping make', '', '', 1, 0, 0),
-(51, 'command not found$', '', '', 5, 0, 0);
-
---
--- Dumping data for table `hilight_css`
---
-
-INSERT INTO `hilight_css` (`hilight_css_id`, `hilight_css_name`, `hilight_css_collor`) VALUES
-(1, 'info1', '#7FFF00'),
-(2, 'info2', 'Green'),
-(3, 'qa1', 'Yellow'),
-(5, 'fail1', 'Red');
-
---
--- Dumping data for table `jobs`
---
-
-INSERT INTO `jobs` (`job_id`, `job_type`, `status`, `user`, `config_id`, `run_config_id`, `time_stamp`) VALUES
-(1, 'updatedb', 'Done', 'cron', 1, 1, '2016-02-20 14:13:32'),
-(2, 'esync', 'Done', 'cron', 1, 1, '2016-02-21 20:02:08'),
-(3, 'removeold_cpv', 'Done', 'cron', 1, 1, '2016-02-21 20:04:51');
-
---
--- Dumping data for table `setups`
---
-
-INSERT INTO `setups` (`setup_id`, `setup`, `profile`) VALUES
-(1, 'base', 'base'),
-(2, 'amd64_hardened_unstable', 'hardened/linux/amd64'),
-(3, 'amd64_default_unstable', 'default/linux/amd64/13.0');
-
---
--- Dumping data for table `tbc_config`
---
-
-INSERT INTO `tbc_config` (`id`, `webirker`, `hostirker`, `webbug`) VALUES
-(1, '77.110.8.76', '192.168.1.4', 'bugs.gentoo.org');
-
-/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
-/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
-/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
diff --git a/sql/structure_dump.sql b/sql/structure_dump.sql
index 2a6f847..320aca7 100644
--- a/sql/structure_dump.sql
+++ b/sql/structure_dump.sql
@@ -1,1061 +1,477 @@
--- phpMyAdmin SQL Dump
--- version 4.2.13
--- http://www.phpmyadmin.net
---
--- Host: localhost
--- Generation Time: Mar 03, 2016 at 03:48 PM
--- Server version: 10.0.22-MariaDB-log
--- PHP Version: 7.0.3-pl0-gentoo
-
-SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
-SET time_zone = "+00:00";
-
-
-/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
-/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
-/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
-/*!40101 SET NAMES utf8 */;
-
---
--- Database: `tbc`
---
-
-DELIMITER $$
---
--- Procedures
---
-CREATE DEFINER=`tbc`@`localhost` PROCEDURE `add_jobs_esync`()
- MODIFIES SQL DATA
-BEGIN
- DECLARE in_config_id INT;
- DECLARE in_job_id INT;
- SET in_config_id = (SELECT config_id
- FROM configs WHERE default_config = True);
- SET in_job_id = (SELECT job_id FROM jobs
- WHERE job_type = 'esync'
- AND config_id = in_config_id
- AND status = 'Done'
- LIMIT 1);
- IF in_job_id >= 1 THEN
- UPDATE jobs SET user = 'cron', status = 'Waiting' WHERE job_type = 'esync';
- ELSE
- SET in_job_id = 0;
- END IF;
-END$$
-
-CREATE DEFINER=`tbc`@`localhost` PROCEDURE `add_jobs_removeold_cpv`()
- MODIFIES SQL DATA
-BEGIN
- DECLARE in_config_id INT;
- DECLARE in_job_id INT;
- SET in_config_id = (SELECT config_id
- FROM configs WHERE default_config = True);
- SET in_job_id = (SELECT job_id FROM jobs
- WHERE job_type = 'removeold_cpv'
- AND config_id = in_config_id
- AND status = 'Done'
- LIMIT 1);
- IF in_job_id >= 1 THEN
- UPDATE jobs SET user = 'cron', status = 'Waiting' WHERE job_type = 'removeold_cpv';
- ELSE
- SET in_job_id = 0;
- END IF;
-END$$
-
-DELIMITER ;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `build_jobs`
---
-
-CREATE TABLE IF NOT EXISTS `build_jobs` (
-`build_job_id` int(11) NOT NULL,
- `ebuild_id` int(11) NOT NULL,
- `setup_id` int(11) NOT NULL,
- `config_id` int(11) NOT NULL,
- `status` enum('Waiting','Building','Looked') NOT NULL DEFAULT 'Waiting',
- `build_now` tinyint(1) NOT NULL,
- `removebin` tinyint(1) NOT NULL,
- `time_stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='The build work list';
-
--- --------------------------------------------------------
-
---
--- Table structure for table `build_jobs_emerge_options`
---
-
-CREATE TABLE IF NOT EXISTS `build_jobs_emerge_options` (
-`id` int(11) NOT NULL,
- `build_job_id` int(11) NOT NULL,
- `eoption_id` int(11) NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `build_jobs_redo`
---
-
-CREATE TABLE IF NOT EXISTS `build_jobs_redo` (
-`id` int(11) NOT NULL,
- `build_job_id` int(11) NOT NULL COMMENT 'build job id',
- `fail_times` int(1) NOT NULL COMMENT 'Fail times max 5',
- `fail_type` varchar(30) NOT NULL COMMENT 'Type of fail',
- `time_stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Time'
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Build jobs that need to be redone';
-
--- --------------------------------------------------------
-
---
--- Table structure for table `build_jobs_use`
---
-
-CREATE TABLE IF NOT EXISTS `build_jobs_use` (
-`id` int(11) NOT NULL,
- `build_job_id` int(11) NOT NULL,
- `use_id` int(11) NOT NULL,
- `status` tinyint(1) NOT NULL DEFAULT '0'
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `build_logs`
---
-
-CREATE TABLE IF NOT EXISTS `build_logs` (
-`build_log_id` int(11) NOT NULL,
- `ebuild_id` int(11) NOT NULL,
- `fail` tinyint(1) NOT NULL DEFAULT '0',
- `summery_text` longtext NOT NULL,
- `log_hash` varchar(100) NOT NULL,
- `bug_id` int(10) NOT NULL DEFAULT '0',
- `time_stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Main log info for the builds';
-
--- --------------------------------------------------------
-
---
--- Table structure for table `build_logs_config`
---
-
-CREATE TABLE IF NOT EXISTS `build_logs_config` (
-`log_id` int(11) NOT NULL,
- `build_log_id` int(11) NOT NULL,
- `config_id` int(11) NOT NULL,
- `einfo_id` int(11) NOT NULL,
- `logname` varchar(150) NOT NULL COMMENT 'filename of the log',
- `time_stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `build_logs_emerge_options`
---
-
-CREATE TABLE IF NOT EXISTS `build_logs_emerge_options` (
-`id` int(11) NOT NULL,
- `build_logs_id` int(11) NOT NULL,
- `eoption_id` int(11) NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `build_logs_errors`
---
-
-CREATE TABLE IF NOT EXISTS `build_logs_errors` (
-`id` int(11) NOT NULL,
- `build_log_id` int(11) NOT NULL,
- `error_id` int(11) NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `build_logs_hilight`
---
-
-CREATE TABLE IF NOT EXISTS `build_logs_hilight` (
-`id` int(11) NOT NULL,
- `log_id` int(11) NOT NULL,
- `start_line` int(11) NOT NULL,
- `end_line` int(11) NOT NULL,
- `hilight_css_id` int(11) NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `build_logs_qa`
---
-
-CREATE TABLE IF NOT EXISTS `build_logs_qa` (
-`id` int(11) NOT NULL,
- `build_log_id` int(11) NOT NULL,
- `summery_text` text NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `build_logs_repoman`
---
-
-CREATE TABLE IF NOT EXISTS `build_logs_repoman` (
-`id` int(11) NOT NULL,
- `build_log_id` int(11) NOT NULL,
- `summery_text` text NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `build_logs_use`
---
-
-CREATE TABLE IF NOT EXISTS `build_logs_use` (
-`id` int(11) NOT NULL,
- `build_log_id` int(11) NOT NULL,
- `use_id` int(11) NOT NULL,
- `status` tinyint(1) NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `categories`
---
-
-CREATE TABLE IF NOT EXISTS `categories` (
-`category_id` int(11) NOT NULL,
- `category` varchar(50) NOT NULL,
- `active` tinyint(1) NOT NULL DEFAULT '0',
- `time_stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Categories main table (C)';
-
--- --------------------------------------------------------
-
---
--- Table structure for table `categories_metadata`
---
-
-CREATE TABLE IF NOT EXISTS `categories_metadata` (
-`id` int(11) NOT NULL,
- `category_id` int(11) NOT NULL,
- `checksum` varchar(100) NOT NULL,
- `descriptions` text NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Categories main table (C)';
-
--- --------------------------------------------------------
-
---
--- Table structure for table `configs`
---
-
-CREATE TABLE IF NOT EXISTS `configs` (
-`config_id` int(11) NOT NULL COMMENT 'Config index',
- `hostname` varchar(50) NOT NULL,
- `setup_id` int(11) NOT NULL COMMENT 'setup',
- `default_config` tinyint(1) NOT NULL COMMENT 'Host setup'
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Main config table';
-
--- --------------------------------------------------------
-
---
--- Table structure for table `configs_emerge_options`
---
-
-CREATE TABLE IF NOT EXISTS `configs_emerge_options` (
-`id` int(11) NOT NULL,
- `config_id` int(11) NOT NULL COMMENT 'config id',
- `eoption_id` int(11) NOT NULL COMMENT 'emerge option id'
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Emerge command options for the configs';
-
--- --------------------------------------------------------
-
---
--- Table structure for table `configs_metadata`
---
-
-CREATE TABLE IF NOT EXISTS `configs_metadata` (
-`id` int(11) NOT NULL,
- `config_id` int(11) NOT NULL,
- `keyword_id` int(11) NOT NULL,
- `make_conf_text` text NOT NULL,
- `checksum` varchar(100) NOT NULL,
- `configsync` tinyint(1) NOT NULL,
- `active` tinyint(1) NOT NULL,
- `config_error_text` text NOT NULL,
- `updateing` tinyint(1) NOT NULL,
- `status` enum('Waiting','Runing','Stoped') NOT NULL,
- `auto` tinyint(1) NOT NULL,
- `repo_path` varchar(100) NOT NULL COMMENT 'git repo path for etc/portage',
- `time_stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Config Status';
-
--- --------------------------------------------------------
-
---
--- Table structure for table `ebuilds`
---
-
-CREATE TABLE IF NOT EXISTS `ebuilds` (
-`ebuild_id` int(11) NOT NULL,
- `package_id` int(11) NOT NULL,
- `version` varchar(50) NOT NULL,
- `checksum` varchar(100) NOT NULL,
- `active` tinyint(1) NOT NULL DEFAULT '0',
- `time_stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Version main table (V)';
-
--- --------------------------------------------------------
-
---
--- Table structure for table `ebuilds_iuse`
---
-
-CREATE TABLE IF NOT EXISTS `ebuilds_iuse` (
-`id` int(11) NOT NULL,
- `ebuild_id` int(11) NOT NULL,
- `use_id` int(11) NOT NULL,
- `status` tinyint(1) NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `ebuilds_keywords`
---
-
-CREATE TABLE IF NOT EXISTS `ebuilds_keywords` (
-`id` int(11) NOT NULL,
- `ebuild_id` int(11) NOT NULL,
- `keyword_id` int(11) NOT NULL,
- `status` enum('Stable','Unstable','Negative') NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `ebuilds_metadata`
---
-
-CREATE TABLE IF NOT EXISTS `ebuilds_metadata` (
-`id` int(11) NOT NULL,
- `ebuild_id` int(11) NOT NULL,
- `commit` varchar(100) NOT NULL COMMENT 'Git commit',
- `new` tinyint(1) NOT NULL,
- `descriptions` varchar(200) NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `ebuilds_restrictions`
---
-
-CREATE TABLE IF NOT EXISTS `ebuilds_restrictions` (
-`id` int(11) NOT NULL,
- `ebuild_id` int(11) NOT NULL,
- `restriction_id` int(11) NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `emails`
---
-
-CREATE TABLE IF NOT EXISTS `emails` (
-`email_id` int(11) NOT NULL,
- `email` varchar(150) NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `emerge_info`
---
-
-CREATE TABLE IF NOT EXISTS `emerge_info` (
-`einfo_id` int(11) NOT NULL,
- `emerge_info_text` text NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `emerge_options`
---
-
-CREATE TABLE IF NOT EXISTS `emerge_options` (
-`eoption_id` int(11) NOT NULL COMMENT 'emerge command options id',
- `eoption` varchar(15) NOT NULL COMMENT 'emerge command options'
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `errors_info`
---
-
-CREATE TABLE IF NOT EXISTS `errors_info` (
-`error_id` int(11) NOT NULL,
- `error_name` varchar(10) NOT NULL,
- `error_search` varchar(20) NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `hilight`
---
-
-CREATE TABLE IF NOT EXISTS `hilight` (
-`hilight_id` int(11) NOT NULL,
- `hilight_search` varchar(30) NOT NULL,
- `hilight_search_end` varchar(30) NOT NULL,
- `hilight_search_pattern` varchar(30) NOT NULL,
- `hilight_css_id` int(11) NOT NULL,
- `hilight_start` int(11) NOT NULL,
- `hilight_end` int(11) NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `hilight_css`
---
-
-CREATE TABLE IF NOT EXISTS `hilight_css` (
-`hilight_css_id` int(11) NOT NULL,
- `hilight_css_name` varchar(11) NOT NULL,
- `hilight_css_collor` varchar(10) NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `jobs`
---
-
-CREATE TABLE IF NOT EXISTS `jobs` (
-`job_id` int(11) NOT NULL,
- `job_type` enum('esync','updatedb','removeold_cpv') NOT NULL,
- `status` enum('Runing','Done','Waiting') NOT NULL DEFAULT 'Waiting',
- `user` varchar(20) NOT NULL,
- `config_id` int(11) NOT NULL,
- `run_config_id` int(11) NOT NULL,
- `time_stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `keywords`
---
-
-CREATE TABLE IF NOT EXISTS `keywords` (
-`keyword_id` int(11) NOT NULL COMMENT 'keyword index',
- `keyword` varchar(15) NOT NULL COMMENT 'keyword'
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='KEYWORD';
-
--- --------------------------------------------------------
-
---
--- Table structure for table `logs`
---
-
-CREATE TABLE IF NOT EXISTS `logs` (
-`log_id` int(11) NOT NULL,
- `config_id` int(11) NOT NULL,
- `log_type` enum('info','error','debug','qa') NOT NULL,
- `msg` text NOT NULL,
- `time_stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `packages`
---
-
-CREATE TABLE IF NOT EXISTS `packages` (
-`package_id` int(11) NOT NULL,
- `category_id` int(11) NOT NULL,
- `package` varchar(50) NOT NULL,
- `repo_id` int(11) NOT NULL,
- `active` tinyint(1) NOT NULL,
- `time_stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Packages main table (P)';
-
--- --------------------------------------------------------
-
---
--- Table structure for table `packages_emails`
---
-
-CREATE TABLE IF NOT EXISTS `packages_emails` (
-`id` int(11) NOT NULL,
- `package_id` int(11) NOT NULL,
- `email_id` int(11) NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `packages_metadata`
---
-
-CREATE TABLE IF NOT EXISTS `packages_metadata` (
-`id` int(11) NOT NULL,
- `package_id` int(11) NOT NULL,
- `gitlog` text NOT NULL,
- `descriptions` text NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `packages_repoman`
---
-
-CREATE TABLE IF NOT EXISTS `packages_repoman` (
-`id` int(11) NOT NULL,
- `package_id` int(11) NOT NULL,
- `repoman_hash` varchar(100) NOT NULL,
- `repoman_text` text NOT NULL,
- `time_stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `repos`
---
-
-CREATE TABLE IF NOT EXISTS `repos` (
-`repo_id` int(11) NOT NULL,
- `repo` varchar(100) NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Repo main table (repo)';
-
--- --------------------------------------------------------
-
---
--- Table structure for table `restrictions`
---
-
-CREATE TABLE IF NOT EXISTS `restrictions` (
-`restriction_id` int(11) NOT NULL,
- `restriction` varchar(50) NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `setups`
---
-
-CREATE TABLE IF NOT EXISTS `setups` (
-`setup_id` int(11) NOT NULL,
- `setup` varchar(100) NOT NULL,
- `profile` varchar(150) NOT NULL,
- `test` tinyint(1) NOT NULL DEFAULT '0',
- `repoman` tinyint(1) NOT NULL DEFAULT '0'
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `tbc_config`
---
-
-CREATE TABLE IF NOT EXISTS `tbc_config` (
-`id` int(11) NOT NULL,
- `webirker` varchar(100) NOT NULL,
- `hostirker` varchar(100) NOT NULL,
- `webbug` varchar(100) NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
--- --------------------------------------------------------
-
---
--- Table structure for table `uses`
---
-
-CREATE TABLE IF NOT EXISTS `uses` (
-`use_id` int(11) NOT NULL,
- `flag` varchar(50) NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Use flags main table';
-
---
--- Indexes for dumped tables
---
-
---
--- Indexes for table `build_jobs`
---
-ALTER TABLE `build_jobs`
- ADD PRIMARY KEY (`build_job_id`), ADD KEY `ebuild_id` (`ebuild_id`), ADD KEY `config_id` (`config_id`), ADD KEY `time_stamp` (`time_stamp`);
-
---
--- Indexes for table `build_jobs_emerge_options`
---
-ALTER TABLE `build_jobs_emerge_options`
- ADD PRIMARY KEY (`id`), ADD KEY `build_job_id` (`build_job_id`), ADD KEY `eoption_id` (`eoption_id`);
-
---
--- Indexes for table `build_jobs_redo`
---
-ALTER TABLE `build_jobs_redo`
- ADD PRIMARY KEY (`id`), ADD KEY `build_job_id` (`build_job_id`);
-
---
--- Indexes for table `build_jobs_use`
---
-ALTER TABLE `build_jobs_use`
- ADD PRIMARY KEY (`id`), ADD KEY `build_job_id` (`build_job_id`), ADD KEY `use_id` (`use_id`);
-
---
--- Indexes for table `build_logs`
---
-ALTER TABLE `build_logs`
- ADD PRIMARY KEY (`build_log_id`), ADD KEY `ebuild_id` (`ebuild_id`);
-
---
--- Indexes for table `build_logs_config`
---
-ALTER TABLE `build_logs_config`
- ADD PRIMARY KEY (`log_id`), ADD KEY `config_id` (`config_id`), ADD KEY `build_log_id` (`build_log_id`), ADD KEY `einfo_id` (`einfo_id`);
-
---
--- Indexes for table `build_logs_emerge_options`
---
-ALTER TABLE `build_logs_emerge_options`
- ADD PRIMARY KEY (`id`), ADD KEY `eoption_id` (`eoption_id`), ADD KEY `build_logs_id` (`build_logs_id`);
-
---
--- Indexes for table `build_logs_errors`
---
-ALTER TABLE `build_logs_errors`
- ADD PRIMARY KEY (`id`), ADD KEY `build_log_id` (`build_log_id`), ADD KEY `error_id` (`error_id`);
-
---
--- Indexes for table `build_logs_hilight`
---
-ALTER TABLE `build_logs_hilight`
- ADD PRIMARY KEY (`id`), ADD KEY `log_id` (`log_id`), ADD KEY `hilight_id` (`hilight_css_id`), ADD KEY `hilight_css_id` (`hilight_css_id`);
-
---
--- Indexes for table `build_logs_qa`
---
-ALTER TABLE `build_logs_qa`
- ADD PRIMARY KEY (`id`), ADD KEY `build_logs_id` (`build_log_id`);
-
---
--- Indexes for table `build_logs_repoman`
---
-ALTER TABLE `build_logs_repoman`
- ADD PRIMARY KEY (`id`), ADD KEY `build_logs_id` (`build_log_id`);
-
---
--- Indexes for table `build_logs_use`
---
-ALTER TABLE `build_logs_use`
- ADD PRIMARY KEY (`id`), ADD KEY `build_log_id` (`build_log_id`), ADD KEY `use_id` (`use_id`);
-
---
--- Indexes for table `categories`
---
-ALTER TABLE `categories`
- ADD PRIMARY KEY (`category_id`);
-
---
--- Indexes for table `categories_metadata`
---
-ALTER TABLE `categories_metadata`
- ADD PRIMARY KEY (`id`);
-
---
--- Indexes for table `configs`
---
-ALTER TABLE `configs`
- ADD PRIMARY KEY (`config_id`);
-
---
--- Indexes for table `configs_emerge_options`
---
-ALTER TABLE `configs_emerge_options`
- ADD PRIMARY KEY (`id`), ADD KEY `config_id` (`config_id`), ADD KEY `eoption_id` (`eoption_id`);
-
---
--- Indexes for table `configs_metadata`
---
-ALTER TABLE `configs_metadata`
- ADD PRIMARY KEY (`id`), ADD KEY `keyword_id` (`keyword_id`), ADD KEY `config_id` (`config_id`);
-
---
--- Indexes for table `ebuilds`
---
-ALTER TABLE `ebuilds`
- ADD PRIMARY KEY (`ebuild_id`), ADD KEY `package_id` (`package_id`), ADD KEY `checksum` (`checksum`), ADD KEY `version` (`version`);
-
---
--- Indexes for table `ebuilds_iuse`
---
-ALTER TABLE `ebuilds_iuse`
- ADD PRIMARY KEY (`id`), ADD KEY `ebuild_id` (`ebuild_id`), ADD KEY `use_id` (`use_id`);
-
---
--- Indexes for table `ebuilds_keywords`
---
-ALTER TABLE `ebuilds_keywords`
- ADD PRIMARY KEY (`id`), ADD KEY `ebuild_id` (`ebuild_id`), ADD KEY `keyword_id` (`keyword_id`);
-
---
--- Indexes for table `ebuilds_metadata`
---
-ALTER TABLE `ebuilds_metadata`
- ADD PRIMARY KEY (`id`), ADD KEY `ebuild_id` (`ebuild_id`);
-
---
--- Indexes for table `ebuilds_restrictions`
---
-ALTER TABLE `ebuilds_restrictions`
- ADD PRIMARY KEY (`id`), ADD KEY `restriction_id` (`restriction_id`), ADD KEY `ebuild_id` (`ebuild_id`);
-
---
--- Indexes for table `emails`
---
-ALTER TABLE `emails`
- ADD PRIMARY KEY (`email_id`);
-
---
--- Indexes for table `emerge_info`
---
-ALTER TABLE `emerge_info`
- ADD UNIQUE KEY `einfo_id` (`einfo_id`);
-
---
--- Indexes for table `emerge_options`
---
-ALTER TABLE `emerge_options`
- ADD PRIMARY KEY (`eoption_id`);
-
---
--- Indexes for table `errors_info`
---
-ALTER TABLE `errors_info`
- ADD PRIMARY KEY (`error_id`);
-
---
--- Indexes for table `hilight`
---
-ALTER TABLE `hilight`
- ADD PRIMARY KEY (`hilight_id`), ADD KEY `hilight_css_id` (`hilight_css_id`);
-
---
--- Indexes for table `hilight_css`
---
-ALTER TABLE `hilight_css`
- ADD PRIMARY KEY (`hilight_css_id`);
-
---
--- Indexes for table `jobs`
---
-ALTER TABLE `jobs`
- ADD PRIMARY KEY (`job_id`), ADD KEY `config_id` (`config_id`), ADD KEY `run_config_id` (`run_config_id`), ADD KEY `job_type_id` (`job_type`);
-
---
--- Indexes for table `keywords`
---
-ALTER TABLE `keywords`
- ADD PRIMARY KEY (`keyword_id`);
-
---
--- Indexes for table `logs`
---
-ALTER TABLE `logs`
- ADD PRIMARY KEY (`log_id`), ADD KEY `config_id` (`config_id`);
-
---
--- Indexes for table `packages`
---
-ALTER TABLE `packages`
- ADD PRIMARY KEY (`package_id`), ADD KEY `category_id` (`category_id`), ADD KEY `repo_id` (`repo_id`), ADD KEY `package` (`package`);
-
---
--- Indexes for table `packages_emails`
---
-ALTER TABLE `packages_emails`
- ADD PRIMARY KEY (`id`), ADD KEY `package_id` (`package_id`,`email_id`);
-
---
--- Indexes for table `packages_metadata`
---
-ALTER TABLE `packages_metadata`
- ADD PRIMARY KEY (`id`), ADD KEY `package_id` (`package_id`);
-
---
--- Indexes for table `packages_repoman`
---
-ALTER TABLE `packages_repoman`
- ADD PRIMARY KEY (`id`);
-
---
--- Indexes for table `repos`
---
-ALTER TABLE `repos`
- ADD PRIMARY KEY (`repo_id`);
-
---
--- Indexes for table `restrictions`
---
-ALTER TABLE `restrictions`
- ADD PRIMARY KEY (`restriction_id`);
-
---
--- Indexes for table `setups`
---
-ALTER TABLE `setups`
- ADD PRIMARY KEY (`setup_id`), ADD UNIQUE KEY `setup_id` (`setup_id`);
-
---
--- Indexes for table `tbc_config`
---
-ALTER TABLE `tbc_config`
- ADD PRIMARY KEY (`id`);
-
---
--- Indexes for table `uses`
---
-ALTER TABLE `uses`
- ADD PRIMARY KEY (`use_id`);
-
---
--- AUTO_INCREMENT for dumped tables
---
-
---
--- AUTO_INCREMENT for table `build_jobs`
---
-ALTER TABLE `build_jobs`
-MODIFY `build_job_id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `build_jobs_emerge_options`
---
-ALTER TABLE `build_jobs_emerge_options`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `build_jobs_redo`
---
-ALTER TABLE `build_jobs_redo`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `build_jobs_use`
---
-ALTER TABLE `build_jobs_use`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `build_logs`
---
-ALTER TABLE `build_logs`
-MODIFY `build_log_id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `build_logs_config`
---
-ALTER TABLE `build_logs_config`
-MODIFY `log_id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `build_logs_emerge_options`
---
-ALTER TABLE `build_logs_emerge_options`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `build_logs_errors`
---
-ALTER TABLE `build_logs_errors`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `build_logs_hilight`
---
-ALTER TABLE `build_logs_hilight`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `build_logs_qa`
---
-ALTER TABLE `build_logs_qa`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `build_logs_repoman`
---
-ALTER TABLE `build_logs_repoman`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `build_logs_use`
---
-ALTER TABLE `build_logs_use`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `categories`
---
-ALTER TABLE `categories`
-MODIFY `category_id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `categories_metadata`
---
-ALTER TABLE `categories_metadata`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `configs`
---
-ALTER TABLE `configs`
-MODIFY `config_id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Config index';
---
--- AUTO_INCREMENT for table `configs_emerge_options`
---
-ALTER TABLE `configs_emerge_options`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `configs_metadata`
---
-ALTER TABLE `configs_metadata`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `ebuilds`
---
-ALTER TABLE `ebuilds`
-MODIFY `ebuild_id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `ebuilds_iuse`
---
-ALTER TABLE `ebuilds_iuse`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `ebuilds_keywords`
---
-ALTER TABLE `ebuilds_keywords`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `ebuilds_metadata`
---
-ALTER TABLE `ebuilds_metadata`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `ebuilds_restrictions`
---
-ALTER TABLE `ebuilds_restrictions`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `emails`
---
-ALTER TABLE `emails`
-MODIFY `email_id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `emerge_info`
---
-ALTER TABLE `emerge_info`
-MODIFY `einfo_id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `emerge_options`
---
-ALTER TABLE `emerge_options`
-MODIFY `eoption_id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'emerge command options id';
---
--- AUTO_INCREMENT for table `errors_info`
---
-ALTER TABLE `errors_info`
-MODIFY `error_id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `hilight`
---
-ALTER TABLE `hilight`
-MODIFY `hilight_id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `hilight_css`
---
-ALTER TABLE `hilight_css`
-MODIFY `hilight_css_id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `jobs`
---
-ALTER TABLE `jobs`
-MODIFY `job_id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `keywords`
---
-ALTER TABLE `keywords`
-MODIFY `keyword_id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'keyword index';
---
--- AUTO_INCREMENT for table `logs`
---
-ALTER TABLE `logs`
-MODIFY `log_id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `packages`
---
-ALTER TABLE `packages`
-MODIFY `package_id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `packages_emails`
---
-ALTER TABLE `packages_emails`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `packages_metadata`
---
-ALTER TABLE `packages_metadata`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `packages_repoman`
---
-ALTER TABLE `packages_repoman`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `repos`
---
-ALTER TABLE `repos`
-MODIFY `repo_id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `restrictions`
---
-ALTER TABLE `restrictions`
-MODIFY `restriction_id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `setups`
---
-ALTER TABLE `setups`
-MODIFY `setup_id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `tbc_config`
---
-ALTER TABLE `tbc_config`
-MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
---
--- AUTO_INCREMENT for table `uses`
---
-ALTER TABLE `uses`
-MODIFY `use_id` int(11) NOT NULL AUTO_INCREMENT;
-DELIMITER $$
---
--- Events
---
-CREATE DEFINER=`tbc`@`localhost` EVENT `add_esync_jobs` ON SCHEDULE EVERY 1 HOUR STARTS '2012-12-23 17:15:13' ON COMPLETION NOT PRESERVE ENABLE DO BEGIN
- CALL add_jobs_esync();
-END$$
-
-CREATE DEFINER=`tbc`@`localhost` EVENT `add_removeold_cpv_jobs` ON SCHEDULE EVERY 24 HOUR STARTS '2016-02-21 21:00:22' ON COMPLETION NOT PRESERVE ENABLE DO BEGIN
- CALL add_jobs_removeold_cpv();
-END$$
-
-DELIMITER ;
-
-/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
-/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
-/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
+SET AUTOCOMMIT = 0;
+START TRANSACTION;
+SET time_zone = "+00:00";
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8mb4 */;
+
+
+CREATE TABLE `builds_uses` (
+ `id` int(11) NOT NULL,
+ `build_uuid` varchar(36) NOT NULL,
+ `use_id` int(11) NOT NULL,
+ `status` tinyint(1) DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `categories` (
+ `created_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL,
+ `deleted_at` datetime DEFAULT NULL,
+ `deleted` tinyint(1) DEFAULT NULL,
+ `name` varchar(255) NOT NULL,
+ `uuid` varchar(36) NOT NULL,
+ `status` enum('failed','completed','in-progress','waiting') DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `categories_metadata` (
+ `id` int(11) NOT NULL,
+ `category_uuid` varchar(36) NOT NULL,
+ `checksum` varchar(255) DEFAULT NULL,
+ `description` text DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `ebuilds` (
+ `created_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL,
+ `deleted_at` datetime DEFAULT NULL,
+ `deleted` tinyint(1) DEFAULT NULL,
+ `version` varchar(255) NOT NULL,
+ `checksum` varchar(255) NOT NULL,
+ `uuid` varchar(36) NOT NULL,
+ `package_uuid` varchar(36) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `ebuilds_keywords` (
+ `id` int(11) NOT NULL,
+ `ebuild_uuid` varchar(36) NOT NULL,
+ `keyword_id` int(11) NOT NULL,
+ `status` enum('stable','unstable','negative') DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `ebuilds_metadata` (
+ `id` int(11) NOT NULL,
+ `ebuild_uuid` varchar(36) NOT NULL,
+ `commit` varchar(255) NOT NULL,
+ `commit_msg` text DEFAULT NULL,
+ `description` text DEFAULT NULL,
+ `slot` varchar(30) NOT NULL,
+ `homepage` varchar(500) NOT NULL,
+ `license` varchar(500) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `ebuilds_restrictions` (
+ `id` int(11) NOT NULL,
+ `ebuild_uuid` varchar(36) NOT NULL,
+ `restriction_id` int(11) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `ebuilds_uses` (
+ `id` int(11) NOT NULL,
+ `ebuild_uuid` varchar(36) NOT NULL,
+ `use_id` int(11) NOT NULL,
+ `status` tinyint(1) DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `emails` (
+ `created_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL,
+ `deleted_at` datetime DEFAULT NULL,
+ `deleted` tinyint(1) DEFAULT NULL,
+ `id` int(11) NOT NULL,
+ `email` varchar(255) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `keywords` (
+ `created_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL,
+ `deleted_at` datetime DEFAULT NULL,
+ `deleted` tinyint(1) DEFAULT NULL,
+ `id` int(11) NOT NULL,
+ `keyword` varchar(255) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `migrate_version` (
+ `repository_id` varchar(250) NOT NULL,
+ `repository_path` text DEFAULT NULL,
+ `version` int(11) DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `packages` (
+ `created_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL,
+ `deleted_at` datetime DEFAULT NULL,
+ `deleted` tinyint(1) DEFAULT NULL,
+ `name` varchar(255) NOT NULL,
+ `uuid` varchar(36) NOT NULL,
+ `status` enum('failed','completed','in-progress','waiting') DEFAULT NULL,
+ `category_uuid` varchar(36) NOT NULL,
+ `repo_uuid` varchar(36) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `packages_emails` (
+ `id` int(11) NOT NULL,
+ `package_uuid` varchar(36) NOT NULL,
+ `email_id` int(11) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `packages_metadata` (
+ `id` int(11) NOT NULL,
+ `package_uuid` varchar(36) NOT NULL,
+ `gitlog` text DEFAULT NULL,
+ `description` text DEFAULT NULL,
+ `checksum` varchar(255) DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `projects` (
+ `created_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL,
+ `deleted_at` datetime DEFAULT NULL,
+ `deleted` tinyint(1) DEFAULT NULL,
+ `name` varchar(255) NOT NULL,
+ `uuid` varchar(36) NOT NULL,
+ `active` tinyint(1) DEFAULT NULL,
+ `auto` tinyint(1) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `projects_builds` (
+ `uuid` varchar(36) NOT NULL,
+ `ebuild_uuid` varchar(36) NOT NULL,
+ `project_uuid` varchar(36) NOT NULL,
+ `user_id` int(10) NOT NULL,
+ `status` enum('failed','completed','in-progress','waiting') NOT NULL,
+ `priority` int(1) NOT NULL DEFAULT 5,
+ `deleted` tinyint(1) NOT NULL DEFAULT 0,
+ `deleted_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL,
+ `created_at` datetime DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `projects_metadata` (
+ `id` int(11) NOT NULL,
+ `project_uuid` varchar(36) NOT NULL,
+ `titel` varchar(50) NOT NULL,
+ `description` text NOT NULL,
+ `project_repo_uuid` varchar(36) NOT NULL,
+ `project_profile` varchar(50) NOT NULL,
+ `project_profile_repo_uuid` varchar(36) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `projects_repos` (
+ `created_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL,
+ `deleted_at` datetime DEFAULT NULL,
+ `deleted` tinyint(1) DEFAULT NULL,
+ `id` int(11) NOT NULL,
+ `repo_uuid` varchar(36) DEFAULT NULL,
+ `project_uuid` varchar(36) DEFAULT NULL,
+ `build` tinyint(1) DEFAULT NULL,
+ `test` tinyint(1) NOT NULL,
+ `repoman` tinyint(1) NOT NULL,
+ `qa` tinyint(1) NOT NULL,
+ `depclean` tinyint(1) NOT NULL,
+ `auto` tinyint(1) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `repos` (
+ `created_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL,
+ `deleted_at` datetime DEFAULT NULL,
+ `deleted` tinyint(1) DEFAULT NULL,
+ `name` varchar(255) NOT NULL,
+ `uuid` varchar(36) NOT NULL,
+ `src_url` varchar(255) NOT NULL,
+ `description` text DEFAULT NULL,
+ `auto` tinyint(1) DEFAULT NULL,
+ `status` enum('failed','completed','in-progress','waiting') DEFAULT NULL,
+ `repo_type` enum('project','ebuild') DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `restrictions` (
+ `created_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL,
+ `deleted_at` datetime DEFAULT NULL,
+ `deleted` tinyint(1) DEFAULT NULL,
+ `id` int(11) NOT NULL,
+ `restriction` varchar(255) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `services` (
+ `created_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL,
+ `deleted_at` datetime DEFAULT NULL,
+ `deleted` tinyint(1) DEFAULT NULL,
+ `id` int(11) NOT NULL,
+ `uuid` varchar(36) DEFAULT NULL,
+ `host` varchar(255) DEFAULT NULL,
+ `binary` varchar(255) DEFAULT NULL,
+ `topic` varchar(255) DEFAULT NULL,
+ `report_count` int(11) NOT NULL,
+ `disabled` tinyint(1) DEFAULT NULL,
+ `disabled_reason` varchar(255) DEFAULT NULL,
+ `last_seen_up` datetime DEFAULT NULL,
+ `forced_down` varchar(255) DEFAULT NULL,
+ `version` int(11) DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `services_repos` (
+ `created_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL,
+ `deleted_at` datetime DEFAULT NULL,
+ `deleted` tinyint(1) DEFAULT NULL,
+ `id` int(11) NOT NULL,
+ `repo_uuid` varchar(36) DEFAULT NULL,
+ `service_uuid` varchar(36) DEFAULT NULL,
+ `auto` tinyint(1) NOT NULL,
+ `status` enum('failed','completed','in-progress','waiting','stopped','rebuild_db','update_db') NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `tasks` (
+ `created_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL,
+ `deleted_at` datetime DEFAULT NULL,
+ `deleted` tinyint(1) DEFAULT NULL,
+ `uuid` varchar(36) NOT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `service_uuid` varchar(36) DEFAULT NULL,
+ `repet` tinyint(1) DEFAULT NULL,
+ `run` datetime DEFAULT NULL,
+ `status` enum('failed','completed','in-progress','waiting') DEFAULT NULL,
+ `last` datetime DEFAULT NULL,
+ `priority` int(11) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `users` (
+ `id` int(10) NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `name` varchar(255) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `uses` (
+ `created_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL,
+ `deleted_at` datetime DEFAULT NULL,
+ `deleted` tinyint(1) DEFAULT NULL,
+ `id` int(11) NOT NULL,
+ `flag` varchar(255) NOT NULL,
+ `description` text DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+ALTER TABLE `builds_uses`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `builds_uses_uuid_fkey` (`build_uuid`) USING BTREE,
+ ADD KEY `builds_uses_use_id_fkey` (`use_id`) USING BTREE;
+
+ALTER TABLE `categories`
+ ADD PRIMARY KEY (`uuid`);
+
+ALTER TABLE `categories_metadata`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `categories_metadata_uuid_fkey` (`category_uuid`) USING BTREE;
+
+ALTER TABLE `ebuilds`
+ ADD PRIMARY KEY (`uuid`),
+ ADD KEY `ebuilds_package_uuid_fkey` (`package_uuid`);
+
+ALTER TABLE `ebuilds_keywords`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `ebuilds_keywords_keyword_id_fkey` (`keyword_id`),
+ ADD KEY `ebuild_uuid` (`ebuild_uuid`) USING BTREE;
+
+ALTER TABLE `ebuilds_metadata`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `ebuild_uuid` (`ebuild_uuid`) USING BTREE;
+
+ALTER TABLE `ebuilds_restrictions`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `ebuilds_restrictions_uuid_fkey` (`ebuild_uuid`),
+ ADD KEY `ebuilds_restrictions_restriction_id_fkey` (`restriction_id`);
+
+ALTER TABLE `ebuilds_uses`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `ebuilds_uses_uuid_fkey` (`ebuild_uuid`),
+ ADD KEY `ebuilds_uses_use_id_fkey` (`use_id`);
+
+ALTER TABLE `emails`
+ ADD PRIMARY KEY (`id`);
+
+ALTER TABLE `keywords`
+ ADD PRIMARY KEY (`id`);
+
+ALTER TABLE `migrate_version`
+ ADD PRIMARY KEY (`repository_id`);
+
+ALTER TABLE `packages`
+ ADD PRIMARY KEY (`uuid`),
+ ADD KEY `packages_category_uuid_fkey` (`category_uuid`),
+ ADD KEY `packages_repo_uuid_fkey` (`repo_uuid`);
+
+ALTER TABLE `packages_emails`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `packages_email_email_id_fkey` (`email_id`),
+ ADD KEY `package_uuid` (`package_uuid`) USING BTREE;
+
+ALTER TABLE `packages_metadata`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `packages_metadata_uuid_fkey` (`package_uuid`) USING BTREE;
+
+ALTER TABLE `projects`
+ ADD PRIMARY KEY (`uuid`),
+ ADD UNIQUE KEY `name` (`name`);
+
+ALTER TABLE `projects_builds`
+ ADD PRIMARY KEY (`uuid`);
+
+ALTER TABLE `projects_metadata`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `projects_metadata_uuid_fkey` (`project_uuid`) USING BTREE,
+ ADD KEY `project_repo_uuid` (`project_repo_uuid`),
+ ADD KEY `project_profile_repo_uuid` (`project_profile_repo_uuid`);
+
+ALTER TABLE `projects_repos`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `projects_repos_repo_uuid_fkey` (`repo_uuid`),
+ ADD KEY `projects_repos_project_uuid_fkey` (`project_uuid`);
+
+ALTER TABLE `repos`
+ ADD PRIMARY KEY (`uuid`);
+
+ALTER TABLE `restrictions`
+ ADD PRIMARY KEY (`id`);
+
+ALTER TABLE `services`
+ ADD PRIMARY KEY (`id`),
+ ADD UNIQUE KEY `uniq_services0host0topic0deleted` (`host`,`topic`,`deleted`),
+ ADD UNIQUE KEY `uniq_services0host0binary0deleted` (`host`,`binary`,`deleted`);
+
+ALTER TABLE `services_repos`
+ ADD PRIMARY KEY (`id`),
+ ADD KEY `projects_repos_repo_uuid_fkey` (`repo_uuid`),
+ ADD KEY `projects_repos_project_uuid_fkey` (`service_uuid`);
+
+ALTER TABLE `tasks`
+ ADD PRIMARY KEY (`uuid`),
+ ADD UNIQUE KEY `uuid` (`uuid`);
+
+ALTER TABLE `users`
+ ADD PRIMARY KEY (`id`);
+
+ALTER TABLE `uses`
+ ADD PRIMARY KEY (`id`);
+
+
+ALTER TABLE `builds_uses`
+ MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
+
+ALTER TABLE `categories_metadata`
+ MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
+
+ALTER TABLE `ebuilds_keywords`
+ MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
+
+ALTER TABLE `ebuilds_metadata`
+ MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
+
+ALTER TABLE `ebuilds_restrictions`
+ MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
+
+ALTER TABLE `ebuilds_uses`
+ MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
+
+ALTER TABLE `emails`
+ MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
+
+ALTER TABLE `keywords`
+ MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
+
+ALTER TABLE `packages_emails`
+ MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
+
+ALTER TABLE `packages_metadata`
+ MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
+
+ALTER TABLE `projects_metadata`
+ MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
+
+ALTER TABLE `projects_repos`
+ MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
+
+ALTER TABLE `restrictions`
+ MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
+
+ALTER TABLE `services`
+ MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
+
+ALTER TABLE `services_repos`
+ MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
+
+ALTER TABLE `users`
+ MODIFY `id` int(10) NOT NULL AUTO_INCREMENT;
+
+ALTER TABLE `uses`
+ MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
+
+
+ALTER TABLE `categories_metadata`
+ ADD CONSTRAINT `categories_metadata_ibfk_1` FOREIGN KEY (`category_uuid`) REFERENCES `categories` (`uuid`),
+ ADD CONSTRAINT `categories_metadata_uuid_fkey` FOREIGN KEY (`category_uuid`) REFERENCES `categories` (`uuid`);
+
+ALTER TABLE `ebuilds`
+ ADD CONSTRAINT `ebuilds_ibfk_1` FOREIGN KEY (`package_uuid`) REFERENCES `packages` (`uuid`),
+ ADD CONSTRAINT `ebuilds_package_uuid_fkey` FOREIGN KEY (`package_uuid`) REFERENCES `packages` (`uuid`);
+
+ALTER TABLE `ebuilds_keywords`
+ ADD CONSTRAINT `ebuilds_keywords_ibfk_1` FOREIGN KEY (`ebuild_uuid`) REFERENCES `ebuilds` (`uuid`),
+ ADD CONSTRAINT `ebuilds_keywords_ibfk_2` FOREIGN KEY (`keyword_id`) REFERENCES `keywords` (`id`),
+ ADD CONSTRAINT `ebuilds_keywords_keyword_id_fkey` FOREIGN KEY (`keyword_id`) REFERENCES `keywords` (`id`),
+ ADD CONSTRAINT `ebuilds_keywords_uuid_fkey` FOREIGN KEY (`ebuild_uuid`) REFERENCES `ebuilds` (`uuid`);
+
+ALTER TABLE `ebuilds_metadata`
+ ADD CONSTRAINT `ebuilds_metadata_ibfk_1` FOREIGN KEY (`ebuild_uuid`) REFERENCES `ebuilds` (`uuid`);
+
+ALTER TABLE `ebuilds_restrictions`
+ ADD CONSTRAINT `ebuilds_restrictions_ibfk_1` FOREIGN KEY (`ebuild_uuid`) REFERENCES `ebuilds` (`uuid`),
+ ADD CONSTRAINT `ebuilds_restrictions_ibfk_2` FOREIGN KEY (`restriction_id`) REFERENCES `restrictions` (`id`),
+ ADD CONSTRAINT `ebuilds_restrictions_restriction_id_fkey` FOREIGN KEY (`restriction_id`) REFERENCES `restrictions` (`id`),
+ ADD CONSTRAINT `ebuilds_restrictions_uuid_fkey` FOREIGN KEY (`ebuild_uuid`) REFERENCES `ebuilds` (`uuid`);
+
+ALTER TABLE `ebuilds_uses`
+ ADD CONSTRAINT `ebuilds_uses_ibfk_1` FOREIGN KEY (`ebuild_uuid`) REFERENCES `ebuilds` (`uuid`),
+ ADD CONSTRAINT `ebuilds_uses_ibfk_2` FOREIGN KEY (`use_id`) REFERENCES `uses` (`id`),
+ ADD CONSTRAINT `ebuilds_uses_use_id_fkey` FOREIGN KEY (`use_id`) REFERENCES `uses` (`id`),
+ ADD CONSTRAINT `ebuilds_uses_uuid_fkey` FOREIGN KEY (`ebuild_uuid`) REFERENCES `ebuilds` (`uuid`);
+
+ALTER TABLE `packages`
+ ADD CONSTRAINT `packages_category_uuid_fkey` FOREIGN KEY (`category_uuid`) REFERENCES `categories` (`uuid`),
+ ADD CONSTRAINT `packages_ibfk_1` FOREIGN KEY (`category_uuid`) REFERENCES `categories` (`uuid`),
+ ADD CONSTRAINT `packages_ibfk_2` FOREIGN KEY (`repo_uuid`) REFERENCES `repos` (`uuid`),
+ ADD CONSTRAINT `packages_repo_uuid_fkey` FOREIGN KEY (`repo_uuid`) REFERENCES `repos` (`uuid`);
+
+ALTER TABLE `packages_emails`
+ ADD CONSTRAINT `packages_email_email_id_fkey` FOREIGN KEY (`email_id`) REFERENCES `emails` (`id`),
+ ADD CONSTRAINT `packages_emails_ibfk_1` FOREIGN KEY (`package_uuid`) REFERENCES `packages` (`uuid`),
+ ADD CONSTRAINT `packages_emails_ibfk_2` FOREIGN KEY (`email_id`) REFERENCES `emails` (`id`);
+
+ALTER TABLE `packages_metadata`
+ ADD CONSTRAINT `packages_metadata_ibfk_1` FOREIGN KEY (`package_uuid`) REFERENCES `packages` (`uuid`),
+ ADD CONSTRAINT `packages_metadata_uuid_fkey` FOREIGN KEY (`package_uuid`) REFERENCES `packages` (`uuid`);
+
+ALTER TABLE `projects_metadata`
+ ADD CONSTRAINT `projects_metadata_ibfk_1` FOREIGN KEY (`project_uuid`) REFERENCES `projects` (`uuid`),
+ ADD CONSTRAINT `projects_metadata_profile_repo_uuid_fkey` FOREIGN KEY (`project_profile_repo_uuid`) REFERENCES `repos` (`uuid`),
+ ADD CONSTRAINT `projects_metadata_repo_uuid_fkey` FOREIGN KEY (`project_repo_uuid`) REFERENCES `repos` (`uuid`),
+ ADD CONSTRAINT `projects_metadata_uuid_fkey` FOREIGN KEY (`project_uuid`) REFERENCES `projects` (`uuid`);
+
+ALTER TABLE `projects_repos`
+ ADD CONSTRAINT `projects_repos_ibfk_1` FOREIGN KEY (`repo_uuid`) REFERENCES `repos` (`uuid`),
+ ADD CONSTRAINT `projects_repos_ibfk_2` FOREIGN KEY (`project_uuid`) REFERENCES `projects` (`uuid`),
+ ADD CONSTRAINT `projects_repos_project_uuid_fkey` FOREIGN KEY (`project_uuid`) REFERENCES `projects` (`uuid`),
+ ADD CONSTRAINT `projects_repos_repo_uuid_fkey` FOREIGN KEY (`repo_uuid`) REFERENCES `repos` (`uuid`);
+COMMIT;
+
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;