-
Notifications
You must be signed in to change notification settings - Fork 3
Implemented AttachISO task #4
Changes from 4 commits
121c791
e91b808
673db57
503e74a
99c7427
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 |
---|---|---|
|
@@ -450,12 +450,6 @@ func (c *ApplyClusterCmd) Run() error { | |
//&model.SSHKeyModelBuilder{KopsModelContext: modelContext}, | ||
) | ||
case fi.CloudProviderVSphere: | ||
vsphereModelContext := &vspheremodel.VSphereModelContext{ | ||
KopsModelContext: modelContext, | ||
} | ||
|
||
l.Builders = append(l.Builders, | ||
&vspheremodel.VirtualMachineModelBuilder{VSphereModelContext: vsphereModelContext}) | ||
|
||
default: | ||
return fmt.Errorf("unknown cloudprovider %q", cluster.Spec.CloudProvider) | ||
|
@@ -482,6 +476,7 @@ func (c *ApplyClusterCmd) Run() error { | |
} | ||
|
||
role := ig.Spec.Role | ||
glog.V(4).Infof("Generating renderNodeUpConfig for %s", role) | ||
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. Nit: Please move this log statement after if-block. If role is nil then anyways the program is exiting with error. "Generating Nodeup config for role..." would be a more appropriate message. 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. I did not intend to checkin this. It was added for debugging. Will remove this log, |
||
if role == "" { | ||
return nil, fmt.Errorf("cannot determine role for instance group: %v", ig.ObjectMeta.Name) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,7 +25,10 @@ import ( | |
"github.com/vmware/govmomi" | ||
"github.com/vmware/govmomi/find" | ||
"github.com/vmware/govmomi/object" | ||
"github.com/vmware/govmomi/property" | ||
"github.com/vmware/govmomi/vim25" | ||
"github.com/vmware/govmomi/vim25/mo" | ||
"github.com/vmware/govmomi/vim25/soap" | ||
"github.com/vmware/govmomi/vim25/types" | ||
"k8s.io/kops/pkg/apis/kops" | ||
"k8s.io/kops/upup/pkg/fi" | ||
|
@@ -49,9 +52,10 @@ type VSphereCloud struct { | |
} | ||
|
||
const ( | ||
snapshotName string = "LinkCloneSnapshotPoint" | ||
snapshotDesc string = "Snapshot created by kops" | ||
privateDNS string = "coredns" | ||
snapshotName string = "LinkCloneSnapshotPoint" | ||
snapshotDesc string = "Snapshot created by kops" | ||
privateDNS string = "coredns" | ||
cloudInitFile string = "cloud-init.iso" | ||
) | ||
|
||
var _ fi.Cloud = &VSphereCloud{} | ||
|
@@ -221,3 +225,72 @@ func (c *VSphereCloud) PowerOn(vm string) error { | |
task.Wait(ctx) | ||
return nil | ||
} | ||
|
||
func (c *VSphereCloud) UploadAndAttachISO(vm *string, isoFile 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) | ||
|
||
vmRef, err := f.VirtualMachine(ctx, *vm) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var refs []types.ManagedObjectReference | ||
refs = append(refs, vmRef.Reference()) | ||
var vmResult mo.VirtualMachine | ||
|
||
pc := property.DefaultCollector(c.Client.Client) | ||
err = pc.RetrieveOne(ctx, vmRef.Reference(), []string{"datastore"}, &vmResult) | ||
if err != nil { | ||
glog.Fatalf("Unable to retrieve VM summary for VM %s", *vm) | ||
} | ||
glog.V(4).Infof("vm property collector result :%+v\n", vmResult) | ||
|
||
// We expect the VM to be on only 1 datastore | ||
dsRef := vmResult.Datastore[0].Reference() | ||
var dsResult mo.Datastore | ||
err = pc.RetrieveOne(ctx, dsRef, []string{"summary"}, &dsResult) | ||
if err != nil { | ||
glog.Fatalf("Unable to retrieve datastore summary for datastore %s", dsRef) | ||
} | ||
glog.V(4).Infof("datastore property collector result :%+v\n", dsResult) | ||
dsObj, err := f.Datastore(ctx, dsResult.Summary.Name) | ||
if err != nil { | ||
return err | ||
} | ||
p := soap.DefaultUpload | ||
dstIsoFile := getCloudInitFileName(*vm) | ||
glog.V(2).Infof("Uploading ISO file %s to datastore %+v, destination iso is %s\n", isoFile, dsObj, dstIsoFile) | ||
err = dsObj.UploadFile(ctx, isoFile, dstIsoFile, &p) | ||
if err != nil { | ||
return err | ||
} | ||
glog.V(2).Infof("Uploaded ISO file %s", isoFile) | ||
|
||
// Find the cd-rom devide and insert the cloud init iso file into it. | ||
devices, err := vmRef.Device(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// passing empty cd-rom name so that the first one gets returned | ||
cdrom, err := devices.FindCdrom("") | ||
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. Can you please explain with does empty string argument signifies here? 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. Done |
||
if err != nil { | ||
return err | ||
} | ||
iso := dsObj.Path(dstIsoFile) | ||
glog.V(2).Infof("Inserting ISO file %s into cd-rom", iso) | ||
return vmRef.EditDevice(ctx, devices.InsertIso(cdrom, iso)) | ||
|
||
} | ||
|
||
func getCloudInitFileName(vmName string) string { | ||
return vmName + "/" + cloudInitFile | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,19 +17,43 @@ limitations under the License. | |
package vspheretasks | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"github.com/golang/glog" | ||
"github.com/pborman/uuid" | ||
"io/ioutil" | ||
"k8s.io/kops/pkg/apis/kops" | ||
"k8s.io/kops/pkg/model" | ||
"k8s.io/kops/upup/pkg/fi" | ||
"k8s.io/kops/upup/pkg/fi/cloudup/vsphere" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"runtime" | ||
"strings" | ||
) | ||
|
||
// AttachISO represents the cloud-init ISO file attached to a VMware VM | ||
//go:generate fitask -type=AttachISO | ||
type AttachISO struct { | ||
Name *string | ||
VM *VirtualMachine | ||
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. Can you please explain why did you have to remove dependency on VirtualMachine task and add it explicitly via GetDependencies method? It would be nice if we can utilize the dependency graph of topological sort, rather than having to implement explicit GetDependencies method for various vsphere tasks. Also, it would make more sense to get the vm name from VirtualMachine, as opposed to setting it in various tasks that need it, eg: AttachISO and VMPowerOn. 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. VirtualMachine is a task. I wanted to add more members to AttachISO. The way the reflection based task dependency works is that it thinks that every field in the task is a dependency. For example, when I added InstanceGroup and BootstratScript fields in AttachISO task, the reflection based task dependency thinks that InstanceGroup and BootstratScript are tasks and fails complaining that they are not tasks. Basically if the tasks structs are simple, then the reflection based task dependency management is useful but in our case it is not simple. So, I had to explicitly call out the task dependency which is also fine. Basically there are 2 ways of expressing task dependency -
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. Hmm... I don't see this happening for slightly complex aws tasks: 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. I believe I have found the problem that you are talking about. This is the code responsible for ignoring or accounting a dependency for reflect.Struct https://github.com/vmware/kops/blob/master/upup/pkg/fi/topological_sort.go#L98 The problem is that current code identifies only those structs that are listed in the if-else structure. The structs that you are adding to your task, like InstanceGroup and BootstratScript don't end up getting processed by last else-block and hence the error "Unhandled type for...". This if-else structure is not the ideal way of defining dependencies. But implementing HasDepenency method is also bit of a redundant work. I would recommend maintaining a set of types, like InstanceGroup and BootScript, which we don't want to include as a dependency. Check that in the above discussed if-else block. |
||
Name *string | ||
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. I see that you have updated code to use VM.Name. Do you still need this string 'Name' field? 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. I think every task struct need to have the task Name. This was added in my previous checkin and I remember that not having this field was having problems executing the tasks. |
||
VM *VirtualMachine | ||
IG *kops.InstanceGroup | ||
BootstrapScript *model.BootstrapScript | ||
} | ||
|
||
var _ fi.HasName = &AttachISO{} | ||
var _ fi.HasDependencies = &AttachISO{} | ||
|
||
func (o *AttachISO) GetDependencies(tasks map[string]fi.Task) []fi.Task { | ||
var deps []fi.Task | ||
vmCreateTask := tasks["VirtualMachine/"+*o.VM.Name] | ||
if vmCreateTask == nil { | ||
glog.Fatalf("Unable to find create VM task %s dependency for AttachISO %s", *o.VM.Name, *o.Name) | ||
} | ||
deps = append(deps, vmCreateTask) | ||
return deps | ||
} | ||
|
||
// GetName returns the Name of the object, implementing fi.HasName | ||
func (o *AttachISO) GetName() *string { | ||
|
@@ -56,7 +80,83 @@ func (_ *AttachISO) CheckChanges(a, e, changes *AttachISO) error { | |
return nil | ||
} | ||
|
||
func (_ *AttachISO) RenderVC(t *vsphere.VSphereAPITarget, a, e, changes *AttachISO) error { | ||
glog.Info("AttachISO.RenderVC invoked!") | ||
func (_ *AttachISO) RenderVSphere(t *vsphere.VSphereAPITarget, a, e, changes *AttachISO) error { | ||
startupScript, err := changes.BootstrapScript.ResourceNodeUp(changes.IG) | ||
startupStr, err := startupScript.AsString() | ||
if err != nil { | ||
return fmt.Errorf("error rendering startup script: %v", err) | ||
} | ||
dir, err := ioutil.TempDir("", *changes.VM.Name) | ||
defer os.RemoveAll(dir) | ||
|
||
isoFile := createISO(changes, startupStr, dir) | ||
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. I see that createISO method is not returning any error. It would be good to either return an error or check isoFile to be not nil. 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. If there is an error in createISO we fail the execution by invoking glog.Fatalf method. Basically there is no point in continuing if they fail. |
||
err = t.Cloud.UploadAndAttachISO(changes.VM.Name, isoFile) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func createUserData(startupStr string, dir string) { | ||
// Update the startup script to add the extra spaces for | ||
// indentation when copied to the user-data file. | ||
strArray := strings.Split(startupStr, "\n") | ||
for i, str := range strArray { | ||
if len(str) > 0 { | ||
strArray[i] = " " + str | ||
} | ||
} | ||
startupStr = strings.Join(strArray, "\n") | ||
|
||
data := strings.Replace(userDataTemplate, "$SCRIPT", startupStr, -1) | ||
userDataFile := filepath.Join(dir, "user-data") | ||
glog.V(4).Infof("User data file content: %s", data) | ||
|
||
if err := ioutil.WriteFile(userDataFile, []byte(data), 0644); err != nil { | ||
glog.Fatalf("Unable to write user-data into file %s", userDataFile) | ||
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. Errors from createUserData and createMetaData are not getting propagated to createISO. How do you plan on handling them? 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. They fail the kops command. See the usage of glog.Fatalf method in error conditions in those methods. |
||
} | ||
return | ||
} | ||
|
||
func createMetaData(dir string, vmName string) { | ||
data := strings.Replace(metaDataTemplate, "$INSTANCE_ID", uuid.NewUUID().String(), -1) | ||
data = strings.Replace(data, "$LOCAL_HOST_NAME", vmName, -1) | ||
|
||
glog.V(4).Infof("Meta data file content: %s", string(data)) | ||
|
||
metaDataFile := filepath.Join(dir, "meta-data") | ||
if err := ioutil.WriteFile(metaDataFile, []byte(data), 0644); err != nil { | ||
glog.Fatalf("Unable to write meta-data into file %s", metaDataFile) | ||
} | ||
return | ||
} | ||
|
||
func createISO(changes *AttachISO, startupStr string, dir string) string { | ||
createUserData(startupStr, dir) | ||
createMetaData(dir, *changes.VM.Name) | ||
|
||
isoFile := filepath.Join(dir, *changes.VM.Name+".iso") | ||
var cmd *exec.Cmd | ||
|
||
switch os := runtime.GOOS; os { | ||
case "darwin": | ||
cmd = exec.Command("mkisofs", "-o", isoFile, "-volid", "cidata", "-joliet", "-rock", dir) | ||
case "linux": | ||
cmd = exec.Command("genisoimage", "-o", isoFile, "-volid", "cidata", "-joliet", "-rock", dir) | ||
default: | ||
glog.Fatalf("Cannot generate ISO file %s. Unsupported operation system!!!", isoFile) | ||
} | ||
var out bytes.Buffer | ||
cmd.Stdout = &out | ||
var stderr bytes.Buffer | ||
cmd.Stderr = &stderr | ||
|
||
err := cmd.Run() | ||
if err != nil { | ||
glog.Fatalf("Error %s occurred while executing command %+v", err, cmd) | ||
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. Return here with nil? 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. Not needed as err not being nil is fatal and will stop kops. |
||
} | ||
glog.V(4).Infof("genisoimage std output : %s\n", out.String()) | ||
glog.V(4).Infof("genisoimage std error : %s\n", stderr.String()) | ||
return isoFile | ||
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. Please save command name in a variable and use that for logging. It could be genisoimage or mkisofs. 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. Done |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,17 @@ type VMPowerOn struct { | |
} | ||
|
||
var _ fi.HasName = &VMPowerOn{} | ||
var _ fi.HasDependencies = &VMPowerOn{} | ||
|
||
func (o *VMPowerOn) GetDependencies(tasks map[string]fi.Task) []fi.Task { | ||
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. Same question here, why do we need to implement explicit GetDependencies method? |
||
var deps []fi.Task | ||
attachISOTask := tasks["AttachISO/"+*o.AttachISO.Name] | ||
if attachISOTask == nil { | ||
glog.Fatalf("Unable to find attachISO task %s dependency for VMPowerOn %s", *o.AttachISO.Name, *o.Name) | ||
} | ||
deps = append(deps, attachISOTask) | ||
return deps | ||
} | ||
|
||
// GetName returns the Name of the object, implementing fi.HasName | ||
func (o *VMPowerOn) GetName() *string { | ||
|
@@ -56,8 +67,8 @@ func (_ *VMPowerOn) CheckChanges(a, e, changes *VMPowerOn) error { | |
return nil | ||
} | ||
|
||
func (_ *VMPowerOn) RenderVC(t *vsphere.VSphereAPITarget, a, e, changes *VMPowerOn) error { | ||
glog.V(2).Infof("VMPowerOn.RenderVC invoked for vm %s", *changes.AttachISO.VM.Name) | ||
func (_ *VMPowerOn) RenderVSphere(t *vsphere.VSphereAPITarget, a, e, changes *VMPowerOn) error { | ||
glog.V(2).Infof("VMPowerOn.RenderVSphere invoked for vm %s", *changes.AttachISO.VM.Name) | ||
err := t.Cloud.PowerOn(*changes.AttachISO.VM.Name) | ||
return err | ||
} |
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.
wait we would need this builder for PKI model...
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.
Depending on who checks in first, one of us can add it!