-
Notifications
You must be signed in to change notification settings - Fork 430
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial Cluster Actuator Implementation #17
Changes from all commits
8b53c8d
d78b937
ab26008
6050f2d
293a094
d5d29bd
25ece3b
fa302b1
4913586
78e24b5
be783ba
28bc476
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,30 +14,137 @@ limitations under the License. | |
package cluster | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"log" | ||
"os" | ||
|
||
"github.com/Azure/go-autorest/autorest/azure/auth" | ||
"github.com/golang/glog" | ||
"github.com/joho/godotenv" | ||
azureconfigv1 "github.com/platform9/azure-provider/cloud/azure/providerconfig/v1alpha1" | ||
"github.com/platform9/azure-provider/cloud/azure/services" | ||
"github.com/platform9/azure-provider/cloud/azure/services/network" | ||
"github.com/platform9/azure-provider/cloud/azure/services/resourcemanagement" | ||
clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" | ||
client "sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset/typed/cluster/v1alpha1" | ||
) | ||
|
||
type AzureClusterClient struct { | ||
clusterClient client.ClusterInterface | ||
services *services.AzureClients | ||
clusterClient client.ClusterInterface | ||
azureProviderConfigCodec *azureconfigv1.AzureProviderConfigCodec | ||
} | ||
|
||
type ClusterActuatorParams struct { | ||
ClusterClient client.ClusterInterface | ||
Services *services.AzureClients | ||
} | ||
|
||
func NewClusterActuator(params ClusterActuatorParams) (*AzureClusterClient, error) { | ||
_, azureProviderConfigCodec, err := azureconfigv1.NewSchemeAndCodecs() | ||
if err != nil { | ||
return nil, err | ||
} | ||
azureServicesClients, err := azureServicesClientOrDefault(params) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &AzureClusterClient{ | ||
clusterClient: params.ClusterClient, | ||
services: azureServicesClients, | ||
clusterClient: params.ClusterClient, | ||
azureProviderConfigCodec: azureProviderConfigCodec, | ||
}, nil | ||
} | ||
|
||
func (azure *AzureClusterClient) Reconcile(cluster *clusterv1.Cluster) error { | ||
return fmt.Errorf("NYI: Cluster Reconciles are not yet supported") | ||
glog.Infof("Reconciling cluster %v.", cluster.Name) | ||
|
||
clusterConfig, err := azure.azureProviderConfigCodec.ClusterProviderFromProviderConfig(cluster.Spec.ProviderConfig) | ||
if err != nil { | ||
return fmt.Errorf("error loading cluster provider config: %v", err) | ||
} | ||
|
||
_, err = azure.resourcemanagement().CreateOrUpdateGroup(clusterConfig.ResourceGroup, clusterConfig.Location) | ||
if err != nil { | ||
return fmt.Errorf("failed to create or update resource group: %v", err) | ||
} | ||
|
||
networkSGFuture, err := azure.network().CreateOrUpdateNetworkSecurityGroup(clusterConfig.ResourceGroup, "ClusterAPINSG", clusterConfig.Location) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should move this to a const under Refer to that defined constant from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes please. Even better if we have a module just for constants. |
||
if err != nil { | ||
return fmt.Errorf("error creating or updating network security group: %v", err) | ||
} | ||
err = azure.network().WaitForNetworkSGsCreateOrUpdateFuture(*networkSGFuture) | ||
if err != nil { | ||
return fmt.Errorf("error waiting for network security group creation or update: %v", err) | ||
} | ||
return nil | ||
} | ||
|
||
func (azure *AzureClusterClient) Delete(cluster *clusterv1.Cluster) error { | ||
return fmt.Errorf("NYI: Cluster Deletions are not yet supported") | ||
clusterConfig, err := azure.azureProviderConfigCodec.ClusterProviderFromProviderConfig(cluster.Spec.ProviderConfig) | ||
if err != nil { | ||
return fmt.Errorf("error loading cluster provider config: %v", err) | ||
} | ||
resp, err := azure.resourcemanagement().CheckGroupExistence(clusterConfig.ResourceGroup) | ||
if err != nil { | ||
return fmt.Errorf("error checking for resource group existence: %v", err) | ||
} | ||
if resp.StatusCode == 404 { | ||
return fmt.Errorf("resource group %v does not exist", clusterConfig.ResourceGroup) | ||
} | ||
|
||
groupsDeleteFuture, err := azure.resourcemanagement().DeleteGroup(clusterConfig.ResourceGroup) | ||
if err != nil { | ||
return fmt.Errorf("error deleting resource group: %v", err) | ||
} | ||
err = azure.resourcemanagement().WaitForGroupsDeleteFuture(groupsDeleteFuture) | ||
if err != nil { | ||
return fmt.Errorf("error waiting for resource group deletion: %v", err) | ||
} | ||
return nil | ||
} | ||
|
||
func azureServicesClientOrDefault(params ClusterActuatorParams) (*services.AzureClients, error) { | ||
if params.Services != nil { | ||
return params.Services, nil | ||
} | ||
//Parse in environment variables if necessary | ||
if os.Getenv("AZURE_SUBSCRIPTION_ID") == "" { | ||
marwanad marked this conversation as resolved.
Show resolved
Hide resolved
|
||
err := godotenv.Load() | ||
if err == nil && os.Getenv("AZURE_SUBSCRIPTION_ID") == "" { | ||
err = errors.New("AZURE_SUBSCRIPTION_ID: \"\"") | ||
} | ||
if err != nil { | ||
log.Fatalf("Failed to load environment variables: %v", err) | ||
return nil, err | ||
} | ||
} | ||
|
||
authorizer, err := auth.NewAuthorizerFromEnvironment() | ||
if err != nil { | ||
log.Fatalf("Failed to get OAuth config: %v", err) | ||
return nil, err | ||
} | ||
subscriptionID := os.Getenv("AZURE_SUBSCRIPTION_ID") | ||
if err != nil { | ||
return nil, err | ||
} | ||
azureNetworkClient := network.NewService(subscriptionID) | ||
azureNetworkClient.SetAuthorizer(authorizer) | ||
azureResourceManagementClient := resourcemanagement.NewService(subscriptionID) | ||
azureResourceManagementClient.SetAuthorizer(authorizer) | ||
return &services.AzureClients{ | ||
Network: azureNetworkClient, | ||
Resourcemanagement: azureResourceManagementClient, | ||
}, nil | ||
} | ||
|
||
func (azure *AzureClusterClient) network() services.AzureNetworkClient { | ||
return azure.services.Network | ||
} | ||
|
||
func (azure *AzureClusterClient) resourcemanagement() services.AzureResourceManagementClient { | ||
return azure.services.Resourcemanagement | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
/* | ||
Copyright 2018 The Kubernetes 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. | ||
*/ | ||
package cluster | ||
|
||
import ( | ||
"errors" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-02-01/resources" | ||
|
||
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-01-01/network" | ||
"github.com/Azure/go-autorest/autorest" | ||
azureconfigv1 "github.com/platform9/azure-provider/cloud/azure/providerconfig/v1alpha1" | ||
"sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" | ||
clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" | ||
|
||
"github.com/platform9/azure-provider/cloud/azure/services" | ||
yaml "gopkg.in/yaml.v2" | ||
"k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
) | ||
|
||
func TestActuatorCreateSuccess(t *testing.T) { | ||
azureServicesClient := services.AzureClients{Network: &services.MockAzureNetworkClient{}} | ||
params := ClusterActuatorParams{Services: &azureServicesClient} | ||
_, err := NewClusterActuator(params) | ||
if err != nil { | ||
t.Fatalf("unable to create cluster actuator: %v", err) | ||
} | ||
} | ||
|
||
func TestReconcileSuccess(t *testing.T) { | ||
azureServicesClient := mockReconcileSuccess() | ||
params := ClusterActuatorParams{Services: &azureServicesClient} | ||
cluster := newCluster(t) | ||
|
||
actuator, err := NewClusterActuator(params) | ||
if err != nil { | ||
t.Fatalf("unable to create cluster actuator: %v", err) | ||
} | ||
err = actuator.Reconcile(cluster) | ||
if err != nil { | ||
t.Fatalf("unexpected error calling Reconcile: %v", err) | ||
} | ||
} | ||
func TestReconcileFailure(t *testing.T) { | ||
azureServicesClient := mockReconcileFailure() | ||
params := ClusterActuatorParams{Services: &azureServicesClient} | ||
cluster := newCluster(t) | ||
|
||
actuator, err := NewClusterActuator(params) | ||
if err != nil { | ||
t.Fatalf("unable to create cluster actuator: %v", err) | ||
} | ||
err = actuator.Reconcile(cluster) | ||
if err == nil { | ||
t.Fatalf("expected error, but got none") | ||
} | ||
} | ||
|
||
func TestDeleteSuccess(t *testing.T) { | ||
azureServicesClient := mockDeleteRGExists() | ||
params := ClusterActuatorParams{Services: &azureServicesClient} | ||
cluster := newCluster(t) | ||
|
||
actuator, err := NewClusterActuator(params) | ||
if err != nil { | ||
t.Fatalf("unable to create cluster actuator: %v", err) | ||
} | ||
err = actuator.Delete(cluster) | ||
if err != nil { | ||
t.Fatalf("unexpected error calling Delete: %v", err) | ||
} | ||
} | ||
|
||
func TestDeleteFailureRGNotExists(t *testing.T) { | ||
azureServicesClient := mockDeleteRGNotExists() | ||
params := ClusterActuatorParams{Services: &azureServicesClient} | ||
cluster := newCluster(t) | ||
|
||
actuator, err := NewClusterActuator(params) | ||
if err != nil { | ||
t.Fatalf("unable to create cluster actuator: %v", err) | ||
} | ||
err = actuator.Delete(cluster) | ||
if err == nil { | ||
t.Fatalf("expected error, but got none") | ||
} | ||
} | ||
|
||
func mockReconcileSuccess() services.AzureClients { | ||
networkMock := services.MockAzureNetworkClient{ | ||
MockCreateOrUpdateNetworkSecurityGroup: func(resourceGroupName string, networkSecurityGroupName string, location string) (*network.SecurityGroupsCreateOrUpdateFuture, error) { | ||
return &network.SecurityGroupsCreateOrUpdateFuture{}, nil | ||
}, | ||
} | ||
return services.AzureClients{Resourcemanagement: &services.MockAzureResourceManagementClient{}, Network: &networkMock} | ||
} | ||
|
||
func mockReconcileFailure() services.AzureClients { | ||
networkMock := services.MockAzureNetworkClient{ | ||
MockCreateOrUpdateNetworkSecurityGroup: func(resourceGroupName string, networkSecurityGroupName string, location string) (*network.SecurityGroupsCreateOrUpdateFuture, error) { | ||
return nil, errors.New("failed to create resource") | ||
}, | ||
} | ||
return services.AzureClients{Resourcemanagement: &services.MockAzureResourceManagementClient{}, Network: &networkMock} | ||
} | ||
|
||
func mockDeleteRGExists() services.AzureClients { | ||
resourcemanagementMock := services.MockAzureResourceManagementClient{ | ||
MockCheckGroupExistence: func(rgName string) (autorest.Response, error) { | ||
return autorest.Response{Response: &http.Response{StatusCode: 200}}, nil | ||
}, | ||
MockDeleteGroup: func(resourceGroupName string) (resources.GroupsDeleteFuture, error) { | ||
return resources.GroupsDeleteFuture{}, nil | ||
}, | ||
} | ||
return services.AzureClients{Resourcemanagement: &resourcemanagementMock} | ||
} | ||
|
||
func mockDeleteRGNotExists() services.AzureClients { | ||
resourcemanagementMock := services.MockAzureResourceManagementClient{ | ||
MockCheckGroupExistence: func(rgName string) (autorest.Response, error) { | ||
return autorest.Response{Response: &http.Response{StatusCode: 404}}, nil | ||
}, | ||
} | ||
return services.AzureClients{Resourcemanagement: &resourcemanagementMock} | ||
} | ||
|
||
func newClusterProviderConfig() azureconfigv1.AzureClusterProviderConfig { | ||
return azureconfigv1.AzureClusterProviderConfig{ | ||
ResourceGroup: "resource-group-test", | ||
Location: "westus2", | ||
} | ||
} | ||
|
||
func providerConfigFromCluster(in *azureconfigv1.AzureClusterProviderConfig) (*clusterv1.ProviderConfig, error) { | ||
bytes, err := yaml.Marshal(in) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &clusterv1.ProviderConfig{ | ||
Value: &runtime.RawExtension{Raw: bytes}, | ||
}, nil | ||
} | ||
|
||
func newCluster(t *testing.T) *v1alpha1.Cluster { | ||
clusterProviderConfig := newClusterProviderConfig() | ||
providerConfig, err := providerConfigFromCluster(&clusterProviderConfig) | ||
if err != nil { | ||
t.Fatalf("error encoding provider config: %v", err) | ||
} | ||
|
||
return &v1alpha1.Cluster{ | ||
TypeMeta: v1.TypeMeta{ | ||
Kind: "Cluster", | ||
}, | ||
ObjectMeta: v1.ObjectMeta{ | ||
Name: "cluster-test", | ||
}, | ||
Spec: v1alpha1.ClusterSpec{ | ||
ClusterNetwork: v1alpha1.ClusterNetworkingConfig{ | ||
Services: v1alpha1.NetworkRanges{ | ||
CIDRBlocks: []string{ | ||
"10.96.0.0/12", | ||
}, | ||
}, | ||
Pods: v1alpha1.NetworkRanges{ | ||
CIDRBlocks: []string{ | ||
"192.168.0.0/16", | ||
}, | ||
}, | ||
}, | ||
ProviderConfig: *providerConfig, | ||
}, | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the resource group creation and updating should be part of the cluster reconciliation loop