[411390] Configure repo in MAAS
- Add a new action for ConfigureNodeProvisioner to configure a node provisioner with site-wide configuration - Add a maasdriver action to configure repositories Change-Id: I8e216a269b300159b7cc26c3a4542e8b61496dc7
This commit is contained in:
parent
cc77125953
commit
6dad448ca6
@ -42,7 +42,8 @@ class NodeDriver(ProviderDriver):
|
|||||||
hd_fields.OrchestratorAction.ApplyNodePlatform,
|
hd_fields.OrchestratorAction.ApplyNodePlatform,
|
||||||
hd_fields.OrchestratorAction.DeployNode,
|
hd_fields.OrchestratorAction.DeployNode,
|
||||||
hd_fields.OrchestratorAction.DestroyNode,
|
hd_fields.OrchestratorAction.DestroyNode,
|
||||||
hd_fields.OrchestratorAction.ConfigureUserCredentials
|
hd_fields.OrchestratorAction.ConfigureUserCredentials,
|
||||||
|
hd_fields.OrchestratorAction.ConfigureNodeProvisioner
|
||||||
]
|
]
|
||||||
|
|
||||||
def execute_task(self, task_id):
|
def execute_task(self, task_id):
|
||||||
|
@ -38,6 +38,7 @@ import drydock_provisioner.drivers.node.maasdriver.models.boot_resource as maas_
|
|||||||
import drydock_provisioner.drivers.node.maasdriver.models.rack_controller as maas_rack
|
import drydock_provisioner.drivers.node.maasdriver.models.rack_controller as maas_rack
|
||||||
import drydock_provisioner.drivers.node.maasdriver.models.partition as maas_partition
|
import drydock_provisioner.drivers.node.maasdriver.models.partition as maas_partition
|
||||||
import drydock_provisioner.drivers.node.maasdriver.models.volumegroup as maas_vg
|
import drydock_provisioner.drivers.node.maasdriver.models.volumegroup as maas_vg
|
||||||
|
import drydock_provisioner.drivers.node.maasdriver.models.repository as maas_repo
|
||||||
|
|
||||||
|
|
||||||
class BaseMaasAction(BaseAction):
|
class BaseMaasAction(BaseAction):
|
||||||
@ -618,6 +619,113 @@ class CreateNetworkTemplate(BaseMaasAction):
|
|||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigureNodeProvisioner(BaseMaasAction):
|
||||||
|
"""Action for configuring site-wide node provisioner options."""
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.task.set_status(hd_fields.TaskStatus.Running)
|
||||||
|
self.task.save()
|
||||||
|
|
||||||
|
try:
|
||||||
|
site_design = self._load_site_design()
|
||||||
|
except errors.OrchestratorError:
|
||||||
|
self.task.add_status_msg(
|
||||||
|
msg="Error loading site design.",
|
||||||
|
error=True,
|
||||||
|
ctx='NA',
|
||||||
|
ctx_type='NA')
|
||||||
|
self.task.set_status(hd_fields.TaskStatus.Complete)
|
||||||
|
self.task.failure()
|
||||||
|
self.task.save()
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
current_repos = maas_repo.Repositories(self.maas_client)
|
||||||
|
current_repos.refresh()
|
||||||
|
except Exception as ex:
|
||||||
|
self.logger.debug("Error accessing the MaaS API.", exc_info=ex)
|
||||||
|
self.task.set_status(hd_fields.TaskStatus.Complete)
|
||||||
|
self.task.failure()
|
||||||
|
self.task.add_status_msg(
|
||||||
|
msg='Error accessing MaaS SshKeys API',
|
||||||
|
error=True,
|
||||||
|
ctx='NA',
|
||||||
|
ctx_type='NA')
|
||||||
|
self.task.save()
|
||||||
|
return
|
||||||
|
|
||||||
|
site_model = site_design.get_site()
|
||||||
|
|
||||||
|
repo_list = getattr(site_model, 'repositories', None) or []
|
||||||
|
|
||||||
|
if repo_list:
|
||||||
|
for r in repo_list:
|
||||||
|
try:
|
||||||
|
existing_repo = current_repos.singleton({
|
||||||
|
'name': r.get_id()
|
||||||
|
})
|
||||||
|
new_repo = self.create_maas_repo(self.maas_client, r)
|
||||||
|
if existing_repo:
|
||||||
|
new_repo.resource_id = existing_repo.resource_id
|
||||||
|
new_repo.update()
|
||||||
|
msg = "Updating repository definition for %s." % (
|
||||||
|
r.name)
|
||||||
|
self.logger.debug(msg)
|
||||||
|
self.task.add_status_msg(
|
||||||
|
msg=msg, error=False, ctx='NA', ctx_type='NA')
|
||||||
|
self.task.success()
|
||||||
|
else:
|
||||||
|
new_repo = current_repos.add(new_repo)
|
||||||
|
msg = "Adding repository definition for %s." % (r.name)
|
||||||
|
self.logger.debug(msg)
|
||||||
|
self.task.add_status_msg(
|
||||||
|
msg=msg, error=False, ctx='NA', ctx_type='NA')
|
||||||
|
self.task.success()
|
||||||
|
except Exception as ex:
|
||||||
|
msg = "Error adding repository to MaaS configuration: %s" % str(
|
||||||
|
ex)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
self.task.add_status_msg(
|
||||||
|
msg=msg, error=True, ctx='NA', ctx_type='NA')
|
||||||
|
self.task.failure()
|
||||||
|
else:
|
||||||
|
msg = ("No repositories to add, no work to do.")
|
||||||
|
self.logger.debug(msg)
|
||||||
|
self.task.success()
|
||||||
|
self.task.add_status_msg(
|
||||||
|
msg=msg, error=False, ctx='NA', ctx_type='NA')
|
||||||
|
|
||||||
|
self.task.set_status(hd_fields.TaskStatus.Complete)
|
||||||
|
self.task.save()
|
||||||
|
return
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_maas_repo(api_client, repo_obj):
|
||||||
|
"""Create a MAAS model of a repo based of a Drydock model.
|
||||||
|
|
||||||
|
If resource_id is specified, assign it the resource_id so the new instance
|
||||||
|
can be used to update an existing repo.
|
||||||
|
|
||||||
|
:param api_client: A MAAS API client configured to connect to MAAS
|
||||||
|
:param repo_obj: Instance of objects.Repository
|
||||||
|
"""
|
||||||
|
model_fields = dict()
|
||||||
|
if repo_obj.distributions:
|
||||||
|
model_fields['distributions'] = ','.join(repo_obj.distributions)
|
||||||
|
if repo_obj.components:
|
||||||
|
model_fields['components'] = ','.join(repo_obj.components)
|
||||||
|
if repo_obj.arches:
|
||||||
|
model_fields['arches'] = ','.join(repo_obj.arches)
|
||||||
|
|
||||||
|
model_fields['key'] = repo_obj.gpgkey
|
||||||
|
|
||||||
|
for k in ['name', 'url']:
|
||||||
|
model_fields[k] = getattr(repo_obj, k)
|
||||||
|
|
||||||
|
repo_model = maas_repo.Repository(api_client, **model_fields)
|
||||||
|
return repo_model
|
||||||
|
|
||||||
|
|
||||||
class ConfigureUserCredentials(BaseMaasAction):
|
class ConfigureUserCredentials(BaseMaasAction):
|
||||||
"""Action for configuring user public keys."""
|
"""Action for configuring user public keys."""
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ from .actions.node import ApplyNodeNetworking
|
|||||||
from .actions.node import ApplyNodePlatform
|
from .actions.node import ApplyNodePlatform
|
||||||
from .actions.node import ApplyNodeStorage
|
from .actions.node import ApplyNodeStorage
|
||||||
from .actions.node import DeployNode
|
from .actions.node import DeployNode
|
||||||
|
from .actions.node import ConfigureNodeProvisioner
|
||||||
|
|
||||||
|
|
||||||
class MaasNodeDriver(NodeDriver):
|
class MaasNodeDriver(NodeDriver):
|
||||||
@ -87,6 +88,8 @@ class MaasNodeDriver(NodeDriver):
|
|||||||
ApplyNodeStorage,
|
ApplyNodeStorage,
|
||||||
hd_fields.OrchestratorAction.DeployNode:
|
hd_fields.OrchestratorAction.DeployNode:
|
||||||
DeployNode,
|
DeployNode,
|
||||||
|
hd_fields.OrchestratorAction.ConfigureNodeProvisioner:
|
||||||
|
ConfigureNodeProvisioner,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
# Copyright 2018 AT&T Intellectual Property. All other 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.
|
||||||
|
"""Model for MaaS Package Repository resources."""
|
||||||
|
|
||||||
|
import drydock_provisioner.drivers.node.maasdriver.models.base as model_base
|
||||||
|
|
||||||
|
|
||||||
|
class Repository(model_base.ResourceBase):
|
||||||
|
|
||||||
|
resource_url = 'package-repositories/{resource_id}/'
|
||||||
|
fields = [
|
||||||
|
'resource_id', 'name', 'url', 'distributions', 'components', 'arches',
|
||||||
|
'key', 'enabled'
|
||||||
|
]
|
||||||
|
json_fields = [
|
||||||
|
'name', 'url', 'distributions', 'components', 'arches', 'key',
|
||||||
|
'enabled'
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, api_client, **kwargs):
|
||||||
|
super().__init__(api_client, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Repositories(model_base.ResourceCollectionBase):
|
||||||
|
|
||||||
|
collection_url = 'package-repositories/'
|
||||||
|
collection_resource = Repository
|
||||||
|
|
||||||
|
def __init__(self, api_client):
|
||||||
|
super().__init__(api_client)
|
@ -47,6 +47,7 @@ class OrchestratorAction(BaseDrydockEnum):
|
|||||||
CreateStorageTemplate = 'create_storage_template'
|
CreateStorageTemplate = 'create_storage_template'
|
||||||
CreateBootMedia = 'create_boot_media'
|
CreateBootMedia = 'create_boot_media'
|
||||||
ConfigureUserCredentials = 'configure_user_credentials'
|
ConfigureUserCredentials = 'configure_user_credentials'
|
||||||
|
ConfigureNodeProvisioner = 'configure_node_provisioner'
|
||||||
PrepareHardwareConfig = 'prepare_hardware_config'
|
PrepareHardwareConfig = 'prepare_hardware_config'
|
||||||
IdentifyNode = 'identify_node'
|
IdentifyNode = 'identify_node'
|
||||||
ConfigureHardware = 'configure_hardware'
|
ConfigureHardware = 'configure_hardware'
|
||||||
@ -69,7 +70,8 @@ class OrchestratorAction(BaseDrydockEnum):
|
|||||||
PowerCycleNode, InterrogateOob, CreateNetworkTemplate,
|
PowerCycleNode, InterrogateOob, CreateNetworkTemplate,
|
||||||
CreateStorageTemplate, CreateBootMedia, PrepareHardwareConfig,
|
CreateStorageTemplate, CreateBootMedia, PrepareHardwareConfig,
|
||||||
ConfigureHardware, InterrogateNode, ApplyNodeNetworking,
|
ConfigureHardware, InterrogateNode, ApplyNodeNetworking,
|
||||||
ApplyNodeStorage, ApplyNodePlatform, DeployNode, DestroyNode)
|
ApplyNodeStorage, ApplyNodePlatform, DeployNode, DestroyNode,
|
||||||
|
ConfigureNodeProvisioner)
|
||||||
|
|
||||||
|
|
||||||
class OrchestratorActionField(fields.BaseEnumField):
|
class OrchestratorActionField(fields.BaseEnumField):
|
||||||
|
@ -305,12 +305,37 @@ class PrepareSite(BaseAction):
|
|||||||
|
|
||||||
self.step_networktemplate(driver)
|
self.step_networktemplate(driver)
|
||||||
self.step_usercredentials(driver)
|
self.step_usercredentials(driver)
|
||||||
|
self.step_configureprovisioner(driver)
|
||||||
|
|
||||||
self.task.align_result()
|
self.task.align_result()
|
||||||
self.task.set_status(hd_fields.TaskStatus.Complete)
|
self.task.set_status(hd_fields.TaskStatus.Complete)
|
||||||
self.task.save()
|
self.task.save()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def step_configureprovisioner(self, driver):
|
||||||
|
"""Run the ConfigureNodeProvisioner step of this action.
|
||||||
|
|
||||||
|
:param driver: The driver instance to use for execution.
|
||||||
|
"""
|
||||||
|
config_prov_task = self.orchestrator.create_task(
|
||||||
|
design_ref=self.task.design_ref,
|
||||||
|
action=hd_fields.OrchestratorAction.ConfigureNodeProvisioner)
|
||||||
|
self.task.register_subtask(config_prov_task)
|
||||||
|
|
||||||
|
self.logger.info(
|
||||||
|
"Starting node drvier task %s to configure the provisioner" %
|
||||||
|
(config_prov_task.get_id()))
|
||||||
|
|
||||||
|
driver.execute_task(config_prov_task.get_id())
|
||||||
|
|
||||||
|
self.task.add_status_msg(
|
||||||
|
msg="Collected subtask %s" % str(config_prov_task.get_id()),
|
||||||
|
error=False,
|
||||||
|
ctx=str(config_prov_task.get_id()),
|
||||||
|
ctx_type='task')
|
||||||
|
self.logger.info("Node driver task %s:%s is complete." %
|
||||||
|
(config_prov_task.get_id(), config_prov_task.action))
|
||||||
|
|
||||||
def step_networktemplate(self, driver):
|
def step_networktemplate(self, driver):
|
||||||
"""Run the CreateNetworkTemplate step of this action.
|
"""Run the CreateNetworkTemplate step of this action.
|
||||||
|
|
||||||
|
@ -379,6 +379,9 @@ class Orchestrator(object):
|
|||||||
for ba in site_design.bootactions:
|
for ba in site_design.bootactions:
|
||||||
nf = ba.node_filter
|
nf = ba.node_filter
|
||||||
target_nodes = self.process_node_filter(nf, site_design)
|
target_nodes = self.process_node_filter(nf, site_design)
|
||||||
|
if not target_nodes:
|
||||||
|
ba.target_nodes = []
|
||||||
|
else:
|
||||||
ba.target_nodes = [x.get_id() for x in target_nodes]
|
ba.target_nodes = [x.get_id() for x in target_nodes]
|
||||||
|
|
||||||
def process_node_filter(self, node_filter, site_design):
|
def process_node_filter(self, node_filter, site_design):
|
||||||
|
32
tests/integration/postgres/test_action_config_node_prov.py
Normal file
32
tests/integration/postgres/test_action_config_node_prov.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Copyright 2018 AT&T Intellectual Property. All other 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.
|
||||||
|
"""Testing the ConfigureNodeProvisioner action."""
|
||||||
|
from drydock_provisioner import objects
|
||||||
|
from drydock_provisioner.drivers.node.maasdriver.actions.node import ConfigureNodeProvisioner
|
||||||
|
|
||||||
|
|
||||||
|
class TestActionConfigureNodeProvisioner(object):
|
||||||
|
def test_create_maas_repo(selfi, mocker):
|
||||||
|
distribution_list = ['xenial', 'xenial-updates']
|
||||||
|
|
||||||
|
repo_obj = objects.Repository(name='foo',
|
||||||
|
url='https://foo.com/repo',
|
||||||
|
repo_type='apt',
|
||||||
|
gpgkey="-----START STUFF----\nSTUFF\n-----END STUFF----\n",
|
||||||
|
distributions=distribution_list,
|
||||||
|
components=['main'])
|
||||||
|
|
||||||
|
maas_model = ConfigureNodeProvisioner.create_maas_repo(mocker.MagicMock(), repo_obj)
|
||||||
|
|
||||||
|
assert maas_model.distributions == ",".join(distribution_list)
|
@ -31,7 +31,8 @@ class TestClass(object):
|
|||||||
assert len(design_data.host_profiles) == 2
|
assert len(design_data.host_profiles) == 2
|
||||||
assert len(design_data.baremetal_nodes) == 3
|
assert len(design_data.baremetal_nodes) == 3
|
||||||
|
|
||||||
def test_ingest_deckhand_repos(self, input_files, setup, deckhand_ingester):
|
def test_ingest_deckhand_repos(self, input_files, setup,
|
||||||
|
deckhand_ingester):
|
||||||
"""Test that the ingester properly parses repo definitions."""
|
"""Test that the ingester properly parses repo definitions."""
|
||||||
input_file = input_files.join("deckhand_fullsite.yaml")
|
input_file = input_files.join("deckhand_fullsite.yaml")
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user