package org.ovirt.mobile.movirt.ui.vms; import android.app.DialogFragment; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.support.v4.view.PagerTabStrip; import android.support.v4.view.ViewPager; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.widget.ProgressBar; import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.App; import org.androidannotations.annotations.Background; import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.OptionsItem; import org.androidannotations.annotations.OptionsMenu; import org.androidannotations.annotations.OptionsMenuItem; import org.androidannotations.annotations.ViewById; import org.androidannotations.annotations.res.StringArrayRes; import org.androidannotations.annotations.res.StringRes; import org.ovirt.mobile.movirt.MoVirtApp; import org.ovirt.mobile.movirt.R; import org.ovirt.mobile.movirt.auth.properties.manager.AccountPropertiesManager; import org.ovirt.mobile.movirt.facade.ConsoleFacade; import org.ovirt.mobile.movirt.facade.SnapshotFacade; import org.ovirt.mobile.movirt.facade.VmFacade; import org.ovirt.mobile.movirt.model.Console; import org.ovirt.mobile.movirt.model.Snapshot; import org.ovirt.mobile.movirt.model.Vm; import org.ovirt.mobile.movirt.model.enums.ConsoleProtocol; import org.ovirt.mobile.movirt.model.trigger.Trigger; import org.ovirt.mobile.movirt.provider.OVirtContract; import org.ovirt.mobile.movirt.provider.ProviderFacade; import org.ovirt.mobile.movirt.rest.SimpleResponse; import org.ovirt.mobile.movirt.rest.client.OVirtClient; import org.ovirt.mobile.movirt.rest.client.VvClient; import org.ovirt.mobile.movirt.rest.dto.ConsoleConnectionDetails; import org.ovirt.mobile.movirt.ui.Constants; import org.ovirt.mobile.movirt.ui.FragmentListPagerAdapter; import org.ovirt.mobile.movirt.ui.HasProgressBar; import org.ovirt.mobile.movirt.ui.MovirtActivity; import org.ovirt.mobile.movirt.ui.ProgressBarResponse; import org.ovirt.mobile.movirt.ui.dialogs.ConfirmDialogFragment; import org.ovirt.mobile.movirt.ui.dialogs.CreateSnapshotDialogFragment; import org.ovirt.mobile.movirt.ui.dialogs.CreateSnapshotDialogFragment_; import org.ovirt.mobile.movirt.ui.dialogs.DialogListener; import org.ovirt.mobile.movirt.ui.events.EventsFragment; import org.ovirt.mobile.movirt.ui.events.EventsFragment_; import org.ovirt.mobile.movirt.ui.triggers.EditTriggersActivity; import org.ovirt.mobile.movirt.ui.triggers.EditTriggersActivity_; import org.ovirt.mobile.movirt.util.ObjectUtils; import org.ovirt.mobile.movirt.util.message.MessageHelper; import org.springframework.util.StringUtils; import java.io.File; import java.io.FileWriter; import java.io.Writer; import java.util.EnumMap; import java.util.List; import java.util.Map; @EActivity(R.layout.activity_vm_detail) @OptionsMenu(R.menu.vm) public class VmDetailActivity extends MovirtActivity implements HasProgressBar, ConfirmDialogFragment.ConfirmDialogListener, DialogListener.NewSnapshotListener, LoaderManager.LoaderCallbacks<Cursor> { private static final String TAG = VmDetailActivity.class.getSimpleName(); private static final int REQUEST_MIGRATE = 0; private static final int ACTION_STOP_VM = 0; private static final int ACTION_REBOOT_VM = 1; private static final int ACTION_STOP_MIGRATE_VM = 2; private static final int SNAPSHOTS_LOADER = 1; // 0 in MovirtActivity private static final int VMS_LOADER = 2; private static final int CONSOLES_LOADER = 3; @ViewById ViewPager viewPager; @ViewById PagerTabStrip pagerTabStrip; @StringRes(R.string.details_for_vm) String VM_DETAILS; @StringArrayRes(R.array.vm_detail_pager_titles) String[] PAGER_TITLES; @Bean OVirtClient client; @Bean VvClient vvClient; @Bean AccountPropertiesManager propertiesManager; @Bean ProviderFacade provider; @ViewById ProgressBar progress; @Bean VmFacade vmFacade; @Bean ConsoleFacade consoleFacade; @Bean SnapshotFacade snapshotFacade; @Bean MessageHelper messageHelper; @App MoVirtApp app; @OptionsMenuItem(R.id.action_run) MenuItem menuRun; @OptionsMenuItem(R.id.action_stop) MenuItem menuStop; @OptionsMenuItem(R.id.action_reboot) MenuItem menuReboot; @OptionsMenuItem(R.id.action_start_migration) MenuItem menuStartMigration; @OptionsMenuItem(R.id.action_cancel_migration) MenuItem menuCancelMigration; @OptionsMenuItem(R.id.action_spice_console) MenuItem menuSpiceConsole; @OptionsMenuItem(R.id.action_vnc_console) MenuItem menuVncConsole; @OptionsMenuItem(R.id.action_create_snapshot) MenuItem menuCreateSnapshot; private String vmId = null; private Vm currentVm = null; private boolean menuCreateSnapshotVisibility = false; private boolean hasSpiceConsole = false; private boolean hasVncConsole = false; private Map<ConsoleProtocol, Console> consoles = new EnumMap<>(ConsoleProtocol.class); @AfterViews void init() { Uri vmUri = getIntent().getData(); vmId = vmUri.getLastPathSegment(); initPagers(); initLoaders(); setProgressBar(progress); } private void initPagers() { EventsFragment eventList = new EventsFragment_(); VmDisksFragment diskList = new VmDisksFragment_(); VmNicsFragment nicList = new VmNicsFragment_(); VmSnapshotsFragment snapshotList = new VmSnapshotsFragment_(); eventList.setFilterVmId(vmId); diskList.setVmId(vmId); nicList.setVmId(vmId); snapshotList.setVmId(vmId); FragmentListPagerAdapter pagerAdapter = new FragmentListPagerAdapter( getSupportFragmentManager(), PAGER_TITLES, new VmDetailGeneralFragment_(), eventList, snapshotList, diskList, nicList); viewPager.setAdapter(pagerAdapter); pagerTabStrip.setTabIndicatorColorResource(R.color.material_deep_teal_200); refreshConsoles(); } private void initLoaders() { getSupportLoaderManager().initLoader(SNAPSHOTS_LOADER, null, this); getSupportLoaderManager().initLoader(VMS_LOADER, null, this); getSupportLoaderManager().initLoader(CONSOLES_LOADER, null, this); } @Override public void restartLoader() { super.restartLoader(); getSupportLoaderManager().restartLoader(SNAPSHOTS_LOADER, null, this); getSupportLoaderManager().restartLoader(VMS_LOADER, null, this); getSupportLoaderManager().restartLoader(CONSOLES_LOADER, null, this); } @Override public void destroyLoader() { super.destroyLoader(); getSupportLoaderManager().destroyLoader(SNAPSHOTS_LOADER); getSupportLoaderManager().destroyLoader(VMS_LOADER); getSupportLoaderManager().destroyLoader(CONSOLES_LOADER); } @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { Loader<Cursor> loader = null; switch (id) { case SNAPSHOTS_LOADER: loader = provider.query(Snapshot.class).where(OVirtContract.Snapshot.VM_ID, vmId).asLoader(); break; case VMS_LOADER: loader = provider.query(Vm.class).id(vmId).asLoader(); break; case CONSOLES_LOADER: loader = provider.query(Console.class).where(OVirtContract.Console.VM_ID, vmId).asLoader(); break; } return loader; } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { if (!data.moveToNext()) { Log.e(TAG, "Error loading data: id=" + loader.getId()); return; } switch (loader.getId()) { case SNAPSHOTS_LOADER: List<Snapshot> snapshots = snapshotFacade.mapAllFromCursor(data); menuCreateSnapshotVisibility = !Snapshot.containsOneOfStatuses(snapshots, Snapshot.SnapshotStatus.LOCKED, Snapshot.SnapshotStatus.IN_PREVIEW); break; case VMS_LOADER: currentVm = vmFacade.mapFromCursor(data); break; case CONSOLES_LOADER: for (Console console : consoleFacade.mapAllFromCursor(data)) { consoles.put(console.getProtocol(), console); } hasSpiceConsole = consoles.containsKey(ConsoleProtocol.SPICE); hasVncConsole = consoles.containsKey(ConsoleProtocol.VNC); break; } invalidateOptionsMenu(); } @Override public void onLoaderReset(Loader<Cursor> loader) { // do nothing } @Override public boolean onPrepareOptionsMenu(Menu menu) { if (currentVm != null) { setTitle(String.format(VM_DETAILS, currentVm.getName())); Vm.Status status = currentVm.getStatus(); menuRun.setVisible(Vm.Command.RUN.canExecute(status)); menuStop.setVisible(Vm.Command.STOP.canExecute(status)); menuReboot.setVisible(Vm.Command.REBOOT.canExecute(status)); menuStartMigration.setVisible(Vm.Command.START_MIGRATION.canExecute(status)); menuCancelMigration.setVisible(Vm.Command.CANCEL_MIGRATION.canExecute(status)); menuCreateSnapshot.setVisible(menuCreateSnapshotVisibility); boolean consoleExecutable = Vm.Command.CONSOLE.canExecute(status); menuSpiceConsole.setVisible(consoleExecutable && hasSpiceConsole); menuVncConsole.setVisible(consoleExecutable && hasVncConsole); } return super.onPrepareOptionsMenu(menu); } @OptionsItem(R.id.action_edit_triggers) void editTriggers() { final Intent intent = new Intent(this, EditTriggersActivity_.class); intent.putExtra(EditTriggersActivity.EXTRA_TARGET_ENTITY_ID, vmId); intent.putExtra(EditTriggersActivity.EXTRA_TARGET_ENTITY_NAME, vmId); intent.putExtra(EditTriggersActivity.EXTRA_SCOPE, Trigger.Scope.ITEM); startActivity(intent); } @OptionsItem(R.id.action_run) @Background void start() { client.startVm(vmId, new SyncVmResponse()); } @OptionsItem(R.id.action_stop) void stop() { DialogFragment confirmDialog = ConfirmDialogFragment .newInstance(ACTION_STOP_VM, getString(R.string.dialog_action_stop_vm)); confirmDialog.show(getFragmentManager(), "confirmStopVM"); } @OptionsItem(R.id.action_reboot) void reboot() { DialogFragment confirmDialog = ConfirmDialogFragment .newInstance(ACTION_REBOOT_VM, getString(R.string.dialog_action_reboot_vm)); confirmDialog.show(getFragmentManager(), "confirmRebootVM"); } @OptionsItem(R.id.action_cancel_migration) void cancelMigration() { DialogFragment confirmDialog = ConfirmDialogFragment .newInstance(ACTION_STOP_MIGRATE_VM, getString(R.string.dialog_action_stop_migrate_vm)); confirmDialog.show(getFragmentManager(), "confirmStopMigrateVM"); } @Override public void onDialogResult(int dialogButton, int actionId) { if (dialogButton == DialogInterface.BUTTON_POSITIVE) { switch (actionId) { case ACTION_STOP_VM: doStop(); break; case ACTION_REBOOT_VM: doReboot(); break; case ACTION_STOP_MIGRATE_VM: doCancelMigration(); break; } } } @Background void doStop() { client.stopVm(vmId, new SyncVmResponse()); } @Background void doReboot() { client.rebootVm(vmId, new SyncVmResponse()); } @Background void doCancelMigration() { client.cancelMigration(vmId, new SyncVmResponse()); } @OptionsItem(R.id.action_start_migration) void showMigrationDialog() { if (currentVm != null) { Intent migrateIntent = new Intent(this, VmMigrateActivity_.class); migrateIntent.putExtra(VmMigrateActivity.HOST_ID_EXTRA, currentVm.getHostId()); migrateIntent.putExtra(VmMigrateActivity.CLUSTER_ID_EXTRA, currentVm.getClusterId()); startActivityForResult(migrateIntent, REQUEST_MIGRATE); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_MIGRATE) { if (resultCode == VmMigrateActivity.RESULT_DEFAULT) { doMigrationToDefault(); } if (resultCode == VmMigrateActivity.RESULT_SELECT) { doMigrationTo(data.getStringExtra(VmMigrateActivity.RESULT_HOST_ID_EXTRA)); } } } @Background public void doMigrationToDefault() { client.migrateVmToDefaultHost(vmId, new SyncVmResponse()); } @Background public void doMigrationTo(String hostId) { client.migrateVmToHost(vmId, hostId, new SyncVmResponse()); } @OptionsItem(R.id.action_create_snapshot) void createSnapshot() { CreateSnapshotDialogFragment dialog = new CreateSnapshotDialogFragment_(); dialog.setVmId(vmId); dialog.show(getFragmentManager(), "createSnapshot"); } @Override @Background public void onDialogResult(org.ovirt.mobile.movirt.rest.dto.Snapshot snapshot) { client.createSnapshot(snapshot, vmId, new SimpleResponse<Void>() { @Override public void onResponse(Void aVoid) throws RemoteException { snapshotFacade.syncAll(vmId); } }); } @OptionsItem(R.id.action_spice_console) @Background void openSpiceConsole() { openConsole(ConsoleProtocol.SPICE); } @OptionsItem(R.id.action_vnc_console) @Background void openVncConsole() { openConsole(ConsoleProtocol.VNC); } private void openConsole(final ConsoleProtocol protocol) { Console console = consoles.get(protocol); vvClient.getConsoleConnectionDetails(vmId, console.getId(), new ProgressBarResponse<ConsoleConnectionDetails>(this) { @Override public void onResponse(ConsoleConnectionDetails details) throws RemoteException { connectToConsole(details); } }); } private void connectToConsole(final ConsoleConnectionDetails details) { try { String caCertPath = null; if (details.getProtocol() == ConsoleProtocol.SPICE && details.getTlsPort() > 0) { caCertPath = Constants.getConsoleCertPath(this); saveCertToFile(caCertPath, details.getCertificate()); } Intent intent = new Intent(Intent.ACTION_VIEW) .setType("application/vnd.vnc") .setData(Uri.parse(makeConsoleUrl(details, caCertPath))); startActivity(intent); } catch (IllegalArgumentException e) { messageHelper.showToast(e.getMessage()); } catch (Exception e) { messageHelper.showToast("Failed to open console client. Check if aSPICE/bVNC is installed."); } } private void saveCertToFile(String caCertPath, String certificate) { if (StringUtils.isEmpty(certificate)) { throw new IllegalArgumentException("Certificate is missing"); } Writer writer = null; try { writer = new FileWriter(new File(caCertPath)); writer.write(certificate); } catch (Exception e) { throw new IllegalArgumentException("Error storing certificate to file: " + e.getMessage()); } finally { ObjectUtils.closeSilently(writer); } } @Background void refreshConsoles() { consoleFacade.syncAll(new ProgressBarResponse<List<Console>>(this), vmId); } private void syncVm() { vmFacade.syncOne(new ProgressBarResponse<Vm>(this), vmId); refreshConsoles(); } /** * Returns URL for running console intent. * * @throws java.lang.IllegalArgumentException with description * if the URL can't be created from input. */ private String makeConsoleUrl(ConsoleConnectionDetails details, String caCertPath) throws IllegalArgumentException { ConsoleProtocol protocol = details.getProtocol(); if (protocol == null) { throw new IllegalArgumentException("Vm's protocol is missing"); } if (StringUtils.isEmpty(details.getPassword())) { throw new IllegalArgumentException("Password is missing"); } if (StringUtils.isEmpty(details.getAddress())) { throw new IllegalArgumentException("Address is missing"); } String parameters = ""; switch (protocol) { case VNC: parameters = Constants.PARAM_VNC_PWD + "=" + details.getPassword(); // vnc password break; case SPICE: parameters = Constants.PARAM_SPICE_PWD + "=" + details.getPassword(); // spice password if (details.getTlsPort() > 0) { if (StringUtils.isEmpty(details.getCertificateSubject())) { throw new IllegalArgumentException("Certificate subject is missing"); } if (StringUtils.isEmpty(caCertPath)) { throw new IllegalArgumentException("Certificate path is missing"); } String tlsPortPart = Constants.PARAM_TLS_PORT + "=" + details.getTlsPort(); String certSubjectPart = Constants.PARAM_CERT_SUBJECT + "=" + details.getCertificateSubject(); String caCertPathPart = Constants.PARAM_CA_CERT_PATH + "=" + caCertPath; parameters += "&" + tlsPortPart + "&" + certSubjectPart + "&" + caCertPathPart; } break; } return protocol.getProtocol() + "://" + details.getAddress() + ":" + details.getPort() + "?" + parameters; } /** * Refreshes VM upon success */ private class SyncVmResponse extends SimpleResponse<Void> { @Override public void onResponse(Void obj) throws RemoteException { syncVm(); } } @OptionsItem(android.R.id.home) public void homeSelected() { app.startMainActivity(); } }