From 9eadb36061054ccbdf78ecb7f858dc2f132872cd Mon Sep 17 00:00:00 2001 From: Tomi Juvonen Date: Fri, 25 Jan 2019 13:57:17 +0200 Subject: [PATCH] One click upgrade Discover all available hosts to be maintained Story: 2004146 Task: #27619 Change-Id: Id11ab3c2f41dfde49d082fb5338182d2c6ede5a5 Signed-off-by: Tomi Juvonen --- fenix/db/api.py | 15 ++++ .../versions/001_initial.py | 2 +- fenix/db/sqlalchemy/models.py | 2 +- fenix/workflow/workflow.py | 12 ++-- fenix/workflow/workflows/default.py | 70 ++++++++++++++++--- 5 files changed, 87 insertions(+), 14 deletions(-) diff --git a/fenix/db/api.py b/fenix/db/api.py index 88bdd9c..cc27005 100644 --- a/fenix/db/api.py +++ b/fenix/db/api.py @@ -143,6 +143,21 @@ def create_hosts(session_id, hostnames): return IMPL.create_hosts(hosts) +def create_hosts_by_details(session_id, hosts_dict_list): + hosts = [] + for hd in hosts_dict_list: + host = {} + host['session_id'] = session_id + host['hostname'] = hd["hostname"] + host['type'] = hd["type"] + host['maintained'] = hd['maintained'] + host['disabled'] = hd['disabled'] + if 'details' in hd: + host['details'] = hd['details'] + hosts.append(host) + return IMPL.create_hosts(hosts) + + def create_projects(session_id, project_ids): projects = [] for project_id in project_ids: diff --git a/fenix/db/migration/alembic_migrations/versions/001_initial.py b/fenix/db/migration/alembic_migrations/versions/001_initial.py index b38ef79..d41dfd9 100644 --- a/fenix/db/migration/alembic_migrations/versions/001_initial.py +++ b/fenix/db/migration/alembic_migrations/versions/001_initial.py @@ -53,7 +53,7 @@ def upgrade(): sa.Column('session_id', sa.String(36), sa.ForeignKey('sessions.session_id')), sa.Column('hostname', sa.String(length=255), nullable=False), - sa.Column('type', sa.String(length=32), nullable=True), + sa.Column('type', sa.String(length=32), nullable=False), sa.Column('maintained', sa.Boolean, default=False), sa.Column('disabled', sa.Boolean, default=False), sa.Column('details', sa.String(length=255), nullable=True), diff --git a/fenix/db/sqlalchemy/models.py b/fenix/db/sqlalchemy/models.py index 152e540..d7e39dd 100644 --- a/fenix/db/sqlalchemy/models.py +++ b/fenix/db/sqlalchemy/models.py @@ -79,7 +79,7 @@ class MaintenanceHost(mb.FenixBase): session_id = sa.Column(sa.String(36), sa.ForeignKey('sessions.session_id'), nullable=False) hostname = sa.Column(sa.String(length=255), primary_key=True) - type = sa.Column(sa.String(length=32), nullable=True) + type = sa.Column(sa.String(length=32), nullable=False) maintained = sa.Column(sa.Boolean, default=False) disabled = sa.Column(sa.Boolean, default=False) details = sa.Column(sa.String(length=255), nullable=True) diff --git a/fenix/workflow/workflow.py b/fenix/workflow/workflow.py index 9ebacdf..92a9103 100644 --- a/fenix/workflow/workflow.py +++ b/fenix/workflow/workflow.py @@ -41,11 +41,12 @@ class BaseWorkflow(Thread): self.timer = {} self.session = self._init_session(data) LOG.info('%s: session %s' % (self.session_id, self.session)) - if len(data['hosts']): + self.hosts = [] + if "hosts" in data and data['hosts']: # Hosts given as input, not to be discovered in workflow self.hosts = self.init_hosts(self.convert(data['hosts'])) else: - self.hosts = [] + LOG.info('%s: No hosts as input' % self.session_id) # TBD API to support action plugins # self.actions = self.projects = [] @@ -118,8 +119,9 @@ class BaseWorkflow(Thread): instance_computes.append(instance.host) return [host for host in all_computes if host not in instance_computes] - def get_maintained_hosts(self): - return [host.hostname for host in self.hosts if host.maintained] + def get_maintained_hosts_by_type(self, host_type): + return [host.hostname for host in self.hosts if host.maintained and + host.type == host_type] def get_disabled_hosts(self): return [host for host in self.hosts if host.disabled] @@ -287,6 +289,8 @@ class BaseWorkflow(Thread): def __str__(self): info = 'Instance info:\n' for host in self.hosts: + if host.type != 'compute': + continue info += ('%s:\n' % host.hostname) for project in self.project_names(): instance_ids = ( diff --git a/fenix/workflow/workflows/default.py b/fenix/workflow/workflows/default.py index 29f9c06..2352b72 100644 --- a/fenix/workflow/workflows/default.py +++ b/fenix/workflow/workflows/default.py @@ -48,15 +48,60 @@ class Workflow(BaseWorkflow): nova_version = max_nova_server_ver self.nova = novaclient.Client(nova_version, session=self.auth_session) - self._init_update_hosts() + if not self.hosts: + self.hosts = self._init_hosts_by_services() + else: + self._init_update_hosts() LOG.info("%s: initialized. Nova version %f" % (self.session_id, nova_version)) + def _init_hosts_by_services(self): + LOG.info("%s: Dicovering hosts by Nova services" % self.session_id) + hosts = [] + + controllers = self.nova.services.list(binary='nova-conductor') + for controller in controllers: + host = {} + service_host = str(controller.__dict__.get(u'host')) + host['hostname'] = service_host + host['type'] = 'controller' + if str(controller.__dict__.get(u'status')) == 'disabled': + LOG.error("%s: %s nova-conductor disabled before maintenance" + % (self.session_id, service_host)) + raise Exception("%s: %s already disabled" + % (self.session_id, service_host)) + host['disabled'] = False + host['details'] = str(controller.__dict__.get(u'id')) + host['maintained'] = False + hosts.append(host) + + computes = self.nova.services.list(binary='nova-compute') + for compute in computes: + host = {} + service_host = str(compute.__dict__.get(u'host')) + host['hostname'] = service_host + host['type'] = 'compute' + if str(compute.__dict__.get(u'status')) == 'disabled': + LOG.error("%s: %s nova-compute disabled before maintenance" + % (self.session_id, service_host)) + raise Exception("%s: %s already disabled" + % (self.session_id, service_host)) + host['disabled'] = False + host['details'] = str(compute.__dict__.get(u'id')) + host['maintained'] = False + hosts.append(host) + + return db_api.create_hosts_by_details(self.session_id, hosts) + def _init_update_hosts(self): + LOG.info("%s: Update given hosts" % self.session_id) controllers = self.nova.services.list(binary='nova-conductor') computes = self.nova.services.list(binary='nova-compute') + for host in self.hosts: hostname = host.hostname + host.disabled = False + host.maintained = False match = [compute for compute in computes if hostname == compute.host] if match: @@ -161,8 +206,16 @@ class Workflow(BaseWorkflow): if project_id not in project_ids: project_ids.append(project_id) - self.projects = self.init_projects(project_ids) - self.instances = self.add_instances(instances) + if len(project_ids): + self.projects = self.init_projects(project_ids) + else: + LOG.info('%s: No projects on computes under maintenance %s' % + self.session_id) + if len(instances): + self.instances = self.add_instances(instances) + else: + LOG.info('%s: No instances on computes under maintenance %s' % + self.session_id) LOG.info(str(self)) def update_instance(self, project_id, instance_id, instance_name, host, @@ -595,7 +648,7 @@ class Workflow(BaseWorkflow): LOG.info("%s: No empty host to be maintained" % self.session_id) self.session.state = 'MAINTENANCE_FAILED' return - maintained_hosts = self.get_maintained_hosts() + maintained_hosts = self.get_maintained_hosts_by_type('compute') if not maintained_hosts: computes = self.get_compute_hosts() for compute in computes: @@ -641,8 +694,8 @@ class Workflow(BaseWorkflow): self.enable_host_nova_compute(host) LOG.info('MAINTENANCE_COMPLETE host %s' % host) self.host_maintained(host) - maintained_hosts = self.get_maintained_hosts() - if len(maintained_hosts) != len(self.hosts): + maintained_hosts = self.get_maintained_hosts_by_type('compute') + if len(maintained_hosts) != len(self.get_compute_hosts()): # Not all host maintained self.session.state = 'PLANNED_MAINTENANCE' else: @@ -650,8 +703,9 @@ class Workflow(BaseWorkflow): def planned_maintenance(self): LOG.info("%s: planned_maintenance called" % self.session_id) - maintained_hosts = self.get_maintained_hosts() - not_maintained_hosts = ([h.hostname for h in self.hosts if h.hostname + maintained_hosts = self.get_maintained_hosts_by_type('compute') + compute_hosts = self.get_compute_hosts() + not_maintained_hosts = ([host for host in compute_hosts if host not in maintained_hosts]) LOG.info("%s: Not maintained hosts: %s" % (self.session_id, not_maintained_hosts))