Skip to content

Commit

Permalink
add keepalived
Browse files Browse the repository at this point in the history
  • Loading branch information
SK1Y101 committed Jun 13, 2024
1 parent 6fce041 commit 6a3f242
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 1 deletion.
115 changes: 115 additions & 0 deletions anvil-python/anvil/commands/virtual_ip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Copyright (c) 2024 Canonical Ltd.
#
# 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 typing import Any

from sunbeam.clusterd.client import Client
from sunbeam.jobs.juju import JujuHelper
from sunbeam.jobs.steps import (
AddMachineUnitsStep,
DeployMachineApplicationStep,
RemoveMachineUnitStep,
)

from anvil.jobs.manifest import Manifest

APPLICATION = "virtual_ip"
CONFIG_KEY = "TerraformVarsVirtualIPPlan"
VIRTUALIP_APP_TIMEOUT = (
180 # 3 minutes, managing the application should be fast
)
VIRTUALIP_UNIT_TIMEOUT = (
1200 # 15 minutes, adding / removing units can take a long time
)


class DeployVirtualIpApplicationStep(DeployMachineApplicationStep):
"""Deploy VirtualIp application using Terraform"""

def __init__(
self,
client: Client,
manifest: Manifest,
jhelper: JujuHelper,
model: str,
refresh: bool = False,
):
super().__init__(
client,
manifest,
jhelper,
CONFIG_KEY,
APPLICATION,
model,
"virtual-ip-plan",
"Deploy VirtualIp",
"Deploying VirtualIp",
refresh,
)

def get_application_timeout(self) -> int:
return VIRTUALIP_APP_TIMEOUT

def extra_tfvars(self) -> dict[str, Any]:
# TODO: How do we pass the VIP from command line to here?
if virtual_ip := None:
return {"virtual_ip": virtual_ip}
return {}


class AddVirtualIpUnitsStep(AddMachineUnitsStep):
"""Add VirtualIp Unit."""

def __init__(
self,
client: Client,
names: list[str] | str,
jhelper: JujuHelper,
model: str,
):
super().__init__(
client,
names,
jhelper,
CONFIG_KEY,
APPLICATION,
model,
"Add VirtualIp unit",
"Adding VirtualIp unit to machine",
)

def get_unit_timeout(self) -> int:
return VIRTUALIP_UNIT_TIMEOUT


class RemoveVirtualIpUnitStep(RemoveMachineUnitStep):
"""Remove VirtualIp Unit."""

def __init__(
self, client: Client, name: str, jhelper: JujuHelper, model: str
):
super().__init__(
client,
name,
jhelper,
CONFIG_KEY,
APPLICATION,
model,
"Remove VirtualIp unit",
"Removing VirtualIp unit from machine",
)

def get_unit_timeout(self) -> int:
return VIRTUALIP_UNIT_TIMEOUT
12 changes: 11 additions & 1 deletion anvil-python/anvil/jobs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# limitations under the License.

import enum
import ipaddress
from typing import Any

import click
Expand All @@ -25,7 +26,7 @@ class Role(enum.Enum):
"""The role that the current node will play
This determines if the role will be a region node, a rack/agent node,
r database node or a haproxy node. The role will help determine which
database node, or a haproxy node. The role will help determine which
particular services need to be configured and installed on the system.
"""

Expand Down Expand Up @@ -78,3 +79,12 @@ def validate_roles(
return [Role[role.upper()] for role in value]
except KeyError as e:
raise click.BadParameter(str(e))


def validate_ip_address(
ctx: click.core.Context, param: click.core.Option, value: str
) -> str:
try:
return ipaddress.ip_address(value).exploded
except ValueError as e:
raise click.BadParameter(f"{value} is not a valid IP address!")
32 changes: 32 additions & 0 deletions anvil-python/anvil/provider/local/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,16 @@
DeployPostgreSQLApplicationStep,
RemovePostgreSQLUnitStep,
)
from anvil.commands.virtual_ip import (
AddVirtualIpUnitsStep,
DeployVirtualIpApplicationStep,
RemoveVirtualIpUnitStep,
)
from anvil.jobs.checks import SystemRequirementsCheck
from anvil.jobs.common import (
Role,
roles_to_str_list,
validate_ip_address,
validate_roles,
)
from anvil.jobs.juju import CONTROLLER
Expand Down Expand Up @@ -169,12 +175,19 @@ def deployment_type(self) -> tuple[str, type[Deployment]]:
help="Specify additional roles, region or agent, for the "
"bootstrap node. Defaults to the database role.",
)
@click.option(
"--vip",
type=click.String,
callback=validate_ip_address,
help="Specify the Virtual IP address.",
)
@click.pass_context
def bootstrap(
ctx: click.Context,
roles: List[Role],
manifest: Path | None = None,
accept_defaults: bool = False,
vip: str | None = None,
) -> None:
"""Bootstrap the local node.
Expand Down Expand Up @@ -304,6 +317,22 @@ def bootstrap(
)
)

# Deploy keepalived charm
# TODO: How do we pass the VIP from here to the plan?
plan4.append(
TerraformInitStep(manifest_obj.get_tfhelper("virtual-ip-plan"))
)
plan4.append(
DeployVirtualIpApplicationStep(
client, manifest_obj, jhelper, deployment.infrastructure_model
)
)
plan4.append(
AddVirtualIpUnitsStep(
client, fqdn, jhelper, deployment.infrastructure_model
)
)

if is_region_node:
# Deploy MAAS Region charm
plan4.append(
Expand Down Expand Up @@ -568,6 +597,9 @@ def remove(ctx: click.Context, name: str) -> None:
RemoveHAProxyUnitStep(
client, name, jhelper, deployment.infrastructure_model
),
RemoveVirtualIpUnitStep(
client, name, jhelper, deployment.infrastructure_model
),
RemoveMAASRegionUnitStep(
client, name, jhelper, deployment.infrastructure_model
),
Expand Down
14 changes: 14 additions & 0 deletions anvil-python/anvil/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
MAAS_AGENT_CHANNEL = "latest/edge"
POSTGRESQL_CHANNEL = "14/stable"
HAPROXY_CHANNEL = "latest/stable"
VIRTUALIP_CHANNEL = "latest/stable"

MACHINE_CHARMS = {
"maas-region": MAAS_REGION_CHANNEL,
"maas-agent": MAAS_AGENT_CHANNEL,
"haproxy": HAPROXY_CHANNEL,
"postgresql": POSTGRESQL_CHANNEL,
"virtual-ip": VIRTUALIP_CHANNEL,
}
K8S_CHARMS: dict[str, str] = {}

Expand All @@ -35,6 +37,7 @@
"maas-agent-plan": "deploy-maas-agent",
"haproxy-plan": "deploy-haproxy",
"postgresql-plan": "deploy-postgresql",
"virtual-ip-plan": "deploy-virtual-ip",
}

DEPLOY_MAAS_REGION_TFVAR_MAP = {
Expand Down Expand Up @@ -77,9 +80,20 @@
}
}

DEPLOY_VIRTUALIP_TFVAR_MAP = {
"charms": {
"keepalived": {
"channel": "charm_keepalived_channel",
"revision": "charm_keepalived_revision",
"config": "charm_keepalived_config",
}
}
}

MANIFEST_ATTRIBUTES_TFVAR_MAP = {
"maas-region-plan": DEPLOY_MAAS_REGION_TFVAR_MAP,
"maas-agent-plan": DEPLOY_MAAS_AGENT_TFVAR_MAP,
"haproxy-plan": DEPLOY_HAPROXY_TFVAR_MAP,
"postgresql-plan": DEPLOY_POSTGRESQL_TFVAR_MAP,
"keepalive-plan": DEPLOY_VIRTUALIP_TFVAR_MAP,
}
62 changes: 62 additions & 0 deletions cloud/etc/deploy-virtual-ip/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Copyright (c) 2024 Canonical Ltd.
#
# 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.

terraform {

required_providers {
juju = {
source = "juju/juju"
version = "= 0.11.0"
}
}

}

provider "juju" {}

data "juju_model" "machine_model" {
name = var.machine_model
}

resource "juju_application" "keepalived" {
name = "keepalived"
model = data.juju_model.machine_model.name
units = 1

charm {
name = "keepalived"
channel = var.charm_keepalived_channel
revision = var.charm_keepalived_revision
base = "ubuntu@22.04"
}

config = var.charm_keepalived_config
}

resource "juju_relation" "haprox_keepalived" {
provider = data.juju_application.haproxy.name
requirer = data.juju_application.keepalived.name
}

resource "juju_config" "keepalived" {
# do nothing if the vip is not given
count = var.virtual_ip != "" ? 1 : 0
application = data.juju_application.keepalived.name
model = data.juju_model.machine_model.name

config = {
virtual_ip = var.virtual_ip
}
}
49 changes: 49 additions & 0 deletions cloud/etc/deploy-virtual-ip/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Copyright (c) 2024 Canonical Ltd.
#
# 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.

variable "charm_keepalived_channel" {
description = "Operator channel for Virtual Ip deployment"
type = string
default = "14/stable"
}

variable "charm_keepalived_revision" {
description = "Operator channel revision for Virtual Ip deployment"
type = number
default = null
}

variable "charm_keepalived_config" {
description = "Operator config for Virtual Ip deployment"
type = map(string)
default = {}
}

variable "machine_ids" {
description = "List of machine ids to include"
type = list(string)
default = []
}

variable "machine_model" {
description = "Model to deploy to"
type = string
}

variable "virtual_ip" {
description = "The virtual IP address for keepalived"
type = string
default = ""
}

0 comments on commit 6a3f242

Please sign in to comment.