Skip to content
This repository has been archived by the owner on Jan 21, 2020. It is now read-only.

Azure plugins - instance and loadbalancer #709

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/infrakit/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (

// Supported "kinds"
_ "github.com/docker/infrakit/pkg/run/v0/aws"
_ "github.com/docker/infrakit/pkg/run/v0/azure"
_ "github.com/docker/infrakit/pkg/run/v0/combo"
_ "github.com/docker/infrakit/pkg/run/v0/digitalocean"
_ "github.com/docker/infrakit/pkg/run/v0/docker"
Expand Down
78 changes: 78 additions & 0 deletions pkg/provider/azure/plugin/instance/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package instance

import (
"math/rand"
"sort"
)

func asTagMap(m *map[string]*string) map[string]string {
x := map[string]string{}
if m == nil {
return x
}

for k, v := range *m {
if v != nil {
x[k] = *v
} else {
x[k] = ""
}
}
return x
}

func formatTags(m map[string]string) *map[string]*string {
x := map[string]*string{}

for k, v := range m {
copy := v
x[k] = &copy
}
return &x
}

// mergeTags merges multiple maps of tags, implementing 'last write wins' for colliding keys.
// Returns a sorted slice of all keys, and the map of merged tags. Sorted keys are particularly useful to assist in
// preparing predictable output such as for tests.
func mergeTags(tagMaps ...map[string]string) ([]string, map[string]string) {
keys := []string{}
tags := map[string]string{}
for _, tagMap := range tagMaps {
for k, v := range tagMap {
if _, exists := tags[k]; exists {
log.Warn("Overwriting tag value", "key", k)
} else {
keys = append(keys, k)
}
tags[k] = v
}
}
sort.Strings(keys)
return keys, tags
}

func hasDifferentTags(expected, actual map[string]string) bool {
if len(actual) == 0 {
return true
}
for k, v := range expected {
if a, ok := actual[k]; ok && a != v {
return true
}
}

return false
}

var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz0123456789")

// RandomSuffix generate a random instance name suffix of length `n`.
func randomSuffix(n int) string {
suffix := make([]rune, n)

for i := range suffix {
suffix[i] = letterRunes[rand.Intn(len(letterRunes))]
}

return string(suffix)
}
239 changes: 239 additions & 0 deletions pkg/provider/azure/plugin/instance/virtualmachines.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
package instance

import (
"fmt"

"github.com/Azure/azure-sdk-for-go/arm/compute"
"github.com/Azure/go-autorest/autorest"
logutil "github.com/docker/infrakit/pkg/log"
"github.com/docker/infrakit/pkg/provider/azure/plugin"
"github.com/docker/infrakit/pkg/spi/instance"
"github.com/docker/infrakit/pkg/types"
)

var (
log = logutil.New("module", "azure/instance")
debugV = logutil.V(500)
)

// NewVirtualMachinePlugin returns a typed instance plugin
func NewVirtualMachinePlugin(options plugin.Options) instance.Plugin {
client := compute.NewVirtualMachinesClient(options.SubscriptionID)
client.Authorizer = autorest.NewBearerAuthorizer(options)
return &virtualMachinesPlugin{
virtualMachinesAPI: &virtualMachinesClient{
VirtualMachinesClient: &client,
},
options: options,
}
}

type virtualMachinesAPI interface {
createOrUpdate(resourceGroupName, name string, vm compute.VirtualMachine) (<-chan compute.VirtualMachine, <-chan error)
list(resourceGroupName string) (compute.VirtualMachineListResult, error)
next(compute.VirtualMachineListResult) (compute.VirtualMachineListResult, error)
get(resourceGroupName, name string) (compute.VirtualMachine, error)
delete(resourceGroupName, name string) (<-chan compute.OperationStatusResponse, <-chan error)
}

type virtualMachinesClient struct {
*compute.VirtualMachinesClient
}

func (v *virtualMachinesClient) createOrUpdate(resourceGroupName, name string,
vm compute.VirtualMachine) (<-chan compute.VirtualMachine, <-chan error) {
return v.CreateOrUpdate(resourceGroupName, name, vm, nil)
}

func (v *virtualMachinesClient) list(resourceGroupName string) (compute.VirtualMachineListResult, error) {
return v.List(resourceGroupName)
}

func (v *virtualMachinesClient) next(r compute.VirtualMachineListResult) (compute.VirtualMachineListResult, error) {
return v.ListNextResults(r)
}

func (v *virtualMachinesClient) get(resourceGroupName, name string) (compute.VirtualMachine, error) {
return v.Get(resourceGroupName, name, compute.InstanceView)
}

func (v *virtualMachinesClient) delete(resourceGroupName, name string) (<-chan compute.OperationStatusResponse, <-chan error) {
return v.Delete(resourceGroupName, name, nil)
}

type virtualMachinesPlugin struct {
virtualMachinesAPI
options plugin.Options
}

// Validate performs local validation on a provision request.
func (p *virtualMachinesPlugin) Validate(req *types.Any) error {
log.Debug("Validate", "req", req)
vm := compute.VirtualMachine{}
return req.Decode(&vm)
}

// Provision creates a new instance based on the spec.
func (p *virtualMachinesPlugin) Provision(spec instance.Spec) (*instance.ID, error) {
log.Debug("Provision", spec, "V", debugV)

vm := &virtualMachine{}
if spec.Properties == nil {
return nil, fmt.Errorf("missing properties")
}

err := spec.Properties.Decode(vm)
if err != nil {
return nil, err
}

vm.mergeTags(spec.Tags, p.options.Namespace).addInit(spec.Init)

vmName := fmt.Sprintf("vm-%v", randomSuffix(8))
vmChan, errChan := p.virtualMachinesAPI.createOrUpdate(
p.options.ResourceGroup, vmName, compute.VirtualMachine(*vm))

provisioned := <-vmChan
err = <-errChan

var instanceID *instance.ID
if provisioned.ID != nil {
// Azure API always uses vmName so we can just return this as the instance ID
// so that we have a handle on future api calls
id := instance.ID(vmName)
instanceID = &id
}

return instanceID, err
}

// Label labels the instance
func (p *virtualMachinesPlugin) Label(instance instance.ID, labels map[string]string) error {
log.Debug("Label", "instance", instance, "labels", labels, "V", debugV)

v, err := p.virtualMachinesAPI.get(p.options.ResourceGroup, string(instance))
if err != nil {
return err
}

vm := virtualMachine(v).mergeTags(labels, p.options.Namespace)

_, errChan := p.virtualMachinesAPI.createOrUpdate(
p.options.ResourceGroup, string(instance), compute.VirtualMachine(*vm))

return <-errChan
}

// Destroy terminates an existing instance.
func (p *virtualMachinesPlugin) Destroy(instance instance.ID, context instance.Context) error {
log.Debug("Destroy", "instance", instance, "context", context, "V", debugV)

_, errChan := p.virtualMachinesAPI.delete(p.options.ResourceGroup, string(instance))
return <-errChan
}

// DescribeInstances returns descriptions of all instances matching all of the provided tags.
// The properties flag indicates the client is interested in receiving details about each instance.
func (p *virtualMachinesPlugin) DescribeInstances(labels map[string]string,
properties bool) ([]instance.Description, error) {
log.Debug("DescribeInstances", "labels", labels, "V", debugV)

matches := []instance.Description{}

all, err := p.virtualMachinesAPI.list(p.options.ResourceGroup)
if err != nil {
return matches, err
}
if all.Value != nil {
desc, err := vms(*all.Value).filter(labels).descriptions()
if err != nil {
return matches, err
}
matches = append(matches, desc...)
}
for all.NextLink != nil {
all, err = p.virtualMachinesAPI.next(all)
if err != nil {
return matches, err
}
if all.Value != nil {
desc, err := vms(*all.Value).filter(labels).descriptions()
if err != nil {
return matches, err
}
matches = append(matches, desc...)
}
}

return matches, nil
}

type vms []compute.VirtualMachine

func (v vms) filter(labels map[string]string) vms {
filtered := vms{}
for _, vm := range v {
if hasDifferentTags(labels, asTagMap(vm.Tags)) {
continue
}
filtered = append(filtered, vm)
}
return filtered
}

func (v vms) descriptions() ([]instance.Description, error) {
descriptions := []instance.Description{}
for _, vm := range v {

if vm.ID == nil {
continue
}

props, err := types.AnyValue(v)
if err != nil {
return nil, err
}
desc := instance.Description{
ID: instance.ID(*vm.ID),
LogicalID: virtualMachine(vm).logicalID(),
Properties: props,
Tags: asTagMap(vm.Tags),
}
descriptions = append(descriptions, desc)
}
return nil, nil
}

type virtualMachine compute.VirtualMachine

func (vm virtualMachine) logicalID() *instance.LogicalID {
// Azure uses name throughout its api, so we just use the name as the logical ID.
if vm.Name == nil {
return nil
}
logical := instance.LogicalID(*vm.Name)
return &logical
}

func (vm virtualMachine) mergeTags(a, b map[string]string) *virtualMachine {
vmm := vm
_, merged := mergeTags(asTagMap(vmm.Tags), a, b)
vmm.Tags = formatTags(merged)
return &vmm
}

func (vm virtualMachine) addInit(initStr string) *virtualMachine {
vmm := vm
init := initStr
if vmm.OsProfile != nil {
if vmm.OsProfile.CustomData != nil {
init = *vmm.OsProfile.CustomData + "\n" + init
}
vmm.OsProfile.CustomData = &init
} else {
vmm.OsProfile = &compute.OSProfile{
CustomData: &init,
}
}
return &vmm
}
Loading