[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.DeployNode,
|
||||
hd_fields.OrchestratorAction.DestroyNode,
|
||||
hd_fields.OrchestratorAction.ConfigureUserCredentials
|
||||
hd_fields.OrchestratorAction.ConfigureUserCredentials,
|
||||
hd_fields.OrchestratorAction.ConfigureNodeProvisioner
|
||||
]
|
||||
|
||||
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.partition as maas_partition
|
||||
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):
|
||||
@ -618,6 +619,113 @@ class CreateNetworkTemplate(BaseMaasAction):
|
||||
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):
|
||||
"""Action for configuring user public keys."""
|
||||
|
||||
|
@ -40,6 +40,7 @@ from .actions.node import ApplyNodeNetworking
|
||||
from .actions.node import ApplyNodePlatform
|
||||
from .actions.node import ApplyNodeStorage
|
||||
from .actions.node import DeployNode
|
||||
from .actions.node import ConfigureNodeProvisioner
|
||||
|
||||
|
||||
class MaasNodeDriver(NodeDriver):
|
||||
@ -87,6 +88,8 @@ class MaasNodeDriver(NodeDriver):
|
||||
ApplyNodeStorage,
|
||||
hd_fields.OrchestratorAction.DeployNode:
|
||||
DeployNode,
|
||||
hd_fields.OrchestratorAction.ConfigureNodeProvisioner:
|
||||
ConfigureNodeProvisioner,
|
||||
}
|
||||
|
||||
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'
|
||||
CreateBootMedia = 'create_boot_media'
|
||||
ConfigureUserCredentials = 'configure_user_credentials'
|
||||
ConfigureNodeProvisioner = 'configure_node_provisioner'
|
||||
PrepareHardwareConfig = 'prepare_hardware_config'
|
||||
IdentifyNode = 'identify_node'
|
||||
ConfigureHardware = 'configure_hardware'
|
||||
@ -69,7 +70,8 @@ class OrchestratorAction(BaseDrydockEnum):
|
||||
PowerCycleNode, InterrogateOob, CreateNetworkTemplate,
|
||||
CreateStorageTemplate, CreateBootMedia, PrepareHardwareConfig,
|
||||
ConfigureHardware, InterrogateNode, ApplyNodeNetworking,
|
||||
ApplyNodeStorage, ApplyNodePlatform, DeployNode, DestroyNode)
|
||||
ApplyNodeStorage, ApplyNodePlatform, DeployNode, DestroyNode,
|
||||
ConfigureNodeProvisioner)
|
||||
|
||||
|
||||
class OrchestratorActionField(fields.BaseEnumField):
|
||||
|
@ -305,12 +305,37 @@ class PrepareSite(BaseAction):
|
||||
|
||||
self.step_networktemplate(driver)
|
||||
self.step_usercredentials(driver)
|
||||
self.step_configureprovisioner(driver)
|
||||
|
||||
self.task.align_result()
|
||||
self.task.set_status(hd_fields.TaskStatus.Complete)
|
||||
self.task.save()
|
||||
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):
|
||||
"""Run the CreateNetworkTemplate step of this action.
|
||||
|
||||
|
@ -379,6 +379,9 @@ class Orchestrator(object):
|
||||
for ba in site_design.bootactions:
|
||||
nf = ba.node_filter
|
||||
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]
|
||||
|
||||
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.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."""
|
||||
input_file = input_files.join("deckhand_fullsite.yaml")
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user