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

Commit

Permalink
Implemented creating a link cloned VM from a template VM (#5)
Browse files Browse the repository at this point in the history
Implemented CreateLinkClonedVM cloud interface to create a link cloned VM from a template VM. The code checks if the template VM has a snapshot, if no it creates it before creating a link cloned VM. If snapshot already exists, it uses it to create the link cloned VM.

Testing done:
1. kops cluster create goes through fine and creates the link cloned VM for the master and worker. Verified that it creates the snapshot on the template VM if it does not exists before creating a link cloned VM. In case the snapshot exists, it uses it to create the link cloned VM.
2. "make ci" is successful.
  • Loading branch information
SandeepPissay authored and Miao Luo committed Mar 30, 2017
1 parent 478142a commit d5dc372
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 12 deletions.
8 changes: 5 additions & 3 deletions pkg/model/vspheremodel/autoscalinggroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ limitations under the License.
package vspheremodel

import (
"github.com/golang/glog"
"k8s.io/kops/pkg/model"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/vspheretasks"
Expand All @@ -32,13 +31,16 @@ type AutoscalingGroupModelBuilder struct {

var _ fi.ModelBuilder = &AutoscalingGroupModelBuilder{}

const defaultVmTemplateName = "Ubuntu_16_10"

func (b *AutoscalingGroupModelBuilder) Build(c *fi.ModelBuilderContext) error {
glog.Warning("AutoscalingGroupModelBuilder.Build not implemented for vsphere")
// Note that we are creating a VM per instance group. Instance group represents a group of VMs.
// This logic should change once we add support for multiple master and worker nodes.
for _, ig := range b.InstanceGroups {
name := b.AutoscalingGroupName(ig)
createVmTask := &vspheretasks.VirtualMachine{
Name: &name,
VMTemplateName: fi.String("dummyVmTemplate"),
VMTemplateName: fi.String(defaultVmTemplateName),
}
c.AddTask(createVmTask)

Expand Down
112 changes: 108 additions & 4 deletions upup/pkg/fi/cloudup/vsphere/vsphere_cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,20 @@ limitations under the License.
package vsphere

import (
"context"
"fmt"
"github.com/golang/glog"
"github.com/pkg/errors"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/types"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kubernetes/federation/pkg/dnsprovider"
k8sroute53 "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/aws/route53"
"net/url"
"os"
)

Expand All @@ -32,8 +40,14 @@ type VSphereCloud struct {
Cluster string
Username string
Password string
Client *govmomi.Client
}

const (
snapshotName string = "LinkCloneSnapshotPoint"
snapshotDesc string = "Snapshot created by kops"
)

var _ fi.Cloud = &VSphereCloud{}

func (c *VSphereCloud) ProviderID() fi.CloudProviderID {
Expand All @@ -44,15 +58,32 @@ func NewVSphereCloud(spec *kops.ClusterSpec) (*VSphereCloud, error) {
server := *spec.CloudConfig.VSphereServer
datacenter := *spec.CloudConfig.VSphereDatacenter
cluster := *spec.CloudConfig.VSphereResourcePool
glog.V(2).Infof("Creating vSphere Cloud with server(%s), datacenter(%s), cluster(%s)", server, datacenter, cluster)

username := os.Getenv("VSPHERE_USERNAME")
password := os.Getenv("VSPHERE_PASSWORD")
if username == "" || password == "" {
return nil, fmt.Errorf("Failed to detect vSphere username and password. Please set env variables: VSPHERE_USERNAME and VSPHERE_PASSWORD accordingly.")
}

c := &VSphereCloud{Server: server, Datacenter: datacenter, Cluster: cluster, Username: username, Password: password}
// TODO: create a client of govmomi here?
return c, nil
u, err := url.Parse(fmt.Sprintf("https://%s/sdk", server))
if err != nil {
return nil, err
}
glog.V(2).Infof("Creating vSphere Cloud URL is %s", u)

// set username and password in URL
u.User = url.UserPassword(username, password)

c, err := govmomi.NewClient(context.TODO(), u, true)
if err != nil {
return nil, err
}
// Add retry functionality
c.RoundTripper = vim25.Retry(c.RoundTripper, vim25.TemporaryNetworkError(5))
vsphereCloud := &VSphereCloud{Server: server, Datacenter: datacenter, Cluster: cluster, Username: username, Password: password, Client: c}
glog.V(2).Infof("Created vSphere Cloud successfully: %+v", vsphereCloud)
return vsphereCloud, nil
}

func (c *VSphereCloud) DNS() (dnsprovider.Interface, error) {
Expand All @@ -66,6 +97,79 @@ func (c *VSphereCloud) DNS() (dnsprovider.Interface, error) {
}

func (c *VSphereCloud) FindVPCInfo(id string) (*fi.VPCInfo, error) {
glog.Warningf("FindVPCInfo not (yet) implemented on VSphere")
glog.Warning("FindVPCInfo not (yet) implemented on VSphere")
return nil, nil
}

func (c *VSphereCloud) CreateLinkClonedVm(vmName, vmImage *string) (string, error) {
f := find.NewFinder(c.Client.Client, true)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

dc, err := f.Datacenter(ctx, c.Datacenter)
if err != nil {
return "", err
}
f.SetDatacenter(dc)

templateVm, err := f.VirtualMachine(ctx, *vmImage)
if err != nil {
return "", err
}

glog.V(2).Infof("Template VM ref is %+v", templateVm)
datacenterFolders, err := dc.Folders(ctx)
if err != nil {
return "", err
}

// Create snapshot of the template VM if not already snapshotted.
snapshot, err := createSnapshot(ctx, templateVm, snapshotName, snapshotDesc)
if err != nil {
return "", err
}

clsComputeRes, err := f.ClusterComputeResource(ctx, c.Cluster)
glog.V(4).Infof("Cluster compute resource is %+v", clsComputeRes)
if err != nil {
return "", err
}

resPool, err := clsComputeRes.ResourcePool(ctx)
glog.V(4).Infof("Cluster resource pool is %+v", resPool)
if err != nil {
return "", err
}

if resPool == nil {
return "", errors.New(fmt.Sprintf("No resource pool found for cluster %s", c.Cluster))
}

resPoolRef := resPool.Reference()
snapshotRef := snapshot.Reference()

cloneSpec := &types.VirtualMachineCloneSpec{
Config: &types.VirtualMachineConfigSpec{},
Location: types.VirtualMachineRelocateSpec{
Pool: &resPoolRef,
DiskMoveType: "createNewChildDiskBacking",
},
Snapshot: &snapshotRef,
}

// Create a link cloned VM from the template VM's snapshot
clonedVmTask, err := templateVm.Clone(ctx, datacenterFolders.VmFolder, *vmName, *cloneSpec)
if err != nil {
return "", err
}

clonedVmTaskInfo, err := clonedVmTask.WaitForResult(ctx, nil)
if err != nil {
return "", err
}

clonedVm := clonedVmTaskInfo.Result.(object.Reference)
glog.V(2).Infof("Created VM %s successfully", clonedVm)

return clonedVm.Reference().Value, nil
}
103 changes: 103 additions & 0 deletions upup/pkg/fi/cloudup/vsphere/vsphere_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
Copyright 2017 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 vsphere

import (
"context"
"github.com/golang/glog"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"path"
"sync"
)

var snapshotLock sync.Mutex

func createSnapshot(ctx context.Context, vm *object.VirtualMachine, snapshotName string, snapshotDesc string) (object.Reference, error) {
snapshotLock.Lock()
defer snapshotLock.Unlock()

snapshotRef, err := findSnapshot(vm, ctx, snapshotName)
if err != nil {
return nil, err
}
glog.V(4).Infof("Template VM is %s and snapshot is %s", vm, snapshotRef)
if snapshotRef != nil {
return snapshotRef, nil
}

task, err := vm.CreateSnapshot(ctx, snapshotName, snapshotDesc, false, false)
if err != nil {
return nil, err
}

taskInfo, err := task.WaitForResult(ctx, nil)
if err != nil {
return nil, err
}
glog.Infof("taskInfo.Result is %s", taskInfo.Result)
return taskInfo.Result.(object.Reference), nil
}

type snapshotMap map[string][]object.Reference

func (m snapshotMap) add(parent string, tree []types.VirtualMachineSnapshotTree) {
for i, st := range tree {
sname := st.Name
names := []string{sname, st.Snapshot.Value}

if parent != "" {
sname = path.Join(parent, sname)
// Add full path as an option to resolve duplicate names
names = append(names, sname)
}

for _, name := range names {
m[name] = append(m[name], &tree[i].Snapshot)
}

m.add(sname, st.ChildSnapshotList)
}
}

func findSnapshot(v *object.VirtualMachine, ctx context.Context, name string) (object.Reference, error) {
var o mo.VirtualMachine

err := v.Properties(ctx, v.Reference(), []string{"snapshot"}, &o)
if err != nil {
return nil, err
}

if o.Snapshot == nil || len(o.Snapshot.RootSnapshotList) == 0 {
return nil, nil
}

m := make(snapshotMap)
m.add("", o.Snapshot.RootSnapshotList)

s := m[name]
switch len(s) {
case 0:
return nil, nil
case 1:
return s[0], nil
default:
glog.Warningf("VM %s seems to have more than one snapshots with name %s. Using a random snapshot.", v, name)
return s[0], nil
}
}
14 changes: 9 additions & 5 deletions upup/pkg/fi/cloudup/vspheretasks/virtualmachine.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,26 +48,30 @@ func (o *VirtualMachine) String() string {
}

func (e *VirtualMachine) CompareWithID() *string {
glog.Info("VirtualMachine.CompareWithID invoked!")
glog.V(4).Info("VirtualMachine.CompareWithID invoked!")
return e.Name
}

func (e *VirtualMachine) Find(c *fi.Context) (*VirtualMachine, error) {
glog.Info("VirtualMachine.Find invoked!")
glog.V(4).Info("VirtualMachine.Find invoked!")
return nil, nil
}

func (e *VirtualMachine) Run(c *fi.Context) error {
glog.Info("VirtualMachine.Run invoked!")
glog.V(4).Info("VirtualMachine.Run invoked!")
return fi.DefaultDeltaRunMethod(e, c)
}

func (_ *VirtualMachine) CheckChanges(a, e, changes *VirtualMachine) error {
glog.Info("VirtualMachine.CheckChanges invoked!")
glog.V(4).Info("VirtualMachine.CheckChanges invoked!")
return nil
}

func (_ *VirtualMachine) RenderVSphere(t *vsphere.VSphereAPITarget, a, e, changes *VirtualMachine) error {
glog.Info("VirtualMachine.RenderVSphere invoked!")
glog.V(4).Infof("VirtualMachine.RenderVSphere invoked with a(%+v) e(%+v) and changes(%+v)", a, e, changes)
_, err := t.Cloud.CreateLinkClonedVm(changes.Name, changes.VMTemplateName)
if err != nil {
return err
}
return nil
}

0 comments on commit d5dc372

Please sign in to comment.